Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 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
4 changes: 2 additions & 2 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
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
55 changes: 23 additions & 32 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,19 @@ 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.
**`--scan`:** diagnose and print findings without mutating state.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Exit codes

Expand Down Expand Up @@ -64,7 +63,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 +76,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** | Always (non-TTY skips) | `nu.binary.missing_on_path` | `numan setup nu` (downloads managed Nushell) |
| **confirm** | Always (non-TTY skips) | `nu.binary.found_off_path` | `numan setup nu use <path>` (adds existing install to PATH) |
| **confirm** | Always (non-TTY skips) | `nu_paths.drift`, `nu_paths.vendor_drift` | `numan init --refresh` |
| **confirm** | Always (non-TTY skips) | `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** | Always (non-TTY skips) | `journal.plugin_deactivate_pending` | `numan deactivate <journal package ids>` (reconciles pending-plugin-deactivate journal only; not a full-root deactivate) |
| **confirm** | Always (non-TTY skips) | `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 +95,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 Down Expand Up @@ -203,16 +202,10 @@ nupm coexistence

Summary: 1 error, 1 warning

Repairs (--fix only):
Repairs:
✓ 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)
→ numan init --refresh required (skipped; user confirmation needed)
```

Use `console` styling consistent with `activate --check`.
Expand All @@ -238,9 +231,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 Down Expand Up @@ -272,16 +262,17 @@ pub fn execute_with_options(args: &DoctorArgs, root: &Path, options: DoctorOptio

## 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) |
6 changes: 3 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ pub enum Commands {
Remove(cmd::remove::RemoveArgs),
/// Garbage-collect orphaned package directories
Gc(cmd::gc::GcArgs),
/// Activate installed plugins with Nu
/// Activate installed plugins and modules with Nu
Activate(cmd::activate::ActivateArgs),
/// Deactivate active modules
/// Deactivate active plugins and modules
Deactivate(cmd::deactivate::DeactivateArgs),
/// List all installed packages
List,
Expand All @@ -54,7 +54,7 @@ pub enum Commands {
Nupm(cmd::nupm::NupmArgs),
/// Generate shell completion scripts
Completions(cmd::completions::CompletionsArgs),
/// Diagnose Numan root health and optionally apply safe repairs
/// Diagnose Numan root health and apply safe repairs (use `--scan` for report-only)
Doctor(cmd::doctor::DoctorArgs),
/// Install optional Nushell integration helpers
#[command(subcommand)]
Expand Down
18 changes: 6 additions & 12 deletions src/cmd/activate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,12 @@ pub struct ActivateArgs {
/// Package IDs (owner/name) to activate. Omit to activate all installed inactive packages.
pub packages: Vec<String>,

/// Skip confirmation prompts
#[arg(long)]
pub yes: bool,
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Comment thread
qodo-code-review[bot] marked this conversation as resolved.

/// Show detailed output
#[arg(long)]
pub verbose: bool,

/// List all installed packages and their activation status (read-only)
#[arg(long, conflicts_with_all = ["yes", "packages"])]
#[arg(long, conflicts_with = "packages")]
Comment thread
tonythethompson marked this conversation as resolved.
pub list: bool,

/// Check activation integrity for packages (read-only, no mutation)
Expand Down Expand Up @@ -154,16 +150,14 @@ fn execute_with_registrar_and_runner(
};
drop(planning_lock);

// 8. Consent table + confirmation
// 8. Consent table (informational only; no prompt)
print_grouped_consent_table(
&plugin_targets,
&module_targets,
managed_file_path.as_deref(),
&nu_paths.plugin_registry_path,
);

crate::util::confirm::confirm_or_bail("Proceed?", args.yes, "Activation cancelled.")?;

// 9. Reacquire the root mutation lock after consent.
let _lock = acquire_mutation_lock(root)?;

Expand Down Expand Up @@ -1421,7 +1415,7 @@ mod tests {

let args = ActivateArgs {
packages: vec!["owner/myscript".to_string()],
yes: true,

verbose: false,
list: false,
check: false,
Expand Down Expand Up @@ -1501,7 +1495,7 @@ mod tests {

let args = ActivateArgs {
packages: vec!["owner/mycomp".to_string()],
yes: true,

verbose: false,
list: false,
check: false,
Expand Down Expand Up @@ -1587,7 +1581,7 @@ mod tests {

let args = ActivateArgs {
packages: vec!["owner/mymod".to_string()],
yes: true,

verbose: false,
list: false,
check: false,
Expand Down Expand Up @@ -1669,7 +1663,7 @@ mod tests {

let args = ActivateArgs {
packages: vec!["owner/depmod".to_string()],
yes: true,

verbose: false,
list: false,
check: false,
Expand Down
Loading
Loading