Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
52ad910
refactor: UX improvements — reduce prompts and simplify doctor command
tonythethompson Aug 1, 2026
5dc3e08
fix: 2 findings — Remove stale DeactivateArgs yes fields; Remove stale A
qodo-code-review[bot] Aug 1, 2026
b95ab9f
fix: Remove obsolete activate yes hints
qodo-code-review[bot] Aug 1, 2026
a6c9fd8
fix: Update doctor documentation and hints
qodo-code-review[bot] Aug 1, 2026
3bc34fd
fix: 2 findings — Remove redundant prompting comments; Clarify concurren
qodo-code-review[bot] Aug 1, 2026
f8bea34
refactor: update command descriptions to reflect plugins+modules support
tonythethompson Aug 1, 2026
180eac8
fix: remove stale yes field from activate_test
tonythethompson Aug 1, 2026
f04c4e7
style: apply formatting
tonythethompson Aug 1, 2026
5c83980
fix: address PR review — remove stale --yes from test support, fix no…
tonythethompson Aug 1, 2026
59f4f92
Merge branch 'master' into refactor/ux-improvements
tonythethompson Aug 2, 2026
0f775bc
Merge branch 'master' into refactor/ux-improvements
tonythethompson Aug 2, 2026
15e6848
refactor: UX improvements — reduce prompts and simplify doctor command
tonythethompson Aug 1, 2026
09cf2a0
fix: 2 findings — Remove stale DeactivateArgs yes fields; Remove stale A
qodo-code-review[bot] Aug 1, 2026
e7cfa16
fix: Remove obsolete activate yes hints
qodo-code-review[bot] Aug 1, 2026
89f3b28
fix: Update doctor documentation and hints
qodo-code-review[bot] Aug 1, 2026
ae50dd1
fix: 2 findings — Remove redundant prompting comments; Clarify concurren
qodo-code-review[bot] Aug 1, 2026
f8716d0
refactor: update command descriptions to reflect plugins+modules support
tonythethompson Aug 1, 2026
a4b4930
fix: remove stale yes field from activate_test
tonythethompson Aug 1, 2026
8623a74
style: apply formatting
tonythethompson Aug 1, 2026
37e3884
fix: address PR review — remove stale --yes from test support, fix no…
tonythethompson Aug 1, 2026
c26f509
fix: apply CodeRabbit auto-fixes
coderabbitai[bot] Aug 2, 2026
e648e7f
fix: unbreak doctor tests after TTY confirm gating
cursoragent Aug 2, 2026
6af9a80
fix: address Copilot PR 68 review findings
cursoragent Aug 2, 2026
6d231f2
fix: keep doctor --json stdout as a single JSON object
cursoragent Aug 3, 2026
708b24e
Merge branch 'refactor/ux-improvements' of https://github.com/tonythe…
tonythethompson Aug 3, 2026
c5ef084
fix: import IsTerminal and rustfmt TryArgs
cursoragent Aug 3, 2026
2d81487
fix: address CodeRabbit doctor UX and test feedback
cursoragent Aug 3, 2026
32f4944
fix: harden doctor off-path skip test and snapshot refresh/repairs
cursoragent Aug 3, 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
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ src/
registry.rs — Registry management subcommands
activate.rs — Plugin + module activation (Phase 3 & 4); public entry: execute_with_candidate_runner
init.rs — `numan init [--refresh]`: Nu probe, paths cache, auto-configures official registry
doctor.rs — `numan doctor [--fix] [--yes]`: health checks + safe repairs (Phase 7.2; spec: docs/numan-doctor.md)
doctor.rs — `numan doctor [--scan]`: health checks + safe repairs by default (or report-only with `--scan`) (Phase 7.2; spec: docs/numan-doctor.md)
snapshot.rs — `numan snapshot list|inspect|delete|rollback` (Phase 5.3)
deactivate.rs — Plugin + module deactivation: journaled plugin unregister (`execute_with_unregistrar`); module full/partial (Phase 4 / Issue #22 PR2)
plugin_lifecycle.rs — Activate/deactivate-owned lifecycle boundary exposed to opt-in update orchestration (Issue #22 PR3)
Expand All @@ -64,7 +64,7 @@ src/
nupm.rs — `numan nupm status|inspect|import|diff`: nupm discovery + import + drift (Phase 6.1–6.3)
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
try_cmd.rs — `numan try [--no-activate]`: curated starter install + activate for current Nu
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/
Expand Down Expand Up @@ -109,7 +109,7 @@ tests/
fixtures/nupm/ — supported/rejected fixture corpus for parser/classifier tests
init_test.rs — `numan init` / `init --refresh` (vendor drift, managed-file revalidation)
completions_test.rs — shell completion script generation (Phase 7.3)
doctor_test.rs — `numan doctor` report-only, --fix auto tier, journal checks (Phase 7.2)
doctor_test.rs — `numan doctor` report-only (`--scan`), default auto/confirm tiers, journal checks (Phase 7.2)
nupm_compat_test.rs — Phase 6 integration tests (T13–T25, import/drift/manifest/activation/platform)
nupm_real_nu_test.rs — Phase 6.4 real-Nu #[ignore] acceptance tests (run with `cargo test -- --ignored`)
plugin_lifecycle_real_nu.rs — Issue #22 smoke marker (points at Stage 1 + active-update suite)
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ uuid = { version = "1", features = ["v7", "v4"] }
# Git (for source builds and registry sync)
git2 = "0.21"

# stdio redirect for doctor --json repair passes (Unix only)
[target.'cfg(unix)'.dependencies]
libc = "0.2"

[dev-dependencies]
tempfile = "3"
rand_core = { version = "0.6", features = ["getrandom"] }
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ See [docs/snapshots-and-rollback.md](docs/snapshots-and-rollback.md) for scope,
#### 6. Verify health

```bash
numan doctor # report-only diagnosis
numan doctor --fix --yes # apply safe automated repairs
numan doctor # diagnose and apply safe automated repairs
numan doctor --scan # report-only diagnosis
```

---
Expand Down Expand Up @@ -308,7 +308,7 @@ Global flag: `--root <path>` — override the Numan root directory (all commands
| Command | Description |
|---------|-------------|
| `numan init [--refresh]` | Probe Nu and cache paths for activation |
| `numan try [--yes] [--no-activate]` | Install and activate a curated starter package for your Nu + platform (prefers Nu 0.114 starters; suggests managed Nu pin or search if no compatible starter) |
| `numan try [--no-activate]` | Install and activate a curated starter package for your Nu + platform (prefers Nu 0.114 starters; suggests managed Nu pin or search if no compatible starter) |
| `numan search <query>` | Search registry by name, description, or tags |
| `numan info <owner/name>` | Show package metadata and available versions |
| `numan install <owner/name[@version]>` | Download, verify, extract, and lock |
Expand Down Expand Up @@ -336,23 +336,23 @@ Global flag: `--root <path>` — override the Numan root directory (all commands
| `numan nupm import --manifest file.toml` | Batch import from manifest |
| `numan nupm diff <owner/name>` | Compare imported payload vs nupm source |
| `numan completions <shell>` | Generate bash, fish, zsh, powershell, or nushell completions |
| `numan doctor [--fix] [--yes] [--json]` | Diagnose root health; optional safe repairs |
| `numan doctor [--scan] [--json]` | Diagnose root health and repair (use `--scan` for report-only) |

### Common flags (by command)

| Command | Flags |
|---------|-------|
| `install` | `--force` reinstall; `-v` / `--verbose` |
| `activate` | `--yes` skip prompt; `--verbose`; `--list` status only; `--check` integrity only |
| `deactivate` | `--yes` skip prompt; `--verbose` |
| `activate` | `--verbose`; `--list` status only; `--check` integrity only |
| `deactivate` | `--verbose` |
| `update` | `--check` report only; `-v` / `--verbose` |
| `remove` | `--force` remove despite active activation |
| `gc` | `--dry-run` preview only |
| `registry add` | `--key <base64-public-key>` (required for custom registries; official is auto-configured on `init`) |
| `nupm status` | `--nupm-home <path>` |
| `nupm inspect` | `--all` scan home; `--nupm-home <path>`; `--exit-on-ineligible` fail on ineligible |
| `nupm import` | `--as owner/name` (single import); `--manifest <file>` (batch); `--nupm-home <path>`; `--yes` skip consent |
| `doctor` | `--fix` apply safe repairs; `--yes` skip confirm tier; `--json` machine output; `--nupm-home <path>` |
| `doctor` | `--scan` report-only; `--json` machine output; `--nupm-home <path>` |
| `setup nu` | `--force` re-download; `--skip-path` don't update PATH; `--yes` skip prompt |

Run `numan <command> --help` for full flag documentation.
Expand Down
81 changes: 37 additions & 44 deletions docs/numan-doctor.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

## Purpose

`numan doctor` diagnoses the health of a Numan root and, with `--fix`, applies **safe automated repairs** — the same pattern as `brew doctor`, `npm doctor`, and similar tooling.
`numan doctor` diagnoses the health of a Numan root and applies **safe automated repairs** by default — the same pattern as `brew doctor`, `npm doctor`, and similar tooling.

Default mode is **report-only** (safe for CI and scripting). Repair mode delegates to existing commands (`init`, `activate`, `registry sync`) rather than inventing new mutation paths.
Use `--scan` for **report-only** mode (safe for CI and scripting). Repair mode delegates to existing commands (`init`, `activate`, `registry sync`) rather than inventing new mutation paths.

It answers: *“Is this Numan root consistent, safe to mutate, and aligned with the current Nu environment?”* and optionally *“Fix what you can.”*

Expand All @@ -22,20 +22,22 @@ It answers: *“Is this Numan root consistent, safe to mutate, and aligned with
## Invocation

```text
numan doctor [--fix] [--yes] [--json] [--nupm-home PATH]
numan doctor [--scan] [--json] [--nupm-home PATH]
```

| Flag | Behavior |
|------|----------|
| `--fix` | After reporting, apply automated repairs (see [Repair policy](#repair-policy)) |
| `--yes` | Skip confirmation prompts for **confirm**-tier repairs (non-TTY implies `--yes` for confirm tier only) |
| `--json` | Emit a single JSON object (schema versioned); no ANSI styling. With `--fix`, include `repairs` attempted/applied |
| `--scan` | Report findings without applying repairs (dry-run mode) |
| `--json` | Emit a single JSON object (schema versioned); no ANSI styling. Includes a `repairs` array when repairs ran; omitted under `--scan` |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
| `--nupm-home PATH` | Override nupm home for the optional coexistence section (same resolution order as `numan nupm status`) |

Global `--root` applies as for all commands.

**Default (no flags):** diagnose and print findings + manual fix hints.
**`--fix`:** diagnose, then repair what is allowed without user-supplied data.
**Default (no flags):** diagnose and apply available repairs, then print findings.
Confirm-tier repairs run only when stdin is a TTY (allow-gate, not a prompt);
non-interactive sessions skip them as `not_confirmed`. Nested `setup nu`
repairs may still prompt when allowed.
**`--scan`:** diagnose and print findings without mutating state.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Exit codes

Expand All @@ -53,7 +55,7 @@ Each finding has:
- `severity` — `ok` \| `info` \| `warn` \| `error`
- `message` — human-readable summary
- `fix` — optional suggested command for manual issues (e.g. `numan init` or `numan registry add …`)
- `repair` — `none` \| `auto` \| `confirm` \| `manual` (whether `--fix` can act; see below)
- `repair` — `none` \| `auto` \| `confirm` \| `manual` (whether default repair mode can act; see below)

**Rules:**

Expand All @@ -64,7 +66,7 @@ Each finding has:

## Repair policy

When `--fix` is set, doctor acquires `acquire_mutation_lock(root)` for its own
By default, doctor acquires `acquire_mutation_lock(root)` for its own
filesystem repairs (layout dirs, `registry.none` config writes), then **releases**
that guard before nested `init` / `setup` / `registry sync` / `activate` /
`deactivate` commands. Those nested commands acquire the mutation lock themselves.
Expand All @@ -77,12 +79,12 @@ Repair steps run in this **order** (each step re-validates only what it changed)
| **auto** | Never | `layout.*` (missing dirs), `nu_paths.missing` | `create_dir_all` for layout; `numan init` |
| **auto** | Never | `registry.index_missing` | `numan registry sync` |
| **auto** | Never | `registry.none` (production trust root only) | Add official registry via same path as `numan init` |
| **confirm** | Unless `--yes` / non-TTY | `nu.binary.missing_on_path` | `numan setup nu` (downloads managed Nushell) |
| **confirm** | Unless `--yes` / non-TTY | `nu.binary.found_off_path` | `numan setup nu use <path>` (adds existing install to PATH) |
| **confirm** | Unless `--yes` / non-TTY | `nu_paths.drift`, `nu_paths.vendor_drift` | `numan init --refresh` |
| **confirm** | Unless `--yes` / non-TTY | `journal.plugin_pending`, `journal.autoload_pending`, `journal.plugin_stale`, `journal.autoload_stale`, `activation.plugin_stale`, `activation.module_stale`, `autoload.projection`, `autoload.managed_missing` | `numan activate` (empty package list — reconciles journals and re-activates stale entries; same entry point as normal activate recovery) |
| **confirm** | Unless `--yes` / non-TTY | `journal.plugin_deactivate_pending` | `numan deactivate <journal package ids>` (reconciles pending-plugin-deactivate journal only; not a full-root deactivate) |
| **confirm** | Unless `--yes` / non-TTY | `journal.plugin_deactivate_stale` | `numan init --refresh` then `numan deactivate` |
| **confirm** | TTY required (non-TTY → `not_confirmed`) | `nu.binary.missing_on_path` | `numan setup nu` (downloads managed Nushell) |
| **confirm** | TTY required (non-TTY → `not_confirmed`) | `nu.binary.found_off_path` | `numan setup nu use <path>` (adds existing install to PATH) |
| **confirm** | TTY required (non-TTY → `not_confirmed`) | `nu_paths.drift`, `nu_paths.vendor_drift` | `numan init --refresh` |
| **confirm** | TTY required (non-TTY → `not_confirmed`) | `journal.plugin_pending`, `journal.autoload_pending`, `journal.plugin_stale`, `journal.autoload_stale`, `activation.plugin_stale`, `activation.module_stale`, `autoload.projection`, `autoload.managed_missing` | `numan activate` (empty package list — reconciles journals and re-activates stale entries; same entry point as normal activate recovery) |
| **confirm** | TTY required (non-TTY → `not_confirmed`) | `journal.plugin_deactivate_pending` | `numan deactivate <journal package ids>` (reconciles pending-plugin-deactivate journal only; not a full-root deactivate) |
| **confirm** | TTY required (non-TTY → `not_confirmed`) | `journal.plugin_deactivate_stale` | `numan init --refresh` then `numan deactivate` |
| **manual** | Never auto | `autoload.managed_foreign`, `payload.missing`, `journal.lifecycle_pending`, `journal.lifecycle_stale`, `registry.none` (placeholder trust root), `nu_paths.vendor_missing`, `nupm.*` | Print fix hint only |
| **none** | Never | `activation.plugin_mutation_gated` (`info`) | Informational only; see [docs/active-plugin-gate.md](active-plugin-gate.md) |

Expand All @@ -96,7 +98,7 @@ Repair steps run in this **order** (each step re-validates only what it changed)
6. Mutation lock ownership is **staged**: doctor's lock covers only its direct edits;
nested mutators reacquire after doctor drops the guard (see above).

**Journal note:** Default mode still *reports* journals without acting. `--fix` may reconcile plugin/autoload journals via `activate` recovery, and plugin-deactivate journals via `deactivate` recovery scoped to journal package IDs — not by editing journal files directly.
**Journal note:** Repair mode reconciles plugin/autoload journals via `activate` recovery, and plugin-deactivate journals via `deactivate` recovery scoped to journal package IDs — not by editing journal files directly.

## Check catalog

Expand All @@ -116,8 +118,8 @@ Checks run in order below. Implementation should call existing validators (`NuPa
|----|----------|-----------|
| `nu.binary.missing_on_path` | `error` | Nu not on PATH and not under `$NUMAN_ROOT/tools/nushell/` → fix: `numan setup nu` |
| `nu.binary.found_off_path` | `warn` | Nu exists in a known install root (e.g. `~/.cargo/bin`, `%LOCALAPPDATA%\Programs\nushell`) but not on PATH → fix: `numan setup nu use <path>` |
| `nu.path.version` | `info` | PATH-only Nu version (`PATH Nu: 0.114.1`), `PATH Nu: not found`, or `PATH Nu: found at '<path>' but version probe failed (<error>)` when the binary exists but `--version` fails. Does not treat managed Nu as PATH. Report-only (no `--fix`). |
| `nu.managed.version` | `info` | Managed binary under `$NUMAN_ROOT/tools/nushell/` with version, `Managed Nu: not installed`, or `Managed Nu: present at '<path>' but version probe failed (<error>)` when the binary exists but `--version` fails. Report-only (no `--fix`). |
| `nu.path.version` | `info` | PATH-only Nu version (`PATH Nu: 0.114.1`), `PATH Nu: not found`, or `PATH Nu: found at '<path>' but version probe failed (<error>)` when the binary exists but `--version` fails. Does not treat managed Nu as PATH. Report-only (no repair). |
| `nu.managed.version` | `info` | Managed binary under `$NUMAN_ROOT/tools/nushell/` with version, `Managed Nu: not installed`, or `Managed Nu: present at '<path>' but version probe failed (<error>)` when the binary exists but `--version` fails. Report-only (no repair). |
| `nu_paths.missing` | `error` | `paths.json` absent → fix: `numan init` |
| `nu_paths.drift` | `error` | `NuPaths::validate_drift()` fails → fix: `numan init --refresh` |
| `nu_paths.vendor_drift` | `error` | `validate_vendor_drift()` fails when `data_dir` cached → fix: `numan init --refresh` |
Expand Down Expand Up @@ -163,9 +165,9 @@ No re-hash or revision recompute in v1 (too expensive for doctor).

| ID | Severity | Condition |
|----|----------|-----------|
| `registry.none` | `warn` | `config.toml` has no registries → fix: `numan init` before first init; `numan doctor --fix` after init (production trust root); `numan registry add …` for custom/placeholder builds |
| `registry.none` | `warn` | `config.toml` has no registries → fix: `numan init` before first init; `numan doctor` after init (production trust root auto-repair); `numan registry add …` for custom/placeholder builds |
| `registry.index_missing` | `info` | Enabled registry has no cached index under `registries/` → fix: `numan registry sync` |
| `registry.trust_root` | `info` | Enabled `official` registry: reports built-in key id (e.g. `official-2026-07-01`). Placeholder builds note that the key is not production. Report-only (no `--fix`). |
| `registry.trust_root` | `info` | Enabled `official` registry: reports built-in key id (e.g. `official-2026-07-01`). Placeholder builds note that the key is not production. Report-only (no repair). |

### 7. nupm coexistence (optional section)

Expand Down Expand Up @@ -203,16 +205,8 @@ nupm coexistence

Summary: 1 error, 1 warning

Repairs (--fix only):
✓ Created missing state/ directory
✓ Ran registry sync
→ numan init --refresh required (skipped; re-run with --yes)
```

With `--fix` and repairs applied:

```text
Repairs: 2 applied, 1 skipped (use --yes to apply confirm-tier fixes)
Repairs: 2 applied, 1 skipped
Confirm-tier repairs skipped: stdin is not a TTY. Re-run `numan doctor` interactively, or use `--scan` for report-only.
```

Use `console` styling consistent with `activate --check`.
Expand All @@ -238,9 +232,6 @@ Use `console` styling consistent with `activate --check`.
{ "id": "nu_paths.drift", "status": "skipped", "reason": "not_confirmed" }
]
}
```

`repairs` is present only when `--fix` was passed.

## Architecture

Expand All @@ -261,27 +252,29 @@ pub fn execute_with_options(args: &DoctorArgs, root: &Path, options: DoctorOptio

| Command | Role |
|---------|------|
| `numan init` / `init --refresh` | **Repair** Nu path drift (`--fix` delegates here) |
| `numan setup nu` | **Repair** missing Nushell (`nu.binary.missing_on_path`; `--fix` downloads managed binary) |
| `numan setup nu use <path>` | **Repair** off-PATH Nushell (`nu.binary.found_off_path`; `--fix` adds parent dir to user PATH) |
| `numan activate` | **Repair** activation + journal reconciliation (`--fix` delegates here) |
| `numan registry sync` | **Repair** missing index cache (`--fix` auto tier) |
| `numan init` / `init --refresh` | **Repair** Nu path drift (default doctor delegates here) |
| `numan setup nu` | **Repair** missing Nushell (`nu.binary.missing_on_path`; confirm-tier downloads managed binary) |
| `numan setup nu use <path>` | **Repair** off-PATH Nushell (`nu.binary.found_off_path`; confirm-tier adds parent dir to user PATH) |
| `numan activate` | **Repair** activation + journal reconciliation (default doctor delegates here) |
| `numan registry sync` | **Repair** missing index cache (auto tier) |
| `numan activate --check` | Deep **module** check only; no repair |
| `numan nupm status` | nupm-only summary; doctor embeds optional subset |
| `numan update` / `remove` / `gc` | Block on stale lifecycle journal; doctor reports, does not fix lifecycle |

## Definition of done

- [x] `numan doctor`, `numan doctor --fix`, and `numan doctor --json` implemented per check catalog
- [x] `numan doctor`, `numan doctor --scan`, and `numan doctor --json` implemented per check catalog
- [x] `scan_on_doctor` respected
- [x] Default mode: no state mutation (test: hashes unchanged)
- [x] `--fix` mode: only repair tiers in policy; uses mutation lock; delegates to init/activate/sync
- [x] `--scan` mode: no state mutation (test: hashes unchanged)
- [x] Default mode: applies repair tiers per policy; uses mutation lock; delegates to init/activate/sync
- [x] Documented in README command table and `AGENTS.md`
- [x] Integration tests: report-only, `--fix` auto tier, `--fix` confirm tier with `--yes`, manual tier untouched
- [x] Integration tests: `--scan` report-only, default auto tier, default confirm tier with TTY, manual tier untouched
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Changelog

| Date | Change |
|------|--------|
| 2026-06-30 | Initial spec (Phase 7.2) |
| 2026-06-30 | Add `--fix` / `--yes` repair policy (auto / confirm / manual tiers) |
| 2026-06-30 | Add repair policy (auto / confirm / manual tiers) |
| 2026-08-02 | Update to reflect `--scan` flag (repairs by default; `--scan` for report-only) |
| 2026-08-03 | Document confirm-tier TTY allow-gate and plain-text `not_confirmed` remediation |
Loading
Loading