Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9fdd94c
refactor(setup nu): replace action flags with subcommands + shared co…
tonythethompson Aug 1, 2026
633c50c
fix: 3 findings — Prevent PATH subcommand from deleting active managed N
qodo-code-review[bot] Aug 1, 2026
fb0ff23
fix: address PR #66 review feedback
tonythethompson Aug 1, 2026
438d80b
fix: clarify PATH messaging in Nu setup hints
tonythethompson Aug 1, 2026
7e726a2
Add reserved `numan use` CLI stub
tonythethompson Aug 1, 2026
7951797
fix: apply CodeRabbit auto-fixes
coderabbitai[bot] Aug 1, 2026
8062a11
feat: implement numan use for side-by-side Nu version management
tonythethompson Aug 1, 2026
213e874
fix: 5 findings — Validate and normalize Nu versions; Propagate legacy m
qodo-code-review[bot] Aug 1, 2026
b8f4233
fix: address PR review — validate before delete, fix help text, fix c…
tonythethompson Aug 1, 2026
62b8656
chore: remove temp files
tonythethompson Aug 1, 2026
2591036
Update src/cmd/snapshot.rs
tonythethompson Aug 2, 2026
d9b8e0f
fix: PR 67 review - bootstrap yes flag, version check, mutation lock
tonythethompson Aug 2, 2026
0429557
Merge origin/master into feature/numan-use - resolve conflicts: take …
tonythethompson Aug 2, 2026
e245232
Update .gitignore
tonythethompson Aug 2, 2026
641bd24
wip(numan-use): integrate use + migration pieces (split pending)
tonythethompson Aug 2, 2026
3a38bbf
feat(numan-use): strip migration pieces for pr-migrate split
tonythethompson Aug 2, 2026
6894382
fix(cmd): address PR 67 review findings (validate versions, drop --us…
tonythethompson Aug 2, 2026
25f57b1
fix(bootstrap): install_from_archive writes to versioned layout
tonythethompson Aug 2, 2026
7c8cc58
fix(numan-use): address remaining 7 PR 67 review threads
tonythethompson Aug 2, 2026
ec949e9
fix(numan-use): confirm.rs ConfirmPolicy + hints.rs shell_quote + set…
tonythethompson Aug 2, 2026
e15e5d3
ci(roadmap): detect roadmap drift before it ships
tonythethompson Aug 2, 2026
d5432d9
docs(roadmap): audit each Post-1.0 claim against shipped code
tonythethompson Aug 2, 2026
99aa695
ci(roadmap): mirror drift check into numan-plugins and numan-registry
tonythethompson Aug 2, 2026
dcd6ad6
Merge branch 'master' into feature/numan-use
tonythethompson Aug 2, 2026
48c3941
fix(version_manager): make is_version_installed consult off-tree acti…
devin-ai-integration[bot] Aug 2, 2026
5022ce2
fix: resolve MSRV compile, format, and test failures for numan use
devin-ai-integration[bot] Aug 2, 2026
ad6ce88
Apply 3 edits across 1 file
devin-ai-integration[bot] Aug 2, 2026
8acbfeb
Apply 1 edit across 1 file
devin-ai-integration[bot] Aug 2, 2026
5d200a1
fix: don't take mutation lock/snapshot for read-only `numan use list`
devin-ai-integration[bot] Aug 2, 2026
6f842c4
fix: rustfmt and update injected-installer test for versioned layout
devin-ai-integration[bot] Aug 2, 2026
193fbb1
test: assert `numan use list` takes no snapshot while switch does
devin-ai-integration[bot] Aug 2, 2026
aedf804
Wire destructive-op TTY guard and clear active-version marker on mana…
devin-ai-integration[bot] Aug 2, 2026
23c0e31
Apply 1 edit across 1 file
devin-ai-integration[bot] Aug 2, 2026
993acea
fix(mirror-dry-run): capture drift-check exit code under set -e
devin-ai-integration[bot] Aug 2, 2026
4f3f478
chore: remove one-off apply_install_from_archive_fix.py (already appl…
devin-ai-integration[bot] Aug 2, 2026
1331ecf
fix: allow injected confirm seam to bypass TTY guard in tests
devin-ai-integration[bot] Aug 2, 2026
8245978
Merge branch 'master' into feature/numan-use
tonythethompson Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ jobs:
components: rustfmt
- run: cargo fmt --all -- --check

roadmap-drift:
name: Roadmap drift
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Run roadmap-drift check against consolidated roadmap
run: python scripts/check-roadmap-drift.py
- name: Mirror to numan-plugins/numan-registry
run: |
# CI intentionally also runs against this PR's siblings when
# those repos pass CONSOLIDATED_ROADMAP via workflow_call; the
# numan job here is the canonical reference build.
echo "see https://github.com/tonythethompson/numan-plugins and https://github.com/tonythethompson/numan-registry for mirrors"

Comment on lines +60 to +73

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 New CI job's second step is a no-op echo, and the drift script only checks the repo it runs in

The Mirror to numan-plugins/numan-registry step only prints a URL; nothing is mirrored, so the job name overstates what runs. Also scripts/check-roadmap-drift.py audits docs/roadmap.md in the current repo, which does not exist in numanaudit_local then emits only a warning, so the job effectively validates the consolidated roadmap alone. Note also the dead heading-pop branch in the script: the first if HEAD_RE.match(line): heading_stack.append(line); continue at scripts/check-roadmap-drift.py:159-161 makes the depth-aware pop logic at :168-174 unreachable, so headings are never popped and any deferral heading permanently exempts the rest of the document.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

msrv:
name: MSRV (1.88)
runs-on: ubuntu-latest
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ src/
completions.rs — `numan completions <shell>`: bash/fish/zsh/powershell/nushell scripts (Phase 7.3)
setup.rs — `numan setup nu [VERSION]|remove|path|use <path>` + `setup loader`: Nushell bootstrap + nushell-loader install
try_cmd.rs — `numan try [--yes] [--no-activate]`: curated starter install + activate for current Nu
use_cmd.rs — `numan use <version>`: reserved stub for side-by-side Nu version management (post-1.0)
use_cmd.rs — `numan use <version>|latest|list`: activates a previously installed managed Nu version (no auto-download); writes the active-version marker after a PreMutation snapshot under the root mutation lock
nu_pin_offer.rs — Shared TTY offer to `setup nu <version>` + `init --refresh` on Nu mismatch
install/
download.rs — HTTP download with progress
Expand Down Expand Up @@ -141,6 +141,7 @@ tests/
- **Activation scope**: `PluginActivation` struct stores `(nu_executable_sha256, nu_version, plugin_registry_path)`; a plugin is "active" only when all three match the current `NuPaths` — bare `bool` would go stale after `numan init --refresh`
- **Journal**: `state/pending-activation.json` written as all-`prepared` before first registration; each entry advances to `registered` atomically before lockfile update; reconciled on next `activate` run if process is interrupted
- **Plugin deactivate journal**: `state/pending-plugin-deactivate.json` (`Prepared` → `Unregistered` → clear lockfile `activation`); reconciled on next `deactivate`; doctor warns `journal.plugin_deactivate_pending`
- **Active version marker**: `nu_state/active-version.json` (`{ "version": "X.Y.Z" }`, optionally `{ "version": "X.Y.Z", "binary_path": "/abs/path/to/nu" }` for off-tree selections). Sole authority for which `tools/nushell/<v>/` is selected. Written by `numan setup nu` and `numan use <version>|latest`. The optional `binary_path` records the resolved off-tree binary when `numan setup nu use <path>` swaps to a user-supplied Nu so subsequent `numan use list` and `find_nu_executable_with_root` can resolve the chosen version even when no on-tree install exists (the field uses `#[serde(default, skip_serializing_if = "Option::is_none")]` so the on-disk shape stays `{ "version": ... }` for on-tree selections and pre-existing markers still load).
- **Atomic writes**: all JSON state files (lockfile, journal, nu_state/paths.json) use `write_json_atomic` (tempfile in same dir + persist) — no partial-write corruption
- **Function signatures**: use `&Path` not `&PathBuf` in function parameters (clippy::ptr_arg is CI-enforced)

Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Side-by-side Nu version management** via `numan use <version>|latest|list`:
- Switches the active managed Nushell install without re-downloading (errors with `numan setup nu <version>` hint when the requested version isn't installed yet)
- Writes `<root>/nu_state/active-version.json` so `numan use list` and downstream activation can resolve the selected Nu
- Acquires the root mutation lock and creates a PreMutation snapshot before any state change, matching `install`/`update`/`activate`/`deactivate`
- **`numan setup nu` versioned layout**: binaries now install under `<root>/tools/nushell/<version>/nu` instead of the previous single-binary `<root>/tools/nushell/nu`. Re-installing one version no longer clobbers another. `numan doctor` reports markers whose on-tree binary is missing.
- Strict version-path validation (`<root>/tools/nushell/<version>/...`): rejects components containing `/`, `\\`, `..`, or anything `semver` won't parse, so `numan setup nu ../../path` cannot escape `$NUMAN_ROOT`.
- Shared confirmation utility (`src/util/confirm.rs`): consistent prompt/auto-confirm behavior across all commands

## [0.1.5] - 2026-07-29
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ would modify the previous commit).
secrets or large binaries.
- Don't commit files that look like secrets (.env, credentials.json, *.pem). If
the user explicitly asks, warn first.
- For commit messages, use a HEREDOC to preserve formatting. End the trailer with:
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- For commit messages, use a HEREDOC to preserve formatting. Add co-author
attribution only when the user explicitly requests the exact trailer.
- Don't push unless asked. Never force-push to main/master; warn if asked.
- For PRs, use `gh` via Bash. Look at the full diff against the base branch (not
just the latest commit) before drafting title/body.
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ Global flag: `--root <path>` — override the Numan root directory (all commands
| `numan setup nu remove` | Remove the managed Nushell install and fall back to PATH Nu |
| `numan setup nu path` | Use the Nushell already on PATH (removes managed install) |
| `numan setup nu use <path>` | Register a specific existing Nushell binary |
| `numan use <version>` | Switch the active managed Nu to a pinned version (no auto-install; errors with a hint to run `numan setup nu <version>` if missing) |
| `numan use latest` | Switch the active managed Nu to the latest installed version |
| `numan use list` | List installed managed Nu versions and mark the active one |
| `numan nupm status` | Summarize nupm home and import eligibility |
| `numan nupm inspect [--all] [path]` | Classify nupm packages at a path |
| `numan nupm import [--as owner/name] [path]` | One-way import into Numan |
Expand Down
81 changes: 81 additions & 0 deletions cross-repo-mirror/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Cross-repo mirror for `check-roadmap-drift`

This directory holds **drop-in artifacts** that complete the cross-repo
roadmap-drift guardrail started in PR 67 of `numan`. Each subdirectory
mirrors both the local-roadmap file and the GitHub Actions workflow
that runs `scripts/check-roadmap-drift.py` against the consolidated
roadmap hosted in `numan@master`.

## What ships

```
cross-repo-mirror/
├── README.md ← you are here
├── numan-plugins/
│ ├── docs/roadmap.md ← the local pointer
│ └── .github/workflows/roadmap-drift.yml ← the CI job snippet
├── numan-registry/
│ ├── docs/roadmap.md ← the local pointer
│ └── .github/workflows/roadmap-drift.yml ← the CI job snippet
└── snapshot-tests/
└── mirror_dry_run.sh ← local sanity check
```

## Why three repos, one source of truth

Numan's product spans three repos; trust is cross-cutting and drift is
silent. PR 67 shipped `numan use` while the consolidated roadmap still
called it post-1.0. The drift script lives in `numan/scripts/`, but a
script only one side knows about isn't a guardrail — both sibling repos
need to run it.

The CI workflow in each sibling repo `curl`s the consolidated roadmap
straight from `numan@master/docs/plans/consolidated-multi-repo-roadmap.md`
into a temp file at the start of the job, then runs the exact same
`scripts/check-roadmap-drift.py` against that fetched copy. There is
never a stale copy in a sibling repo: the source of truth is the
published URL.

## Installation

In each sibling repo:

```bash
mkdir -p scripts
curl -sSfL \
https://raw.githubusercontent.com/tonythethompson/numan/master/scripts/check-roadmap-drift.py \
-o scripts/check-roadmap-drift.py
chmod +x scripts/check-roadmap-drift.py

# Then drop the contents of the relevant subdirectory into the sibling repo:
# cross-repo-mirror/numan-plugins/docs/roadmap.md → <sibling>/docs/roadmap.md
# cross-repo-mirror/numan-plugins/.github/workflows/roadmap-drift.yml
# → <sibling>/.github/workflows/roadmap-drift.yml

mkdir -p docs .github/workflows
cp cross-repo-mirror/<sibling>/docs/roadmap.md docs/roadmap.md
cp cross-repo-mirror/<sibling>/.github/workflows/roadmap-drift.yml \
.github/workflows/roadmap-drift.yml

# Smoke-test locally before pushing:
python scripts/check-roadmap-drift.py
# expect: 0 errors, optionally a warning about the consolidated roadmap
# being fetched URL-side. The warning is informational only.
```

## Mirror contract

When `numan/scripts/check-roadmap-drift.py` evolves (new forbidden
phrases, new SHIPPED_MARKERS, stronger structural checks), the mirror
artifacts in this directory MUST stay in sync. The recommended
workflow:

1. Update `scripts/check-roadmap-drift.py` in `numan` and run its tests
(the negative-test proves it still bites).
2. Update each sibling's local CI workflow to mirror the change — most
changes are confined to the script itself; the workflow is just a
thin courier that `curl`s the consolidated roadmap and runs the script
with `CONSOLIDATED_ROADMAP=<path>`.
3. Bump the pinned `commit` SHA the workflow fetches (currently
`tonythethompson/numan@master`; the URL can be pinned to a tag for
reproducibility once a roadmap release cadence exists).
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Roadmap drift

# Mirror of numan/scripts/check-roadmap-drift.py runner. Fetches the
# canonical consolidated roadmap from numan@master so the sibling repo
# never holds a stale copy. Pinned actions-commit SHA matches
# numan/.github/workflows/ci.yml so future audit passes change in lockstep.

on:
pull_request:
branches: [master, main]
push:
branches: [master, main]

permissions:
contents: read

jobs:
roadmap-drift:
name: Roadmap drift against numan consolidated roadmap
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Fetch consolidated roadmap from numan@master
run: |
mkdir -p docs/plans
curl --fail-with-body --silent --show-error --location \
https://raw.githubusercontent.com/tonythethompson/numan/master/docs/plans/consolidated-multi-repo-roadmap.md \
-o docs/plans/consolidated-multi-repo-roadmap.md
# Sanity check: the file must not be empty AND must mention this
# repo by path so we know we didn't grab a placeholder document.
test -s docs/plans/consolidated-multi-repo-roadmap.md
grep -q "numan-plugins/docs/roadmap.md" docs/plans/consolidated-multi-repo-roadmap.md
env:
# Pin via tag once a roadmap release cadence exists; master is OK
# for the first cut because the consolidated doc is the canonical
# cross-repo authority referenced by all three repos.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Pull check-roadmap-drift.py from numan@master
run: |
mkdir -p scripts
curl --fail-with-body --silent --show-error --location \
https://raw.githubusercontent.com/tonythethompson/numan/master/scripts/check-roadmap-drift.py \
-o scripts/check-roadmap-drift.py
chmod +x scripts/check-roadmap-drift.py
- name: Run drift check
env:
CONSOLIDATED_ROADMAP: docs/plans/consolidated-multi-repo-roadmap.md
LOCAL_ROADMAP: docs/roadmap.md
run: python scripts/check-roadmap-drift.py
- name: Verify repo-local roadmap cross-link (defense in depth)
run: |
test -f docs/roadmap.md
grep -q "consolidated-multi-repo-roadmap" docs/roadmap.md
28 changes: 28 additions & 0 deletions cross-repo-mirror/numan-plugins/docs/roadmap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Repo-local roadmap for numan-plugins

This repo owns CI-built plugin binaries for upstreams without compliant
release assets. The roadmap that covers the entire three-repo plan —
catalog intake, signing, plugin backfills, client compat, lifecycle
evidence, and the active-plugin gate — lives in the consolidated
cross-repo plan:

[**`numan/docs/plans/consolidated-multi-repo-roadmap.md`**](https://github.com/tonythethompson/numan/blob/master/docs/plans/consolidated-multi-repo-roadmap.md)

The cross-repo drill is enforced by
[`scripts/check-roadmap-drift.py`](https://github.com/tonythethompson/numan/blob/master/scripts/check-roadmap-drift.py),
which CI runs at `.github/workflows/roadmap-drift.yml` and which fails
this PR if the local roadmap drifts from the consolidated truth.

## Repo-local detail

Use this page for **operational** detail that belongs only to
`numan-plugins`:

- Workflow manifests under `.github/workflows/` (`build-plugins.yml`,
`release.yml`, `windows-recheck.yml`).
- Per-upstream build matrix decisions (`docs/upstream-build-decisions.md`).
- Backlog triage notes (`docs/backlog.json` schema + review log).

Promote any cross-repo claim back into the consolidated roadmap via a
PR against `numan/docs/plans/consolidated-multi-repo-roadmap.md`
alongside the matching code change, so the three repos stay aligned.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Roadmap drift

# Mirror of numan/scripts/check-roadmap-drift.py runner. Fetches the
# canonical consolidated roadmap from numan@master so the sibling repo
# never holds a stale copy. Pinned actions-commit SHA matches
# numan/.github/workflows/ci.yml so future audit passes change in lockstep.

on:
pull_request:
branches: [main, master]
push:
branches: [main, master]

permissions:
contents: read

jobs:
roadmap-drift:
name: Roadmap drift against numan consolidated roadmap
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Fetch consolidated roadmap from numan@master
run: |
mkdir -p docs/plans
curl --fail-with-body --silent --show-error --location \
https://raw.githubusercontent.com/tonythethompson/numan/master/docs/plans/consolidated-multi-repo-roadmap.md \
-o docs/plans/consolidated-multi-repo-roadmap.md
test -s docs/plans/consolidated-multi-repo-roadmap.md
grep -q "numan-registry/docs/roadmap.md" docs/plans/consolidated-multi-repo-roadmap.md
- name: Pull check-roadmap-drift.py from numan@master
run: |
mkdir -p scripts
curl --fail-with-body --silent --show-error --location \
https://raw.githubusercontent.com/tonythethompson/numan/master/scripts/check-roadmap-drift.py \
-o scripts/check-roadmap-drift.py
chmod +x scripts/check-roadmap-drift.py
- name: Run drift check
env:
CONSOLIDATED_ROADMAP: docs/plans/consolidated-multi-repo-roadmap.md
LOCAL_ROADMAP: docs/roadmap.md
run: python scripts/check-roadmap-drift.py
- name: Verify repo-local roadmap cross-link (defense in depth)
run: |
test -f docs/roadmap.md
grep -q "consolidated-multi-repo-roadmap" docs/roadmap.md
27 changes: 27 additions & 0 deletions cross-repo-mirror/numan-registry/docs/roadmap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Repo-local roadmap for numan-registry

This repo owns the signed official catalog, intake evidence, staging,
and production signing. The roadmap that covers the entire three-repo
plan — catalog intake, signing, plugin backfills, client compat,
lifecycle evidence, and the active-plugin gate — lives in the
consolidated cross-repo plan:

[**`numan/docs/plans/consolidated-multi-repo-roadmap.md`**](https://github.com/tonythethompson/numan/blob/master/docs/plans/consolidated-multi-repo-roadmap.md)

The cross-repo drill is enforced by
[`scripts/check-roadmap-drift.py`](https://github.com/tonythethompson/numan/blob/master/scripts/check-roadmap-drift.py),
which CI runs at `.github/workflows/roadmap-drift.yml` and which fails
this PR if the local roadmap drifts from the consolidated truth.

## Repo-local detail

Use this page for **operational** detail that belongs only to
`numan-registry`:

- Stage gate evidence under `stage-evidence/` and `scripts/lifecycle-prove.py`.
- Intake state machine and candidate promotion rules.
- Signing key ceremony and protected-branch disclosure.

Promote any cross-repo claim back into the consolidated roadmap via a
PR against `numan/docs/plans/consolidated-multi-repo-roadmap.md`
alongside the matching code change, so the three repos stay aligned.
44 changes: 44 additions & 0 deletions cross-repo-mirror/snapshot-tests/mirror_dry_run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Local sanity check that each sibling-repo CI job will pass against the
# current state of numan/scripts/check-roadmap-drift.py and the
# consolidated roadmap. Mimics what each .github/workflows/roadmap-drift.yml
# step does, but entirely offline by using the local files.
#
# Usage: bash cross-repo-mirror/snapshot-tests/mirror_dry_run.sh
# Expected: exit 0 with "pass" line for both numan-plugins and numan-registry.

set -euo pipefail

ROOT=$(cd "$(dirname "$0")/../.." && pwd)
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT

for sibling in numan-plugins numan-registry; do
echo "==> mirror dry-run for $sibling"
# Map the sibling name to the corresponding repo-local roadmap content.
local_roadmap="$ROOT/cross-repo-mirror/$sibling/docs/roadmap.md"
test -f "$local_roadmap" \
|| { echo "FAIL: missing $local_roadmap"; exit 2; }

# Stub ci.sh here: stage the consolidated roadmap, the local roadmap, and
# the script into the temp dir the way the GitHub workflow will.
cp "$ROOT/docs/plans/consolidated-multi-repo-roadmap.md" \
"$TMP/consolidated.md"
cp "$local_roadmap" "$TMP/local.md"
cp "$ROOT/scripts/check-roadmap-drift.py" "$TMP/check.py"

# Capture the status explicitly: under `set -e` a bare invocation would
# abort the script before we could report the failing exit code.
if CONSOLIDATED_ROADMAP="$TMP/consolidated.md" \
LOCAL_ROADMAP="$TMP/local.md" \
python "$TMP/check.py"; then
echo " pass: drift check exits 0"
else
rc=$?
echo "FAIL: drift check exited $rc"
exit "$rc"
fi
echo
done

echo "ALL MIRRORS PASS"
Loading
Loading