Skip to content

Add cross-channel install guard for cargo, winget, and Homebrew - #93

Closed
tonythethompson wants to merge 5 commits into
masterfrom
feature/install-channel-guard
Closed

Add cross-channel install guard for cargo, winget, and Homebrew#93
tonythethompson wants to merge 5 commits into
masterfrom
feature/install-channel-guard

Conversation

@tonythethompson

@tonythethompson tonythethompson commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add install-guard crate to detect numan binaries installed via cargo, winget, Homebrew, or release archives
  • Run guard automatically during cargo install (build.rs): prompt to uninstall other channels first; declining cancels the install
  • Add wrapper scripts for winget (scripts/install-winget.ps1) and Homebrew (scripts/install-homebrew.sh)
  • Add numan doctor warning install.multiple_channels when mixed install channels are detected

Test plan

  • cargo test -p numan-install-guard
  • cargo test cmd::doctor
  • cargo clippy -- -D warnings
  • CI green on Ubuntu, Windows, macOS

Made with Cursor

Review in cubic

Prompt to uninstall an existing numan from another package manager before installing via a different channel; declining cancels the install. Cargo install runs the guard automatically via build.rs; winget and Homebrew use wrapper scripts. Doctor warns when multiple channels are detected.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@tonythethompson, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 59598149-9845-4942-85f6-ec0541e4de48

📥 Commits

Reviewing files that changed from the base of the PR and between a620eaa and 5c8d2db.

📒 Files selected for processing (11)
  • AGENTS.md
  • Cargo.toml
  • build.rs
  • docs/numan-doctor.md
  • scripts/install-cargo.ps1
  • scripts/install-homebrew.sh
  • scripts/install-winget.ps1
  • src/bin/install_guard.rs
  • src/cmd/doctor.rs
  • src/util/install_channel.rs
  • src/util/mod.rs
📝 Walkthrough

Walkthrough

Adds shared installation-channel detection, interactive conflict guards for Cargo, Winget, and Homebrew, installer wrappers, Cargo build integration, multiple-channel doctor diagnostics, and updated installation documentation.

Changes

Installation channel management

Layer / File(s) Summary
Installation discovery and classification
install-guard/Cargo.toml, install-guard/src/lib.rs
Defines installation channels, discovers binaries, filters conflicts, and tests classification behavior.
Guard execution and command dispatch
install-guard/src/lib.rs, install-guard/src/main.rs
Adds guarded installation flows, interactive uninstall prompts, uninstall commands, and channel dispatch.
Cargo integration and installer wrappers
Cargo.toml, build.rs, scripts/install-cargo.ps1, scripts/install-winget.ps1, scripts/install-homebrew.sh, AGENTS.md
Wires the guard into Cargo and adds installer wrappers for Cargo, Winget, and Homebrew.
Multiple-channel doctor warning
src/cmd/doctor.rs, docs/numan-doctor.md
Adds doctor detection and warnings for binaries installed through multiple channels.
Installation and packaging guidance
README.md, docs/PACKAGING.md, packaging/winget/README.md
Documents installation commands, conflict handling, and diagnostic output.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.48% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary cross-channel installation guard for Cargo, WinGet, and Homebrew.
Description check ✅ Passed The description directly explains the install guard, wrapper scripts, doctor warning, and test plan covered by the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Pipeline Stage Enum Ordering ✅ Passed SessionWorkflowStage enum does not exist in the codebase (0 matches found). This PR adds install-guard functionality and does not modify workflow stage enums. The check is not applicable.
Gpu/Cpu Runtime Boundary ✅ Passed The commit changes only Cargo.toml and install-guard/Cargo.toml; no inference/ path or CPU/GPU requirements file is modified, so the runtime-boundary checks do not apply.
Managed Host Restart Safety ✅ Passed The PR changes install-guard, packaging, scripts, and doctor files; none of the four managed-host components or restart/lease symbols are present or modified.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/install-channel-guard
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feature/install-channel-guard

Warning

Review ran into problems

🔥 Problems

Linked repositories: Public OSS repositories can only analyze public repositories installed in this organization. Analyzed tonythethompson/QuickShell, tonythethompson/numan, tonythethompson/dependency-chain-substrate, skipped Trackdubllc/Trackdub.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Add install-channel guard across cargo, winget, and Homebrew

✨ Enhancement 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Detect existing numan installs across cargo, winget, Homebrew, and release archives.
• Gate cross-channel installs behind an interactive uninstall prompt; declining aborts install.
• Add numan doctor warning when multiple install channels are detected.
Diagram

graph TD
  A["cargo install (build.rs)"] --> D["install-guard crate"] --> E[("PATH + known dirs")] --> F{"Multiple channels?"} --> G["Uninstall via manager"]
  B["Wrapper scripts"] --> D
  C["numan doctor"] --> D
  F -- "decline / non-tty" --> H["Abort install"]
  G -- "uninstall failed" --> H

  subgraph Legend
    direction LR
    _p["Process"] ~~~ _fs[("Filesystem scan")] ~~~ _d{"Decision"}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Warn-only (no uninstall, no build.rs gating)
  • ➕ Avoids running package-manager commands during build/install phases
  • ➕ Lower risk of surprising side effects for users and CI systems
  • ➖ Doesn't prevent PATH ambiguity; users may keep broken mixed installs
  • ➖ Relies on user follow-through, which is error-prone when switching channels
2. Channel marker file instead of path heuristics
  • ➕ More deterministic identification of install source (explicit metadata)
  • ➕ Can reduce false positives from path substring matching
  • ➖ Requires every packaging channel to write and maintain the marker consistently
  • ➖ Harder to retrofit for existing installs; still needs fallback detection
3. Integrate guard solely into main `numan` crate (no separate crate/bin)
  • ➕ Fewer crates/binaries; simpler workspace structure
  • ➕ All logic lives with the main CLI codebase
  • ➖ Harder to reuse from build.rs and external installer scripts cleanly
  • ➖ Makes installer-time behavior coupled to the main binary build graph

Recommendation: The PR’s approach (a small shared crate used by build.rs, wrapper scripts, and numan doctor) is the best tradeoff: it centralizes channel detection and keeps installer-time behavior consistent across platforms. The main thing to validate in review is the safety/UX boundary: refusing non-interactive installs is intentional, and the NUMAN_SKIP_INSTALL_GUARD=1 escape hatch provides an automation backdoor.

Files changed (15) +655 / -8

Enhancement (5) +525 / -0
build.rsRun install-channel guard during cargo install +7/-0

Run install-channel guard during cargo install

• Adds a build script that detects 'cargo install' via 'CARGO_INSTALL_ROOT' and aborts the build when the guard fails (e.g., user declines uninstall).

build.rs

Cargo.tomlIntroduce numan-install-guard crate manifest +11/-0

Introduce numan-install-guard crate manifest

• Creates a non-published crate with a small binary entrypoint for running channel-specific guards.

install-guard/Cargo.toml

lib.rsImplement cross-channel detection and uninstall prompting +434/-0

Implement cross-channel detection and uninstall prompting

• Implements install channel classification heuristics, discovery across PATH and known locations (cargo, winget, Homebrew, release archives), and an interactive guard that can uninstall conflicting channels. Includes non-interactive refusal and environment-variable/CI bypass behavior plus basic unit tests.

install-guard/src/lib.rs

main.rsAdd CLI entrypoint for running the guard per channel +18/-0

Add CLI entrypoint for running the guard per channel

• Adds a small CLI that dispatches to cargo/winget/homebrew guard modes based on the first argument.

install-guard/src/main.rs

doctor.rsWarn in doctor when multiple install channels are present +55/-0

Warn in doctor when multiple install channels are present

• Integrates install discovery into 'numan doctor' and emits 'install.multiple_channels' with detected paths, current channel, and uninstall hints for other channels.

src/cmd/doctor.rs

Documentation (5) +30 / -5
AGENTS.mdDocument install-guard and build.rs in project structure +2/-0

Document install-guard and build.rs in project structure

• Updates the project structure section to list the new 'build.rs' install guard hook and the 'install-guard/' crate directory.

AGENTS.md

README.mdDocument guarded installs for Homebrew, winget, and cargo +4/-2

Document guarded installs for Homebrew, winget, and cargo

• Extends installation docs to recommend using wrapper scripts when switching channels and notes that 'cargo install' runs the guard automatically.

README.md

PACKAGING.mdAdd install-channel guard documentation and bypass option +15/-3

Add install-channel guard documentation and bypass option

• Documents guard behavior per install channel, including wrapper entrypoints and the 'NUMAN_SKIP_INSTALL_GUARD=1' bypass for CI/automation.

docs/PACKAGING.md

numan-doctor.mdAdd doctor finding for multi-channel installs +1/-0

Add doctor finding for multi-channel installs

• Adds 'install.multiple_channels' as a documented doctor warning with guidance to uninstall other channels.

docs/numan-doctor.md

README.mdRecommend winget wrapper script when switching channels +8/-0

Recommend winget wrapper script when switching channels

• Documents that plain 'winget install' does not run the guard and provides the wrapper command to prompt for uninstalling other channels first.

packaging/winget/README.md

Other (5) +100 / -3
Cargo.lockAdd lockfile entry for numan-install-guard +5/-0

Add lockfile entry for numan-install-guard

• Records the new 'numan-install-guard' package in the dependency graph.

Cargo.lock

Cargo.tomlWire install-guard as build-dependency and runtime dependency +8/-3

Wire install-guard as build-dependency and runtime dependency

• Adds 'numan-install-guard' as a local path dependency for both build-time and runtime use, and reintroduces the '[[bin]]' definition for 'numan'. Also excludes 'install-guard/**' from packaging.

Cargo.toml

install-cargo.ps1Add cargo wrapper script for explicit pre-checks +33/-0

Add cargo wrapper script for explicit pre-checks

• Adds a PowerShell wrapper that runs the guard from the repo before executing 'cargo install --path .', useful for local source installs.

scripts/install-cargo.ps1

install-homebrew.shAdd Homebrew wrapper script to run guard before brew install +16/-0

Add Homebrew wrapper script to run guard before brew install

• Adds a bash script that runs the guard (brew mode) from the repo and then executes 'brew install' for the tap formula.

scripts/install-homebrew.sh

install-winget.ps1Add winget wrapper script to run guard before winget install +38/-0

Add winget wrapper script to run guard before winget install

• Adds a PowerShell script that runs the guard (winget mode) and only proceeds to 'winget install' if conflicts are resolved.

scripts/install-winget.ps1

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've found 2 issues, and left some high level feedback:

  • The conflicting_installs_excludes_paths test doesn’t exercise conflicting_installs at all and instead reimplements its filtering logic inline; consider rewriting it to call conflicting_installs with a controlled discover_installations (e.g., via dependency injection or a small refactor) so regressions in the real function are caught.
  • The directory walks in known_install_candidates (especially under WinGet/Packages and Homebrew Cellar) now run both for numan doctor and during install; if these trees are large they could add noticeable latency, so you may want to constrain the search more tightly (e.g., narrower depth/paths or a fast path for PATH hits) or cache results across a single process run.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `conflicting_installs_excludes_paths` test doesn’t exercise `conflicting_installs` at all and instead reimplements its filtering logic inline; consider rewriting it to call `conflicting_installs` with a controlled `discover_installations` (e.g., via dependency injection or a small refactor) so regressions in the real function are caught.
- The directory walks in `known_install_candidates` (especially under `WinGet/Packages` and Homebrew Cellar) now run both for `numan doctor` and during install; if these trees are large they could add noticeable latency, so you may want to constrain the search more tightly (e.g., narrower depth/paths or a fast path for PATH hits) or cache results across a single process run.

## Individual Comments

### Comment 1
<location path="src/cmd/doctor.rs" line_range="327-336" />
<code_context>
+fn check_install_channels(findings: &mut Vec<Finding>) {
</code_context>
<issue_to_address>
**suggestion:** Avoid duplicate uninstall hints when multiple installs share the same channel.

`fix` currently aggregates `uninstall_hint()` for each conflicting install, so multiple installs from the same channel (e.g., two cargo builds) produce repeated commands. Please deduplicate uninstall commands per `InstallChannel` before joining, either by collecting hints into a `HashSet` or by deduping channels first and then mapping to hints.

Suggested implementation:

```rust
fn check_install_channels(findings: &mut Vec<Finding>) {
    let installs = discover_installations();
    if installs.len() <= 1 {
        return;
    }

    // Collect all distinct install channels
    let channels: std::collections::HashSet<InstallChannel> =
        installs.iter().map(|install| install.channel).collect();
    if channels.len() <= 1 {
        return;
    }

    // Deduplicate uninstall hints per InstallChannel by:
    // 1. Iterating over each unique channel
    // 2. Picking one representative install for that channel
    // 3. Collecting its uninstall_hint() into a HashSet<String>
    //
    // The resulting `_uninstall_hints` set can be used when constructing
    // the Finding's fix to avoid duplicated uninstall commands.
    let _uninstall_hints: std::collections::HashSet<String> = channels
        .into_iter()
        .filter_map(|channel| {
            installs
                .iter()
                .find(|install| install.channel == channel)
                .map(|install| install.uninstall_hint())
        })
        .collect();

```

To fully apply the deduplicated uninstall hints in the `fix`:

1. Replace any existing logic that aggregates uninstall commands like:
   `let fixes = installs.iter().map(|i| i.uninstall_hint()).collect::<Vec<_>>().join("\n");`
   with code that uses the `_uninstall_hints` set, e.g. by:
   - Converting `_uninstall_hints` into a `Vec<String>`, sorting if desired for determinism, and then joining with `"\n"`.
2. Ensure that the `Finding` created inside `check_install_channels` (or any helper it calls) uses this joined string of deduplicated hints as the fix text.
3. If `InstallChannel` does not yet implement `Eq + Hash + Copy/Clone`, derive or implement these traits so it can be used as a key in `HashSet<InstallChannel>`.
</issue_to_address>

### Comment 2
<location path="docs/numan-doctor.md" line_range="132" />
<code_context>
+| `install.multiple_channels` | `warn` | More than one `numan` binary from different install channels (cargo, winget, homebrew, release archive) → fix: uninstall the other channel(s) before reinstalling |
</code_context>
<issue_to_address>
**nitpick (typo):** Capitalize "Homebrew" to match its usage elsewhere and maintain consistency in tool names.

Update "homebrew" here to "Homebrew" so it matches the capitalization used elsewhere in the docs.

```suggestion
| `install.multiple_channels` | `warn` | More than one `numan` binary from different install channels (cargo, winget, Homebrew, release archive) → fix: uninstall the other channel(s) before reinstalling |
```
</issue_to_address>

Fix all in Cursor


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/cmd/doctor.rs
Comment on lines +327 to +336
fn check_install_channels(findings: &mut Vec<Finding>) {
let installs = discover_installations();
if installs.len() <= 1 {
return;
}

let channels: std::collections::HashSet<InstallChannel> =
installs.iter().map(|install| install.channel).collect();
if channels.len() <= 1 {
return;

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.

suggestion: Avoid duplicate uninstall hints when multiple installs share the same channel.

fix currently aggregates uninstall_hint() for each conflicting install, so multiple installs from the same channel (e.g., two cargo builds) produce repeated commands. Please deduplicate uninstall commands per InstallChannel before joining, either by collecting hints into a HashSet or by deduping channels first and then mapping to hints.

Suggested implementation:

fn check_install_channels(findings: &mut Vec<Finding>) {
    let installs = discover_installations();
    if installs.len() <= 1 {
        return;
    }

    // Collect all distinct install channels
    let channels: std::collections::HashSet<InstallChannel> =
        installs.iter().map(|install| install.channel).collect();
    if channels.len() <= 1 {
        return;
    }

    // Deduplicate uninstall hints per InstallChannel by:
    // 1. Iterating over each unique channel
    // 2. Picking one representative install for that channel
    // 3. Collecting its uninstall_hint() into a HashSet<String>
    //
    // The resulting `_uninstall_hints` set can be used when constructing
    // the Finding's fix to avoid duplicated uninstall commands.
    let _uninstall_hints: std::collections::HashSet<String> = channels
        .into_iter()
        .filter_map(|channel| {
            installs
                .iter()
                .find(|install| install.channel == channel)
                .map(|install| install.uninstall_hint())
        })
        .collect();

To fully apply the deduplicated uninstall hints in the fix:

  1. Replace any existing logic that aggregates uninstall commands like:
    let fixes = installs.iter().map(|i| i.uninstall_hint()).collect::<Vec<_>>().join("\n");
    with code that uses the _uninstall_hints set, e.g. by:
    • Converting _uninstall_hints into a Vec<String>, sorting if desired for determinism, and then joining with "\n".
  2. Ensure that the Finding created inside check_install_channels (or any helper it calls) uses this joined string of deduplicated hints as the fix text.
  3. If InstallChannel does not yet implement Eq + Hash + Copy/Clone, derive or implement these traits so it can be used as a key in HashSet<InstallChannel>.

Fix in Cursor

Comment thread docs/numan-doctor.md Outdated
|----|----------|-----------|
| `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>` |
| `install.multiple_channels` | `warn` | More than one `numan` binary from different install channels (cargo, winget, homebrew, release archive) → fix: uninstall the other channel(s) before reinstalling |

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.

nitpick (typo): Capitalize "Homebrew" to match its usage elsewhere and maintain consistency in tool names.

Update "homebrew" here to "Homebrew" so it matches the capitalization used elsewhere in the docs.

Suggested change
| `install.multiple_channels` | `warn` | More than one `numan` binary from different install channels (cargo, winget, homebrew, release archive) → fix: uninstall the other channel(s) before reinstalling |
| `install.multiple_channels` | `warn` | More than one `numan` binary from different install channels (cargo, winget, Homebrew, release archive) → fix: uninstall the other channel(s) before reinstalling |

Fix in Cursor

@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds cross-channel installation detection and uninstall prompting for Cargo, winget, and Homebrew, plus a doctor warning for mixed installations.

  • Integrates the install guard into the main crate and invokes it conditionally from build.rs.
  • Adds guarded wrapper scripts for winget, Homebrew, and repository-based Cargo installation.
  • Detects multiple installation channels in numan doctor and documents the new installation flows.

Confidence Score: 5/5

The PR appears safe to merge based on established findings, although the prior ordinary-Cargo-install guard issue remains unresolved by the available evidence.

No blocking failure remains established; the previous path-dependency publication issue is fixed, while the repository does not establish whether the revised OUT_DIR heuristic fixes the previously reported ordinary cargo-install path.

Important Files Changed

Filename Overview
Cargo.toml Registers the install guard as a binary in the main publishable crate, removing the prior path-only dependency packaging failure.
build.rs Adds a fallback Cargo-install heuristic, but the available repository evidence does not establish that ordinary documented installs satisfy it.
src/util/install_channel.rs Implements channel classification, installation discovery, prompting, and package-manager-specific uninstall behavior.
src/cmd/doctor.rs Adds a warning when PATH contains numan binaries classified under multiple installation channels.
scripts/install-homebrew.sh Runs the install guard before invoking the Homebrew installation.
scripts/install-winget.ps1 Runs the install guard before invoking the winget installation.

Reviews (4): Last reviewed commit: "Address install guard review feedback" | Re-trigger Greptile

Comment thread Cargo.toml Outdated
Comment thread build.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e0aa500c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Cargo.toml Outdated
Comment on lines +23 to +27
[build-dependencies]
numan-install-guard = { path = "install-guard" }

[dependencies]
numan-install-guard = { path = "install-guard" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make the guard dependency publishable

When publishing or packaging the crates.io channel, these path-only numan-install-guard dependencies make cargo package fail with “all dependencies must have a version specified”; the guard crate is also marked publish = false, so cargo install numan-cli cannot resolve it from the registry. This breaks the documented crates.io/source distribution path before users can install the binary.

AGENTS.md reference: AGENTS.md:L197-L197

Useful? React with 👍 / 👎.

Comment thread build.rs Outdated
Comment on lines +2 to +3
if std::env::var("CARGO_INSTALL_ROOT").is_ok()
&& numan_install_guard::run_cargo_install_guard() != std::process::ExitCode::SUCCESS

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Run the cargo guard during plain installs

For normal cargo install numan-cli or cargo install --git ..., Cargo does not set CARGO_INSTALL_ROOT for build scripts unless the user explicitly exports it; I verified with a minimal cargo install --root ... -vv probe that the build script saw CARGO_INSTALL_ROOT=None. This condition therefore skips the advertised automatic guard on the default cargo install path, allowing cross-channel installs to proceed silently.

AGENTS.md reference: AGENTS.md:L37-L38

Useful? React with 👍 / 👎.

@qodo-code-review

qodo-code-review Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Rustfmt gate will fail ✓ Resolved 🐞 Bug ☼ Reliability
Description
install-guard/src/lib.rs contains formatting that does not match rustfmt output (e.g.,
mis-indented let remaining), which will fail the repo’s cargo fmt --check CI gate. This will
block merges/releases until the guard crate is formatted.
Code

install-guard/src/lib.rs[R315-316]

+  let remaining = conflicting_installs(channel, exclude_paths);
+    if !remaining.is_empty() {
Relevance

●●● Strong

Formatting mismatches regularly fixed to satisfy cargo fmt --check CI gate.

PR-#46
PR-#9

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The guard source shows non-rustfmt indentation, and the repository explicitly enforces `cargo fmt
--check` as a required CI/release step.

install-guard/src/lib.rs[313-318]
REVIEW.md[7-12]
.github/workflows/release.yml[83-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The new `install-guard` Rust sources are not rustfmt-compliant (e.g., inconsistent indentation), and this repo enforces `cargo fmt --check` in CI.

### Issue Context
A single indentation mismatch is enough to fail `cargo fmt --check`.

### Fix Focus Areas
- install-guard/src/lib.rs[313-318]
- REVIEW.md[7-12]
- .github/workflows/release.yml[83-90]

### Suggested fix direction
- Run `cargo fmt --all` (or at least `cargo fmt -p numan-install-guard`) and commit the resulting formatting changes.
- Re-run the formatting gate locally (`cargo fmt --all -- --check`) to confirm.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Crate packaging will fail ✓ Resolved 🐞 Bug ≡ Correctness
Description
numan-cli now depends on numan-install-guard via a local path, and also excludes
install-guard/** from the package, which will break cargo package/crates.io release pipelines.
This is a merge-blocker because CI and the release workflow both run cargo package --locked.
Code

Cargo.toml[R24-27]

+numan-install-guard = { path = "install-guard" }

[dependencies]
+numan-install-guard = { path = "install-guard" }
Relevance

●●● Strong

Team strongly enforces release/CI gates; breaking cargo package --locked is merge-blocking and
likely fixed.

PR-#13

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The root manifest both (1) excludes install-guard/** and (2) declares numan-install-guard only
as a local path dependency, while CI/release explicitly run cargo package --locked, which requires
the published/packaged crate to be self-contained and publishable.

Cargo.toml[15-28]
.github/workflows/ci.yml[159-171]
.github/workflows/release.yml[83-91]
docs/RELEASING.md[70-77]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`numan-cli` is published/packaged in CI, but it now uses a local-only path dependency (`numan-install-guard = { path = "install-guard" }`) while also excluding `install-guard/**` from the crate package. This will break `cargo package` and crates.io publication.

### Issue Context
- CI and release workflows run `cargo package --locked`.
- `numan-install-guard` is declared `publish = false`, so the main crate cannot depend on it as an external crates.io dependency.

### Fix Focus Areas
- Cargo.toml[15-28]
- install-guard/Cargo.toml[1-11]
- .github/workflows/ci.yml[159-171]
- .github/workflows/release.yml[83-91]

### Suggested fix direction
Choose one:
1) **Inline the guard as a module** inside `numan-cli` (no path dependency), and (optionally) keep a small internal bin wrapper if still needed.
2) **Publish `numan-install-guard`** to crates.io (remove `publish = false`, give it a real versioning plan), then depend on it via `version = "..."` in `numan-cli` (you can keep `path = "install-guard"` for workspace dev, but include `version` so publish works).
3) If the project should not be published anymore, explicitly set `publish = false` for `numan-cli` and update release workflows/docs accordingly (likely not desired given current repo docs/workflows).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Homebrew install ignores NUMAN_ROOT 📘 Rule violation ≡ Correctness
Description
The install-time scripts scripts/install-homebrew.sh, scripts/install-winget.ps1, and
scripts/install-cargo.ps1 invoke brew install, winget install, and cargo install,
respectively, which perform installations into tool/system-managed locations not constrained under
$NUMAN_ROOT. Compliance requires install-time code to keep all filesystem writes confined to paths
rooted under $NUMAN_ROOT.
Code

scripts/install-homebrew.sh[R13-16]

+echo "Checking for conflicting numan installs..."
+cargo run --quiet --manifest-path "${guard_manifest}" -- brew
+echo "Running: brew install tonythethompson/numan/numan ${*}"
+brew install tonythethompson/numan/numan "$@"
Relevance

●● Moderate

Compliance constraint is broad/behavioral; no close historical precedent on forbidding
package-manager installs outside $NUMAN_ROOT.

PR-#23

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The compliance rule cited in the findings requires that install-time scripts confine all filesystem
writes under $NUMAN_ROOT and avoid actions that mutate external locations. Each referenced script
explicitly calls a package manager install command (brew install, winget install, or `cargo
install`), and these tools install into their own managed prefixes or system locations rather than
under $NUMAN_ROOT, demonstrating that the scripts can cause writes outside the allowed root.

Rule 2436679: Install code must not invoke Nu integration APIs
scripts/install-homebrew.sh[13-16]
scripts/install-winget.ps1[36-38]
scripts/install-cargo.ps1[31-33]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`scripts/install-homebrew.sh`, `scripts/install-winget.ps1`, and `scripts/install-cargo.ps1` run `brew install`, `winget install`, and `cargo install`, respectively, which perform filesystem mutations outside `$NUMAN_ROOT`.

## Issue Context
Compliance requires install-time scripts/hooks to avoid Nu integration APIs and to keep all filesystem writes confined to paths rooted under `$NUMAN_ROOT`.

## Fix Focus Areas
- scripts/install-homebrew.sh[13-16]
- scripts/install-winget.ps1[36-38]
- scripts/install-cargo.ps1[31-33]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. NUMAN_SKIP_INSTALL_GUARD feature flag ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
install-guard introduces a bypass flag NUMAN_SKIP_INSTALL_GUARD, which is a feature-flag-like
compatibility shim without any linked requirement or ticket reference in this PR. Unrequested flags
add ongoing maintenance and risk of inconsistent behavior across environments.
Code

install-guard/src/lib.rs[R327-333]

+fn should_skip_guard() -> bool {
+    std::env::var("NUMAN_SKIP_INSTALL_GUARD")
+        .map(|v| v == "1")
+        .unwrap_or(false)
+        || std::env::var("CI").ok().as_deref() == Some("true")
+        || std::env::var("GITHUB_ACTIONS").ok().as_deref() == Some("true")
+}
Relevance

●● Moderate

Repo dislikes unrequested shims/flags, but no close precedent on env-based bypass for install guard.

PR-#67

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist forbids adding feature flags/shims unless explicitly requested. The new
should_skip_guard() logic introduces NUMAN_SKIP_INSTALL_GUARD to toggle behavior via environment
configuration.

Rule 2452627: Exclude unrequested feature flags and compatibility shims from change sets
install-guard/src/lib.rs[327-333]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new environment-variable bypass (`NUMAN_SKIP_INSTALL_GUARD`) was added, functioning as a feature flag/shim.

## Issue Context
Compliance requires excluding unrequested feature flags and compatibility shims unless explicitly required by linked requirements/tickets.

## Fix Focus Areas
- install-guard/src/lib.rs[327-333]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Doctor scans package directories ✓ Resolved 🐞 Bug ➹ Performance
Description
numan doctor now always runs check_install_channels, which calls discover_installations() and
may recursively enumerate WinGet/Homebrew directories. This can add avoidable filesystem I/O
(especially on Windows with large WinGet trees) every time doctor is run.
Code

src/cmd/doctor.rs[199]

+    check_install_channels(&mut findings);
Relevance

●● Moderate

Performance tradeoff in doctor is subjective; history shows changes to doctor accepted but not
this specific optimization.

PR-#35

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Doctor now calls check_install_channels on every run, and that check invokes
discover_installations, which performs recursive read_dir walks (bounded by depth) under known
WinGet/Homebrew install roots.

src/cmd/doctor.rs[191-205]
src/cmd/doctor.rs[327-338]
install-guard/src/lib.rs[83-100]
install-guard/src/lib.rs[132-147]
install-guard/src/lib.rs[168-193]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`numan doctor` now triggers install-channel discovery unconditionally. Discovery includes depth-bounded recursive directory traversal under package-manager roots, which can still be non-trivial on some machines.

### Issue Context
The recursion is bounded (`max_depth`), but it still performs `read_dir` walks under WinGet/Homebrew roots when they exist.

### Fix Focus Areas
- src/cmd/doctor.rs[191-205]
- src/cmd/doctor.rs[327-378]
- install-guard/src/lib.rs[83-100]
- install-guard/src/lib.rs[132-147]
- install-guard/src/lib.rs[168-193]

### Suggested fix direction
- Prefer a **fast path** for doctor (e.g., scan only PATH by default), and only scan known package-manager roots when needed (or behind an opt-in flag / env var).
- Alternatively, add a lightweight early-exit heuristic (e.g., once >1 channel is already detected, stop deeper scanning).
- Consider caching discovery results for the duration of the doctor run if multiple checks ever call into it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 23 rules
✅ REVIEW.md

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +13 to +16
echo "Checking for conflicting numan installs..."
cargo run --quiet --manifest-path "${guard_manifest}" -- brew
echo "Running: brew install tonythethompson/numan/numan ${*}"
brew install tonythethompson/numan/numan "$@"

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.

Remediation recommended

1. Homebrew install ignores numan_root 📘 Rule violation ≡ Correctness

The install-time scripts scripts/install-homebrew.sh, scripts/install-winget.ps1, and
scripts/install-cargo.ps1 invoke brew install, winget install, and cargo install,
respectively, which perform installations into tool/system-managed locations not constrained under
$NUMAN_ROOT. Compliance requires install-time code to keep all filesystem writes confined to paths
rooted under $NUMAN_ROOT.
Agent Prompt
## Issue description
`scripts/install-homebrew.sh`, `scripts/install-winget.ps1`, and `scripts/install-cargo.ps1` run `brew install`, `winget install`, and `cargo install`, respectively, which perform filesystem mutations outside `$NUMAN_ROOT`.

## Issue Context
Compliance requires install-time scripts/hooks to avoid Nu integration APIs and to keep all filesystem writes confined to paths rooted under `$NUMAN_ROOT`.

## Fix Focus Areas
- scripts/install-homebrew.sh[13-16]
- scripts/install-winget.ps1[36-38]
- scripts/install-cargo.ps1[31-33]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread install-guard/src/lib.rs
Comment thread Cargo.toml Outdated
Comment thread install-guard/src/lib.rs Outdated
Comment thread src/cmd/doctor.rs

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build.rs`:
- Around line 2-3: Replace the CARGO_INSTALL_ROOT check in build.rs with a
reliable install-only signal or wrapper so run_cargo_install_guard executes only
for cargo install invocations and still covers standard cargo install numan-cli
without --root. Update AGENTS.md line 37, README.md line 148, docs/PACKAGING.md
line 49, and packaging/winget/README.md line 29 to remove or correct claims that
plain cargo install numan-cli automatically enforces the guard.

In `@Cargo.toml`:
- Around line 20-27: Make numan-install-guard publishable before numan-cli by
publishing the install-guard crate and replacing its path-only entries in both
[build-dependencies] and [dependencies] with the released registry version. Keep
the dependency available in both sections and ensure the version matches the
published crate.

In `@install-guard/src/lib.rs`:
- Around line 327-332: Update should_skip_guard to treat CI and GITHUB_ACTIONS
as enabled when their values are either "1" or case-insensitive "true", while
preserving the existing NUMAN_SKIP_INSTALL_GUARD behavior and guard bypass
logic.
- Around line 414-433: Replace the local filtering in
conflicting_installs_excludes_paths with a call to the production
conflicting_installs function through an injectable discovery or candidate-roots
seam. Extend the tests to cover excluded targets, same-channel installs,
unreadable directories, and cancellation in non-interactive mode, ensuring each
assertion exercises the real conflicting_installs behavior.
- Around line 1-2: Remove the narrative comments at the module header and line
10; retain comments only if they explain a non-obvious design reason rather than
describing what the code declares or does.
- Line 315: Restore rustfmt formatting for the conflicting_installs call in the
surrounding function so its indentation matches rustfmt output and cargo fmt
--check passes.
- Around line 354-380: Update the uninstall flow in run_install_guard and
uninstall_channel so package-manager mutations are serialized under the
installation-root mutation lock. Resolve an authoritative root and create the
pre-mutation snapshot before invoking run_command; if the standalone guard
cannot obtain either, do not uninstall and instead print the appropriate
uninstall command. Ensure the lock and snapshot cover the entire cargo, winget,
or brew uninstall operation.
- Around line 181-183: Update walk_for_numan and the related discovery flow in
discover_installations to propagate read_dir failures as contextual
anyhow::Result errors instead of returning an empty traversal. Treat an absent
directory as the only case that may skip the install guard; otherwise fail
closed, and ensure numan doctor reports incomplete detection rather than
interpreting missing results as no multi-install finding.

In `@install-guard/src/main.rs`:
- Around line 8-18: Replace the manual argument matching in main with a
clap-based CLI module: add install-guard/src/cli.rs defining Parser and
Subcommand-derived types for cargo, winget, and brew/homebrew commands, add the
clap 4 dependency to Cargo.toml, and have main parse and dispatch the CLI
values. Preserve the existing usage/error behavior where applicable, and do not
modify Cargo.lock.

In `@scripts/install-cargo.ps1`:
- Line 13: Update the install-cargo wrapper so the default Cargo --path is
derived from the script’s resolved repository root instead of the caller’s
current directory. In the install flow around $repoRoot and $CargoArgs, set or
rewrite the default path after $repoRoot is established so cargo install always
targets the checkout the script belongs to, while preserving any explicit path
overrides.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 95ff94aa-0a7e-4f26-be54-2d54a1ca1273

📥 Commits

Reviewing files that changed from the base of the PR and between 5b186bc and 4e0aa50.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • AGENTS.md
  • Cargo.toml
  • README.md
  • build.rs
  • docs/PACKAGING.md
  • docs/numan-doctor.md
  • install-guard/Cargo.toml
  • install-guard/src/lib.rs
  • install-guard/src/main.rs
  • packaging/winget/README.md
  • scripts/install-cargo.ps1
  • scripts/install-homebrew.sh
  • scripts/install-winget.ps1
  • src/cmd/doctor.rs
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • tonythethompson/QuickShell (manual)
  • tonythethompson/numan (manual)
  • tonythethompson/dependency-chain-substrate (manual)
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: Greptile Review
  • GitHub Check: Sourcery review
  • GitHub Check: Test (windows-latest)
  • GitHub Check: Analyze (rust)
🧰 Additional context used
📓 Path-based instructions (10)
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

**/*: Use Serena's semantic, symbol-aware tools as the primary tools for reading and editing code; use built-in Read, Glob, Grep, and Edit only under the stated exceptions.
Before editing a code file, inspect its symbol overview, read the specific symbols being changed, and edit them with Serena's symbol-aware tools.
Understand the existing code before changing it and make the smallest change that satisfies the request; avoid unrelated cleanup, premature abstractions, unnecessary error handling, feature flags, and compatibility shims.
Prefer editing existing files over creating new ones, and never create Markdown or README files unless explicitly requested.
For exploratory questions, provide a 2–3 sentence recommendation with the main tradeoff and do not implement until the user agrees.
For UI or frontend changes that cannot be tested in a browser, explicitly state that browser testing was not performed rather than claiming success.
Address security issues when discovered, including injection, XSS, SQL injection, path traversal, and secret leaks.
Pause and obtain confirmation before destructive, hard-to-reverse, externally visible, or third-party-upload actions, including deleting files or branches, dropping tables, force-pushing, modifying CI/CD, posting externally, or uploading content.
When blocked, investigate the root cause instead of bypassing it with --no-verify, --force, or deletion; investigate unfamiliar files, branches, and configuration before deleting them.
Only commit when explicitly asked; do not proactively update git configuration or push changes.
Do not skip Git hooks unless explicitly asked; if a pre-commit hook fails, fix the issue, re-stage, and create a new commit rather than amending.
Stage files by name rather than using git add -A or git add ., to avoid accidentally including secrets or large binaries.
Use a HEREDOC for commit messages, and add co-author attribution only when the user explicitly requests the exact trailer.
Do not force-pus...

Files:

  • build.rs
  • scripts/install-cargo.ps1
  • install-guard/src/main.rs
  • install-guard/Cargo.toml
  • packaging/winget/README.md
  • scripts/install-winget.ps1
  • scripts/install-homebrew.sh
  • README.md
  • AGENTS.md
  • docs/PACKAGING.md
  • src/cmd/doctor.rs
  • docs/numan-doctor.md
  • Cargo.toml
  • install-guard/src/lib.rs
**/*.{js,jsx,ts,tsx,py,java,go,rs,rb,php,c,cpp,h,hpp,cs,swift,kt,kts}

📄 CodeRabbit inference engine (CLAUDE.md)

Add comments only when the WHY is non-obvious; do not narrate what the code does, reference the current task, or reference the PR in comments.

Files:

  • build.rs
  • install-guard/src/main.rs
  • src/cmd/doctor.rs
  • install-guard/src/lib.rs
!**/.env,!**/credentials.json,!**/*.pem

📄 CodeRabbit inference engine (CLAUDE.md)

Do not commit files that appear to contain secrets, including .env, credentials.json, and PEM files; warn before doing so even if explicitly requested.

Files:

  • build.rs
  • scripts/install-cargo.ps1
  • install-guard/src/main.rs
  • install-guard/Cargo.toml
  • packaging/winget/README.md
  • scripts/install-winget.ps1
  • scripts/install-homebrew.sh
  • README.md
  • AGENTS.md
  • docs/PACKAGING.md
  • src/cmd/doctor.rs
  • docs/numan-doctor.md
  • Cargo.toml
  • install-guard/src/lib.rs
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use the Rust 2021 edition.
Use anyhow::Result with .context(...) in application code; use thiserror for library error types that callers match on.
Use clap derive macros for CLI definitions.
Use serde with serde_json or toml for serialization.
Function parameters must use &Path, not &PathBuf.
Library code must not panic; error paths should return anyhow::Result with context where appropriate.
Add or update tests for behavior changes, including relevant failure paths.
New mutating code paths must acquire the mutation lock via acquire_mutation_lock(root) and snapshot the lockfile before writes.
Lockfile, journal, and state-file JSON writes must use write_json_atomic.
numan install must write only to $NUMAN_ROOT; it must not invoke Nu or register plugins/autoloads.
Only activate and deactivate may modify Nu integration state.
Treat the lockfile as the authoritative source of truth; derived projections such as autoload state must not be authoritative.
Install payloads under versioned, content-addressed paths and never overwrite them in place.
Never overwrite foreign autoload files; respect OWNERSHIP_MARKER.
Pass plugin paths through environment variables only; do not use runtime interpolation in Nu program strings.

**/*.rs: All CI gates must pass: cargo test, cargo clippy -- -D warnings, and cargo fmt --check.
Every mutating command—including install, remove, update, gc, and future nupm import—must call acquire_mutation_lock(root).
Lockfiles, journals, and state files must use write_json_atomic; partial writes are not allowed.
Pending activation, autoload, and lifecycle journals must be stored under $NUMAN_ROOT/state/.
Module autoload identity must match all four fields: Nu executable hash, Nu version, vendor autoload directory, and managed file path; the lockfile module_activation value is authoritative.
Never overwrite foreign autoload files; respect OWNERSHIP_MARKER.
Pass paths to Nu only throu...

Files:

  • build.rs
  • install-guard/src/main.rs
  • src/cmd/doctor.rs
  • install-guard/src/lib.rs
**/*.{rs,toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run and keep cargo fmt/rustfmt clean, and ensure cargo clippy -- -D warnings passes.

Files:

  • build.rs
  • install-guard/src/main.rs
  • install-guard/Cargo.toml
  • src/cmd/doctor.rs
  • Cargo.toml
  • install-guard/src/lib.rs
**/*.{rs,nu}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.{rs,nu}: Real-Nu acceptance tests must be marked #[ignore] and should be run when changes affect activation or nupm import; unit tests must not spawn real nu and should use injectable seams such as FakeCandidateRunner or registrars.
The nupm integration must be read-only toward NUPM_HOME, must not execute build.nu, and must not perform bidirectional synchronization.

Unit tests must use FakeCandidateRunner or injectable registrars and must not spawn a real nu process.

Files:

  • build.rs
  • install-guard/src/main.rs
  • src/cmd/doctor.rs
  • install-guard/src/lib.rs
**/*.{rs,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Match existing naming, module layout, and documentation level in the file being edited; update AGENTS.md, docs/, or command help when structure, conventions, or user-visible behavior changes.

Tests must cover failure modes, not only successful execution.

Files:

  • build.rs
  • install-guard/src/main.rs
  • packaging/winget/README.md
  • README.md
  • AGENTS.md
  • docs/PACKAGING.md
  • src/cmd/doctor.rs
  • docs/numan-doctor.md
  • install-guard/src/lib.rs
**/*.{rs,md,toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use the repository's established serialization and module conventions rather than introducing unrelated refactors.

Files:

  • build.rs
  • install-guard/src/main.rs
  • install-guard/Cargo.toml
  • packaging/winget/README.md
  • README.md
  • AGENTS.md
  • docs/PACKAGING.md
  • src/cmd/doctor.rs
  • docs/numan-doctor.md
  • Cargo.toml
  • install-guard/src/lib.rs
**/*.md

📄 CodeRabbit inference engine (REVIEW.md)

Update documentation and AGENTS.md when project structure or conventions change.

Files:

  • packaging/winget/README.md
  • README.md
  • AGENTS.md
  • docs/PACKAGING.md
  • docs/numan-doctor.md
src/{state/migration_journal.rs,nu/migrate_legacy.rs,cmd/doctor.rs}

📄 CodeRabbit inference engine (AGENTS.md)

The legacy-Nu migration journal must use the documented PreparedRenamedActive stages, self-heal at the top of migrate_legacy_install_with_detector, and leave unreadable or schema-mismatched journals for manual repair.

Files:

  • src/cmd/doctor.rs
🔍 Remote MCP Context7

Based on my search attempts, I've gathered relevant context about the technologies used in this PR. Let me provide a summary of the review context:

Additional Context Found for PR Review

Build System Integration (Rust Cargo)

The PR's use of build.rs as an entry point that checks CARGO_INSTALL_ROOT and calls numan_install_guard::run_cargo_install_guard() leverages Cargo's standard build script capabilities. According to Rust documentation, Cargo build scripts can access environment variables at runtime using std::env, and build scripts execute during the compilation phase before the binary is created. The implementation checks exit status and fails the build (exit status 1) if the guard doesn't return success—a correct pattern for guarding against conflicting installations during cargo install.

PowerShell Script Patterns

The PR introduces scripts/install-cargo.ps1 and scripts/install-winget.ps1 with configurable parameters:

  • Both scripts accept $CargoArgs and $WingetArgs parameters with default values
  • This follows PowerShell best practices for parameter validation and native executable invocation with argument arrays (& $exePath $argsArray) as documented in the PowerShell best practices guide.
  • The pattern of validating manifest presence and forwarding arguments to native tools (cargo, winget) is consistent with cross-platform script design practices.

Shell Script Error Handling

The scripts/install-homebrew.sh uses strict Bash error handling as mentioned in the AI summary. The documented approach for production shell scripts includes mechanisms like set -e (exit on error) and set -u (error on undefined variables), though the specific implementation details would require viewing the actual script content.

Install Guard Architecture

The PR introduces a comprehensive install guard library (install-guard/) with:

  • Public API: InstallChannel enum (Cargo, Winget, Homebrew, ReleaseArchive, Unknown), discovery functions (discover_installations(), conflicting_installs()), and channel-specific guard functions
  • Integration points: Called during Cargo build, wrapped in WinGet/Homebrew scripts, and integrated into the numan doctor command for diagnostic warnings
  • Behavior: Interactive installs prompt user uninstall confirmation; non-interactive installs fail; CI environments bypass checks via environment variable

Test Coverage

The AI summary indicates tests are included for install guard classification and conflict detection, with the code review effort rated as "High" for the 434-line install-guard/src/lib.rs implementation and "Medium" for the CLI dispatcher (install-guard/src/main.rs at 18 lines).

Note: The user requirements referenced a different project (Babel-Player), so this analysis focused on the technical context directly relevant to the numan install-guard PR.

🔇 Additional comments (10)
Cargo.toml (1)

80-83: LGTM!

scripts/install-winget.ps1 (1)

1-38: LGTM!

scripts/install-homebrew.sh (1)

1-16: LGTM!

README.md (1)

130-140: LGTM!

docs/PACKAGING.md (1)

40-41: LGTM!

packaging/winget/README.md (1)

23-27: LGTM!

build.rs (1)

3-3: 🎯 Functional Correctness

No change needed.

install-guard/src/lib.rs (1)

6-6: 📐 Maintainability & Code Quality

Verify MSRV support for IsTerminal.

Rust 2021 does not define a minimum compiler version that guarantees std::io::IsTerminal. Confirm that the declared toolchain supports this API, or use a compatible alternative.

Source: Coding guidelines

src/cmd/doctor.rs (1)

327-378: 📐 Maintainability & Code Quality

Run the required Rust validation before merge.

The PR summary states that Ubuntu, Windows, and macOS validation remains pending. Run cargo test, cargo clippy -- -D warnings, and cargo fmt --check on the final revision before release.

As per coding guidelines, "**/*.rs: All CI gates must pass: cargo test, cargo clippy -- -D warnings, and cargo fmt --check."

Source: Coding guidelines

docs/numan-doctor.md (1)

132-132: LGTM!

Comment thread build.rs Outdated
Comment on lines +2 to +3
if std::env::var("CARGO_INSTALL_ROOT").is_ok()
&& numan_install_guard::run_cargo_install_guard() != std::process::ExitCode::SUCCESS

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files relevant =="
git ls-files | rg '(^build\.rs$|AGENTS\.md$|README\.md$|docs/PACKAGING\.md$|packaging/winget/README\.md$|^Cargo\.toml$|install-guard|numan_install_guard)' || true

echo
echo "== build.rs =="
cat -n build.rs

echo
echo "== relevant docs snippets =="
for f in AGENTS.md README.md docs/PACKAGING.md packaging/winget/README.md Cargo.toml; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat -n "$f" | sed -n '1,220p'
  fi
done

echo
echo "== references to CARGO_INSTALL_ROOT / cargo install guard =="
rg -n "CARGO_INSTALL_ROOT|cargo install|install-guard|guard|uninstall|winget|Homebrew" .

Repository: tonythethompson/numan

Length of output: 50379


🏁 Script executed (no clone):

#!/bin/bash
set -euo pipefail

echo "== cargo reference for CARGO_INSTALL_ROOT via rustforge/cargo-source =="
curl -fsSL 'https://raw.githubusercontent.com/rust-lang/cargo/master/src/cargo/util/config/mod.rs' \
  | rg -n "CARGO_INSTALL_ROOT|install\.root" -C 4 || true

echo
echo "== cargo docs pages (raw) for CARGO_INSTALL_ROOT =="
curl -fsSL 'https://raw.githubusercontent.com/rust-lang/cargo/master/src/doc/reference/environment-variables.adoc' \
  | rg -n "CARGO_INSTALL_ROOT|install\.root" -C 6 || true

Length of output: 348


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== install-guard public API =="
cat -n install-guard/src/lib.rs | sed -n '1,260p'

echo
echo "== install-guard CLI behavior =="
cat -n install-guard/src/main.rs | sed -n '1,260p'

echo
echo "== cargo wrapper scripts =="
for f in scripts/install-cargo.ps1 scripts/install-homebrew.sh scripts/install-winget.ps1; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat -n "$f" | sed -n '1,220p'
  fi
done

Repository: tonythethompson/numan

Length of output: 14109


🌐 Web query:

Cargo reference CARGO_INSTALL_ROOT install.root environment variable documentation

💡 Result:

The CARGO_INSTALL_ROOT environment variable is used to define the root directory where the cargo install command places installed binary crates [1][2]. Executables are specifically placed into a bin subdirectory under this root path [1][2]. Additionally, metadata files used to track installed packages, such as.crates.toml and.crates2.json, are stored within this root directory [1][2]. If not specified, the default installation root is Cargo's home directory, which typically defaults to.cargo in the user's home directory [1][2]. The precedence for determining the installation root, from highest to lowest, is as follows [3][4]: 1. The --root command-line option [3][1] 2. The CARGO_INSTALL_ROOT environment variable [3][1] 3. The install.root Cargo configuration value [3][1] 4. The CARGO_HOME environment variable [3][4] 5. The default location ($HOME/.cargo) [3][4] The install.root configuration value mentioned in Cargo's TOML configuration is equivalent to the CARGO_INSTALL_ROOT environment variable [1][2]. Cargo configuration keys formatted as foo.bar can generally be defined by setting an environment variable of the form CARGO_FOO_BAR [1][2]. In this case, the install.root key corresponds to CARGO_INSTALL_ROOT [1][2].

Citations:


Use an install-only guard boundary.

CARGO_INSTALL_ROOT sets Cargo’s install root path and is resolved after resolving the default .cargo root if absent. It does not prove the current invocation is cargo install, so build.rs can also run the uninstall prompt for configured cargo build/cargo test invocations, while standard cargo install numan-cli without --root may skip it. Gate this with an install-only signal or wrapper instead of treating CARGO_INSTALL_ROOT as the installation boundary.

Update the docs that describe plain cargo install numan-cli as automatically enforcing this guard: AGENTS.md#37, README.md#148, docs/PACKAGING.md#49, and packaging/winget/README.md#29.

📍 Affects 5 files
  • build.rs#L2-L3 (this comment)
  • AGENTS.md#L37-L37
  • README.md#L148-L148
  • docs/PACKAGING.md#L49-L49
  • packaging/winget/README.md#L29-L29
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build.rs` around lines 2 - 3, Replace the CARGO_INSTALL_ROOT check in
build.rs with a reliable install-only signal or wrapper so
run_cargo_install_guard executes only for cargo install invocations and still
covers standard cargo install numan-cli without --root. Update AGENTS.md line
37, README.md line 148, docs/PACKAGING.md line 49, and
packaging/winget/README.md line 29 to remove or correct claims that plain cargo
install numan-cli automatically enforces the guard.

Comment thread Cargo.toml Outdated
Comment on lines +20 to +27
"install-guard/**",
]

[[bin]]
name = "numan"
path = "src/main.rs"
[build-dependencies]
numan-install-guard = { path = "install-guard" }

[dependencies]
numan-install-guard = { path = "install-guard" }

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Make numan-install-guard publishable before releasing numan-cli.

Cargo rejects non-dev path dependencies without a version during cargo package, and manifests for published crates remove path. Since this package excludes install-guard/**, the archive also cannot provide that source. Publish numan-install-guard and switch both [build-dependencies]/[dependencies] to a registry version, or move the guard code into this package.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` around lines 20 - 27, Make numan-install-guard publishable before
numan-cli by publishing the install-guard crate and replacing its path-only
entries in both [build-dependencies] and [dependencies] with the released
registry version. Keep the dependency available in both sections and ensure the
version matches the published crate.

Comment thread install-guard/src/lib.rs Outdated
Comment on lines +1 to +2
//! Detect Numan binaries installed via different package managers and gate
//! cross-channel installs behind an interactive uninstall prompt.

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove narrative code comments.

Lines 1-2 and Line 10 describe what the code declares. Remove them or document a non-obvious design reason instead.

As per coding guidelines, “Add comments only when the WHY is non-obvious; do not narrate what the code does.”

Also applies to: 10-10

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install-guard/src/lib.rs` around lines 1 - 2, Remove the narrative comments
at the module header and line 10; retain comments only if they explain a
non-obvious design reason rather than describing what the code declares or does.

Source: Coding guidelines

Comment on lines +181 to +183
let entries = match std::fs::read_dir(dir) {
Ok(entries) => entries,
Err(_) => return,

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching install-guard/src/lib.rs:"
if [ -f install-guard/src/lib.rs ]; then
  wc -l install-guard/src/lib.rs
  echo "--- outline ---"
  ast-grep outline install-guard/src/lib.rs || true
  echo "--- relevant lines 140-240 ---"
  sed -n '140,240p' install-guard/src/lib.rs | nl -ba -v140
else
  fd -a 'lib\.rs$' .
fi

echo "--- references to walk_for_numan/conflicting_installs/discover_installations/doctor ---"
rg -n "walk_for_numan|conflicting_installs|discover_installations|doctor|InstallGuard|install guard|Guard" install-guard . -g '*.rs' || true

Repository: tonythethompson/numan

Length of output: 1961


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- install-guard/src/lib.rs line 165-222 ---"
sed -n '165,222p' install-guard/src/lib.rs | awk '{printf "%4d\t%s\n", NR+164, $0}'

echo "--- install-guard/src/lib.rs line 256-330 ---"
sed -n '256,330p' install-guard/src/lib.rs | awk '{printf "%4d\t%s\n", NR+256, $0}'

echo "--- cargo/doctor references in repo by file type ---"
rg -n "install-guard|discover_installations|conflicting_installs|doctor|Doctor|RunDoctor|run.*doctor|numan.*doctor|doctor.*numan" -g '*.rs' -g '*.toml' . || true

Repository: tonythethompson/numan

Length of output: 32559


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- install-guard/src/lib.rs discovery symbols ---"
sed -n '50,180p' install-guard/src/lib.rs | awk '{printf "%4d\t%s\n", NR+49, $0}'

echo "--- install-guard/src/lib.rs guard return paths ---"
sed -n '202,326p' install-guard/src/lib.rs | awk '{printf "%4d\t%s\n", NR+201, $0}'

echo "--- src/cmd/doctor.rs off-path/install-findings usage ---"
sed -n '315,365p' src/cmd/doctor.rs | awk '{printf "%4d\t%s\n", NR+314, $0}'

echo "--- src/main.rs install guard launch paths ---"
sed -n '1,80p' src/main.rs | awk '{printf "%4d\t%s\n", NR, $0}'

Repository: tonythethompson/numan

Length of output: 13580


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- src/cmd/install.rs guard call around install entry points ---"
rg -n "numan_install_guard|run_.*_install_guard|install_guard|NUMAN_SKIP_INSTALL_GUARD|skip.*guard|guard" src/cmd/install.rs | head -n 80 || true

echo "--- install command outline ---"
ast-grep outline src/cmd/install.rs --view expanded || true

echo "--- src/cmd/install.rs relevant search regions ---"
for pat in "install_guard|NUMAN_SKIP|install"; do
  echo "### $pat"
  rg -n "$pat" src/cmd/install.rs -C 2 || true
done

Repository: tonythethompson/numan

Length of output: 1596


Fail closed when installation discovery cannot read a directory.

walk_for_numan turns std::fs::read_dir failures into an empty traversal result at install-guard/src/lib.rs:181-183. discover_installations() then returns an incomplete list, so the install guard and numan doctor can miss an installed Numan channel or off-path binary. Change discovery to return a contexted anyhow::Result; for the install guard, fail closed or skip the guard only when the directory is absent. For doctor, report incomplete detection instead of treating the missing results as no multi-install finding. Also applies to install-guard/src/lib.rs:218-222.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install-guard/src/lib.rs` around lines 181 - 183, Update walk_for_numan and
the related discovery flow in discover_installations to propagate read_dir
failures as contextual anyhow::Result errors instead of returning an empty
traversal. Treat an absent directory as the only case that may skip the install
guard; otherwise fail closed, and ensure numan doctor reports incomplete
detection rather than interpreting missing results as no multi-install finding.

Source: Coding guidelines

Comment thread install-guard/src/lib.rs Outdated
Comment thread install-guard/src/lib.rs
Comment on lines +327 to +332
fn should_skip_guard() -> bool {
std::env::var("NUMAN_SKIP_INSTALL_GUARD")
.map(|v| v == "1")
.unwrap_or(false)
|| std::env::var("CI").ok().as_deref() == Some("true")
|| std::env::var("GITHUB_ACTIONS").ok().as_deref() == Some("true")

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Accept common truthy CI values.

CI=1 does not bypass the guard. If that job detects another install, Lines 269-275 cancel the non-interactive installation. Accept 1 and case-insensitive true for CI flags.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install-guard/src/lib.rs` around lines 327 - 332, Update should_skip_guard to
treat CI and GITHUB_ACTIONS as enabled when their values are either "1" or
case-insensitive "true", while preserving the existing NUMAN_SKIP_INSTALL_GUARD
behavior and guard bypass logic.

Source: MCP tools

Comment on lines +354 to +380
fn uninstall_channel(channel: InstallChannel) -> bool {
match channel {
InstallChannel::Cargo => run_command("cargo", &["uninstall", "numan-cli"]),
InstallChannel::Winget => run_command(
"winget",
&[
"uninstall",
"--id",
"tonythethompson.numan",
"--exact",
"--accept-source-agreements",
],
),
InstallChannel::Homebrew => run_command("brew", &["uninstall", "numan"]),
InstallChannel::ReleaseArchive | InstallChannel::Unknown => false,
}
}

fn run_command(program: &str, args: &[&str]) -> bool {
Command::new(program)
.args(args)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.map(|status| status.success())
.unwrap_or(false)

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file locations =="
git ls-files | rg '(^|/)install-guard/src/lib\.rs$|install-guard|Root|acquire_mutation_lock|lockfile|numan-cli|winget|homebrew' || true

echo
echo "== install-guard outline =="
ast-grep outline install-guard/src/lib.rs --view compact || true

echo
echo "== relevant install-guard section =="
sed -n '1,460p' install-guard/src/lib.rs

echo
echo "== search acquire_mutation_lock in install-guard =="
rg -n "acquire_mutation_lock|snapshot|lockfile|root" install-guard -S || true

echo
echo "== search mutation helpers repo-wide =="
rg -n "acquire_mutation_lock|write_json_atomic|snapshot" . -S | head -200 || true

Repository: tonythethompson/numan

Length of output: 40422


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== install-guard Cargo.toml =="
cat -n install-guard/Cargo.toml

echo
echo "== main entrypoints =="
sed -n '1,240p' install-guard/src/main.rs

echo
echo "== mutation_lock implementation =="
rg -n -C 5 "pub fn acquire_mutation_lock|fn acquire_mutation_lock|struct MutationLock|impl MutationLock|create_snapshot" src -S

echo
echo "== fs_safety outline/locations =="
fd -a 'fs_safety\.rs|lockfile\.rs|snapshot' src | sed 's#^\./##'
rg -n "use crate::util::fs_safety|acquire_mutation_lock|Lockfile::snapshot|write_json_atomic" src/install src/util src/state src/nupm_compat docs -S

echo
echo "== precise call sites for Lockfile snapshot methods =="
rg -n -C 3 "Lockfile::snapshot\(|\.snapshot\(|create_snapshot\(" src -S

Repository: tonythethompson/numan

Length of output: 50379


Serialize the guard’s uninstall path behind the root mutation lock.

numan-install-guard now runs cargo uninstall, winget uninstall, or brew uninstall inside run_install_guard. Concurrent guard runs can detect the same existing install and start these package-manager mutations concurrently. Acquire the installation-root mutation lock and create a pre-mutation snapshot before uninstalling; if the standalone guard cannot determine an authoritative root/snapshot, stop before mutation and print the appropriate uninstall command.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install-guard/src/lib.rs` around lines 354 - 380, Update the uninstall flow
in run_install_guard and uninstall_channel so package-manager mutations are
serialized under the installation-root mutation lock. Resolve an authoritative
root and create the pre-mutation snapshot before invoking run_command; if the
standalone guard cannot obtain either, do not uninstall and instead print the
appropriate uninstall command. Ensure the lock and snapshot cover the entire
cargo, winget, or brew uninstall operation.

Source: Coding guidelines

Comment on lines +414 to +433
fn conflicting_installs_excludes_paths() {
let excluded = PathBuf::from("/tmp/excluded/numan");
let installs = vec![
DiscoveredInstall {
path: excluded.clone(),
channel: InstallChannel::Winget,
},
DiscoveredInstall {
path: PathBuf::from("/tmp/other/numan"),
channel: InstallChannel::Cargo,
},
];
let filtered = installs
.into_iter()
.filter(|install| !std::slice::from_ref(&excluded).contains(&install.path))
.filter(|install| install.channel != InstallChannel::Winget)
.collect::<Vec<_>>();
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].channel, InstallChannel::Cargo);
}

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.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Test conflicting_installs through an injectable discovery seam.

This test duplicates the filter predicates locally. It never calls conflicting_installs, so it cannot detect a regression in that function.

Inject installation discovery or candidate roots. Test target exclusion, same-channel filtering, unreadable directories, and non-interactive conflict cancellation.

As per coding guidelines, “Add or update tests for behavior changes, including relevant failure paths.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install-guard/src/lib.rs` around lines 414 - 433, Replace the local filtering
in conflicting_installs_excludes_paths with a call to the production
conflicting_installs function through an injectable discovery or candidate-roots
seam. Extend the tests to cover excluded targets, same-channel installs,
unreadable directories, and cancellation in non-interactive mode, ensuring each
assertion exercises the real conflicting_installs behavior.

Source: Coding guidelines

Comment thread src/bin/install_guard.rs
Comment on lines +8 to +18
fn main() -> ExitCode {
match env::args().nth(1).map(|s| s.to_ascii_lowercase()) {
Some(arg) if arg == "cargo" => run_cargo_install_guard(),
Some(arg) if arg == "winget" => run_winget_install_guard(),
Some(arg) if arg == "brew" || arg == "homebrew" => run_homebrew_install_guard(),
_ => {
eprintln!("usage: numan-install-guard <cargo|winget|brew>");
ExitCode::from(2)
}
}
}

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
fd -a 'Cargo.toml|main.rs' . | sed 's#^\./##'

echo
echo "== install-guard Cargo.toml =="
cat -n install-guard/Cargo.toml

echo
echo "== install-guard main.rs =="
cat -n install-guard/src/main.rs

echo
echo "== clap usages in repo =="
rg -n "use clap|clap::|Command|Subcommand" --glob '*.rs' .

echo
echo "== cargo dependencies containing clap =="
rg -n "clap\s*=|dependencies" Cargo.toml install-guard/Cargo.toml

Repository: tonythethompson/numan

Length of output: 15778


🏁 Script executed (no clone):

#!/bin/bash
set -euo pipefail

echo "== crates.io clap 4.6.5 derive feature metadata =="
curl -s https://crates.io/api/v1/crates/clap/4.6.5 --fail | jq '{name, version, features}'

echo
echo "== crates.io clap 4.6.5 Cargo.toml features =="
curl -sL https://crates.io/api/v1/crates/clap/4.6.5/download | tar -xOzf - clap-4.6.5/Cargo.toml | sed -n '/\[features\]/,/^$/p'

Length of output: 178


🌐 Web query:

clap Rust derive feature version 4 documentation

💡 Result:

To use the derive API in clap version 4, you must enable the derive feature flag [1][2]. You can enable this feature by adding clap to your Cargo.toml with the derive feature enabled [2]: cargo add clap --features derive Once enabled, the derive API allows you to define your command-line interface using structs, enums, and derive macros such as #[derive(Parser)], #[derive(Args)], #[derive(Subcommand)], and #[derive(ValueEnum)] [1][3]. The derive feature works by depending on the clap_derive crate [4]. You can refer to the official clap documentation, specifically the Derive Reference and the Derive Tutorial, for detailed usage examples and configuration options [1][2][5].

Citations:


Use clap derives for the install guard CLI.

install-guard/src/main.rs manually matches env::args().nth(1), and install-guard/Cargo.toml has no CLI dependencies. Move this command into install-guard/src/cli.rs with #[derive(Parser)]/Subcommand, add clap = "4", and call it from main. No change should mutate install-guard/Cargo.lock.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install-guard/src/main.rs` around lines 8 - 18, Replace the manual argument
matching in main with a clap-based CLI module: add install-guard/src/cli.rs
defining Parser and Subcommand-derived types for cargo, winget, and
brew/homebrew commands, add the clap 4 dependency to Cargo.toml, and have main
parse and dispatch the CLI values. Preserve the existing usage/error behavior
where applicable, and do not modify Cargo.lock.

Source: Coding guidelines

Comment thread scripts/install-cargo.ps1
[CmdletBinding()]
param(
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$CargoArgs = @("install", "--path", ".")

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Resolve the default Cargo path from the script directory.

$repoRoot uses the script location, but the default --path . uses the caller’s current directory. If a user invokes this wrapper outside the checkout, Cargo installs the wrong directory or fails to find a manifest. Set the default path after $repoRoot is known.

Proposed fix
 $repoRoot = Split-Path -Parent $PSScriptRoot
 $guardManifest = Join-Path $repoRoot "install-guard\Cargo.toml"
+
+if (-not $PSBoundParameters.ContainsKey("CargoArgs")) {
+    $CargoArgs = @("install", "--path", $repoRoot)
+}

Also applies to: 18-19, 31-32

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/install-cargo.ps1` at line 13, Update the install-cargo wrapper so
the default Cargo --path is derived from the script’s resolved repository root
instead of the caller’s current directory. In the install flow around $repoRoot
and $CargoArgs, set or rewrite the default path after $repoRoot is established
so cargo install always targets the checkout the script belongs to, while
preserving any explicit path overrides.

- Format install guard sources
- Remove install guard bypass flag
- Use PATH-only doctor scanning
@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo Fixer

✅ Committed (4) · ☑ Fixed (4)

Grey Divider

Commits pushed directly to this PR — no separate fix PR opened.

Process — 4 fixed
  • ☑ Fixed: Rustfmt gate will fail
  • ☑ Fixed: Crate packaging will fail
  • ☑ Fixed: NUMAN_SKIP_INSTALL_GUARD feature flag
  • ☑ Fixed: Doctor scans package directories
  • ⏭ Skipped (1)

Move install-channel logic to src/util/install_channel.rs shared by build.rs, doctor, and the numan-install-guard binary. Removes the path-dependency install-guard crate so cargo package and cargo deny succeed.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
src/cmd/doctor.rs (2)

346-354: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use channel-neutral wording in the diagnostic.

InstallChannel includes ReleaseArchive and Unknown, but Line 347 says “different package managers.” The warning can therefore describe a release archive or unknown binary as a package manager. Replace that phrase with “different installation channels” to match the install.multiple_channels finding and the labels below.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cmd/doctor.rs` around lines 346 - 354, Update the diagnostic text in the
`install.multiple_channels` handling within `doctor.rs` so the first line no
longer says “different package managers” and instead uses channel-neutral
wording like “different installation channels.” Keep the existing loop over
`installs` and `install.channel.label()` output unchanged so the message stays
aligned with `InstallChannel` values such as `ReleaseArchive` and `Unknown`.

340-369: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not generate uninstall hints when the current channel is unknown.

current_exe().ok() maps lookup failures to InstallChannel::Unknown, and classify_binary_path can also return Unknown. Lines 365-369 then treat every known channel as an installation from another channel. If the running executable belongs to one of those channels, the manual fix can recommend uninstalling the active installation.

Keep the warning, but leave fix unset until the current executable is matched to a known channel. Add regression tests for lookup failure and unknown classification.

As per coding guidelines, Rust error paths should return anyhow::Result with context.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cmd/doctor.rs` around lines 340 - 369, Update the warning assembly in
doctor.rs so `fix` is only populated when `current_exe()` resolves to a known
install channel via `classify_binary_path`; if `current_channel` is
`InstallChannel::Unknown`, leave `fix` unset and keep the warning text. Adjust
the `fix` filter logic around `installs`/`uninstall_hint` to skip uninstall
recommendations until the running executable is positively matched to a known
channel. If you touch the executable lookup path, return `anyhow::Result` with
context instead of silently converting failures to `Unknown`. Add regression
coverage for both `current_exe()` lookup failure and `classify_binary_path`
returning `Unknown`.

Source: Coding guidelines

install-guard/src/lib.rs (4)

381-432: 📐 Maintainability & Code Quality | 🔵 Trivial

Run the required Rust gates before declaring this ready.

Run cargo test, cargo clippy -- -D warnings, and cargo fmt --check. Validate the guarded installer flows on Ubuntu, Windows, and macOS before release.

As per coding guidelines, all Rust CI gates must pass: cargo test, cargo clippy -- -D warnings, and cargo fmt --check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install-guard/src/lib.rs` around lines 381 - 432, Before considering the
installer changes complete, run the Rust validation gates: cargo test, cargo
clippy -- -D warnings, and cargo fmt --check. Resolve any failures, then
validate the guarded installer flows on Ubuntu, Windows, and macOS.

Source: Coding guidelines


307-321: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Preflight manual-only conflicts before mutating state.

ReleaseArchive and Unknown have no automatic uninstall hint, but the loop still attempts every conflict. If a supported channel appears before one of these channels, the guard removes the supported installation and then cancels on the unsupported one. The user loses an installation and the requested installation does not proceed.

Reject manual-only conflicts before running any uninstall command. Print the path and manual action first.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install-guard/src/lib.rs` around lines 307 - 321, Before the uninstall loop,
preflight all conflicts whose channels lack an automatic uninstall action or
hint, such as ReleaseArchive and Unknown; print each affected path and the
required manual action, then return ExitCode::from(1) without mutating state.
Only proceed to the existing uninstall_channel loop when every conflict is
automatically removable.

307-321: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Serialize uninstall mutations and snapshot before writes.

After confirmation, Line 308 starts package-manager removal without acquire_mutation_lock(root) or a pre-mutation lockfile snapshot. Two guard runs can act on the same stale discovery result and mutate installation state concurrently. The check at Line 321 does not repair that race.

Resolve the authoritative root before mutation. Acquire the mutation lock and create the snapshot before the first uninstall. Hold the lock through the complete uninstall sequence. If the standalone guard cannot obtain these, stop before mutation and print the manual command.

As per coding guidelines, every mutating command must call acquire_mutation_lock(root), and new mutating paths must snapshot the lockfile before writes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install-guard/src/lib.rs` around lines 307 - 321, Before the uninstall loop
in the guard flow, resolve the authoritative root, acquire_mutation_lock(root),
and create the required pre-mutation lockfile snapshot; abort before any
uninstall and print the manual command if either setup step fails. Hold the
acquired lock through the complete conflict uninstall sequence, including the
calls to uninstall_channel, and retain the existing failure handling.

Source: Coding guidelines


307-315: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Uninstall each detected copy instead of running channel-level uninstall commands again.

At src/util/install_channel.rs:319-339, the loop iterates by DiscoveredInstall, but uninstall_channel(conflict.channel) runs cargo uninstall numan-cli, winget, or brew without the discovered path. For Cargo with CARGO_INSTALL_ROOT set, this can uninstall a different home root; duplicate discovered numan binaries also re-run the same package-manager command. Pass the detected install identity into the uninstall routine and avoid re-running the same command for multiple paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install-guard/src/lib.rs` around lines 307 - 315, Update the conflict-removal
loop and its uninstall helper so each DiscoveredInstall is removed using its
detected path and install identity, rather than repeatedly invoking
channel-level commands through uninstall_channel. Ensure duplicate discoveries
for the same channel do not rerun the package-manager uninstall command, while
preserving the existing failure message and uninstall hint behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Cargo.toml`:
- Around line 23-26: Update the release pipeline around the numan-cli
publish/package flow to ensure numan-install-guard version 0.1.0 is published to
the registry before any CLI packaging or publish step runs. Keep the local
path-plus-version dependency in Cargo.toml for development, but make the release
ordering in the pipeline explicitly publish the guard first so the packaged
numan-cli can resolve numan-install-guard = 0.1.0 from the registry.

In `@install-guard/src/lib.rs`:
- Around line 84-93: Update discover_path_installations and its callers to
return or propagate a contexted error when path discovery cannot read an
existing root, rather than treating it as an empty result. In the underlying
push_if_numan scan, skip only roots that are genuinely missing; propagate all
other directory-read failures so the install guard and src/cmd/doctor.rs fail
closed instead of omitting cross-channel or install.multiple_channels findings.

---

Outside diff comments:
In `@install-guard/src/lib.rs`:
- Around line 381-432: Before considering the installer changes complete, run
the Rust validation gates: cargo test, cargo clippy -- -D warnings, and cargo
fmt --check. Resolve any failures, then validate the guarded installer flows on
Ubuntu, Windows, and macOS.
- Around line 307-321: Before the uninstall loop, preflight all conflicts whose
channels lack an automatic uninstall action or hint, such as ReleaseArchive and
Unknown; print each affected path and the required manual action, then return
ExitCode::from(1) without mutating state. Only proceed to the existing
uninstall_channel loop when every conflict is automatically removable.
- Around line 307-321: Before the uninstall loop in the guard flow, resolve the
authoritative root, acquire_mutation_lock(root), and create the required
pre-mutation lockfile snapshot; abort before any uninstall and print the manual
command if either setup step fails. Hold the acquired lock through the complete
conflict uninstall sequence, including the calls to uninstall_channel, and
retain the existing failure handling.
- Around line 307-315: Update the conflict-removal loop and its uninstall helper
so each DiscoveredInstall is removed using its detected path and install
identity, rather than repeatedly invoking channel-level commands through
uninstall_channel. Ensure duplicate discoveries for the same channel do not
rerun the package-manager uninstall command, while preserving the existing
failure message and uninstall hint behavior.

In `@src/cmd/doctor.rs`:
- Around line 346-354: Update the diagnostic text in the
`install.multiple_channels` handling within `doctor.rs` so the first line no
longer says “different package managers” and instead uses channel-neutral
wording like “different installation channels.” Keep the existing loop over
`installs` and `install.channel.label()` output unchanged so the message stays
aligned with `InstallChannel` values such as `ReleaseArchive` and `Unknown`.
- Around line 340-369: Update the warning assembly in doctor.rs so `fix` is only
populated when `current_exe()` resolves to a known install channel via
`classify_binary_path`; if `current_channel` is `InstallChannel::Unknown`, leave
`fix` unset and keep the warning text. Adjust the `fix` filter logic around
`installs`/`uninstall_hint` to skip uninstall recommendations until the running
executable is positively matched to a known channel. If you touch the executable
lookup path, return `anyhow::Result` with context instead of silently converting
failures to `Unknown`. Add regression coverage for both `current_exe()` lookup
failure and `classify_binary_path` returning `Unknown`.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 674101b8-99ed-4445-bb31-3b9358601311

📥 Commits

Reviewing files that changed from the base of the PR and between 4e0aa50 and a620eaa.

📒 Files selected for processing (4)
  • Cargo.toml
  • install-guard/Cargo.toml
  • install-guard/src/lib.rs
  • src/cmd/doctor.rs
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • tonythethompson/QuickShell (manual)
  • tonythethompson/numan (manual)
  • tonythethompson/dependency-chain-substrate (manual)
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: Greptile Review
  • GitHub Check: Real-Nu acceptance (windows-latest)
  • GitHub Check: Test (windows-latest)
  • GitHub Check: MSRV (1.88)
  • GitHub Check: Analyze (rust)
🧰 Additional context used
📓 Path-based instructions (10)
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

**/*: Use Serena's semantic, symbol-aware tools as the primary tools for reading and editing code; use built-in Read, Glob, Grep, and Edit only under the stated exceptions.
Before editing a code file, inspect its symbol overview, read the specific symbols being changed, and edit them with Serena's symbol-aware tools.
Understand the existing code before changing it and make the smallest change that satisfies the request; avoid unrelated cleanup, premature abstractions, unnecessary error handling, feature flags, and compatibility shims.
Prefer editing existing files over creating new ones, and never create Markdown or README files unless explicitly requested.
For exploratory questions, provide a 2–3 sentence recommendation with the main tradeoff and do not implement until the user agrees.
For UI or frontend changes that cannot be tested in a browser, explicitly state that browser testing was not performed rather than claiming success.
Address security issues when discovered, including injection, XSS, SQL injection, path traversal, and secret leaks.
Pause and obtain confirmation before destructive, hard-to-reverse, externally visible, or third-party-upload actions, including deleting files or branches, dropping tables, force-pushing, modifying CI/CD, posting externally, or uploading content.
When blocked, investigate the root cause instead of bypassing it with --no-verify, --force, or deletion; investigate unfamiliar files, branches, and configuration before deleting them.
Only commit when explicitly asked; do not proactively update git configuration or push changes.
Do not skip Git hooks unless explicitly asked; if a pre-commit hook fails, fix the issue, re-stage, and create a new commit rather than amending.
Stage files by name rather than using git add -A or git add ., to avoid accidentally including secrets or large binaries.
Use a HEREDOC for commit messages, and add co-author attribution only when the user explicitly requests the exact trailer.
Do not force-pus...

Files:

  • install-guard/Cargo.toml
  • Cargo.toml
  • src/cmd/doctor.rs
  • install-guard/src/lib.rs
!**/.env,!**/credentials.json,!**/*.pem

📄 CodeRabbit inference engine (CLAUDE.md)

Do not commit files that appear to contain secrets, including .env, credentials.json, and PEM files; warn before doing so even if explicitly requested.

Files:

  • install-guard/Cargo.toml
  • Cargo.toml
  • src/cmd/doctor.rs
  • install-guard/src/lib.rs
**/*.{rs,toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run and keep cargo fmt/rustfmt clean, and ensure cargo clippy -- -D warnings passes.

Files:

  • install-guard/Cargo.toml
  • Cargo.toml
  • src/cmd/doctor.rs
  • install-guard/src/lib.rs
**/*.{rs,md,toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use the repository's established serialization and module conventions rather than introducing unrelated refactors.

Files:

  • install-guard/Cargo.toml
  • Cargo.toml
  • src/cmd/doctor.rs
  • install-guard/src/lib.rs
Cargo.toml

📄 CodeRabbit inference engine (AGENTS.md)

Maintain MSRV Rust 1.88 via the crate’s rust-version setting.

Files:

  • Cargo.toml
**/*.{js,jsx,ts,tsx,py,java,go,rs,rb,php,c,cpp,h,hpp,cs,swift,kt,kts}

📄 CodeRabbit inference engine (CLAUDE.md)

Add comments only when the WHY is non-obvious; do not narrate what the code does, reference the current task, or reference the PR in comments.

Files:

  • src/cmd/doctor.rs
  • install-guard/src/lib.rs
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use the Rust 2021 edition.
Use anyhow::Result with .context(...) in application code; use thiserror for library error types that callers match on.
Use clap derive macros for CLI definitions.
Use serde with serde_json or toml for serialization.
Function parameters must use &Path, not &PathBuf.
Library code must not panic; error paths should return anyhow::Result with context where appropriate.
Add or update tests for behavior changes, including relevant failure paths.
New mutating code paths must acquire the mutation lock via acquire_mutation_lock(root) and snapshot the lockfile before writes.
Lockfile, journal, and state-file JSON writes must use write_json_atomic.
numan install must write only to $NUMAN_ROOT; it must not invoke Nu or register plugins/autoloads.
Only activate and deactivate may modify Nu integration state.
Treat the lockfile as the authoritative source of truth; derived projections such as autoload state must not be authoritative.
Install payloads under versioned, content-addressed paths and never overwrite them in place.
Never overwrite foreign autoload files; respect OWNERSHIP_MARKER.
Pass plugin paths through environment variables only; do not use runtime interpolation in Nu program strings.

**/*.rs: All CI gates must pass: cargo test, cargo clippy -- -D warnings, and cargo fmt --check.
Every mutating command—including install, remove, update, gc, and future nupm import—must call acquire_mutation_lock(root).
Lockfiles, journals, and state files must use write_json_atomic; partial writes are not allowed.
Pending activation, autoload, and lifecycle journals must be stored under $NUMAN_ROOT/state/.
Module autoload identity must match all four fields: Nu executable hash, Nu version, vendor autoload directory, and managed file path; the lockfile module_activation value is authoritative.
Never overwrite foreign autoload files; respect OWNERSHIP_MARKER.
Pass paths to Nu only throu...

Files:

  • src/cmd/doctor.rs
  • install-guard/src/lib.rs
**/*.{rs,nu}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.{rs,nu}: Real-Nu acceptance tests must be marked #[ignore] and should be run when changes affect activation or nupm import; unit tests must not spawn real nu and should use injectable seams such as FakeCandidateRunner or registrars.
The nupm integration must be read-only toward NUPM_HOME, must not execute build.nu, and must not perform bidirectional synchronization.

Unit tests must use FakeCandidateRunner or injectable registrars and must not spawn a real nu process.

Files:

  • src/cmd/doctor.rs
  • install-guard/src/lib.rs
**/*.{rs,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Match existing naming, module layout, and documentation level in the file being edited; update AGENTS.md, docs/, or command help when structure, conventions, or user-visible behavior changes.

Tests must cover failure modes, not only successful execution.

Files:

  • src/cmd/doctor.rs
  • install-guard/src/lib.rs
src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Use paths and names through environment variables (NUMAN_PLUGIN_BINARY, NUMAN_PLUGIN_CONFIG, NUMAN_PLUGIN_NAME); the Nu program string must be a compile-time constant with no runtime interpolation.

Files:

  • src/cmd/doctor.rs
🔇 Additional comments (12)
src/cmd/doctor.rs (4)

36-36: LGTM!


199-199: LGTM!


327-338: LGTM!


356-363: LGTM!

install-guard/src/lib.rs (5)

1-39: 📐 Maintainability & Code Quality

Verify that module comments explain a non-obvious WHY.

The current range summary does not show the module header or the comment at Line 10. A previous review flagged those comments as narrative. Remove them unless they explain a non-obvious design reason.

As per coding guidelines, add comments only when the WHY is non-obvious.

Source: Coding guidelines


381-432: 🎯 Functional Correctness

Verify that tests exercise production conflict filtering.

Confirm that conflicting_installs_excludes_paths calls conflicting_installs() through an injectable discovery or candidate-root seam. A local copy of the predicates can pass while production filtering regresses.

Add cases for excluded targets, same-channel filtering, unreadable roots, unsupported manual-only conflicts, and non-interactive cancellation.

As per coding guidelines, tests must cover behavior changes and relevant failure paths.

Source: Coding guidelines


41-81: LGTM!


235-264: LGTM!


272-281: 🎯 Functional Correctness

Verify the interactive Cargo path end to end.

build.rs invokes run_cargo_install_guard() during cargo install, but Line 275 refuses when standard input is not a terminal. Confirm that Cargo preserves the user's TTY for the build script. If it does not, every conflict cancels the installation and the documented prompt is unreachable.

install-guard/Cargo.toml (2)

7-11: LGTM!


5-5: 📐 Maintainability & Code Quality

No changes needed.

numan-install-guard is an [[bin]] in the root manifest, whose rust-version = "1.88" applies to both binary targets.

Cargo.toml (1)

79-82: LGTM!

Comment thread Cargo.toml Outdated
Comment on lines +23 to +26
numan-install-guard = { path = "install-guard", version = "0.1.0" }

[dependencies]
numan-install-guard = { path = "install-guard", version = "0.1.0" }

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.

🗄️ Data Integrity & Integration | 🔵 Trivial

Publish the guard before packaging numan-cli.

The path-plus-version dependency is suitable for local development, but a packaged numan-cli artifact requires numan-install-guard = 0.1.0 from the registry. Verify that the release pipeline publishes that exact guard version before the CLI package and publish stages. Otherwise the release pipeline can fail before producing the CLI artifact.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` around lines 23 - 26, Update the release pipeline around the
numan-cli publish/package flow to ensure numan-install-guard version 0.1.0 is
published to the registry before any CLI packaging or publish step runs. Keep
the local path-plus-version dependency in Cargo.toml for development, but make
the release ordering in the pipeline explicitly publish the guard first so the
packaged numan-cli can resolve numan-install-guard = 0.1.0 from the registry.

Comment on lines +84 to +93
pub fn discover_path_installations() -> Vec<DiscoveredInstall> {
let mut seen = HashSet::new();
let mut out = Vec::new();
for dir in path_directories() {
push_if_numan(&dir, &mut seen, &mut out);
}
out.sort_by(|a, b| a.path.cmp(&b.path));
out
}

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Do not treat unreadable discovery as a clean result.

The scan converts directory-read failures into “no matching binary”. This makes discover_path_installations() and the full discovery path indistinguishable from a complete empty scan. The install guard can continue while a cross-channel installation is hidden, and src/cmd/doctor.rs can omit install.multiple_channels.

Return a contexted error or an explicit incomplete result. Skip only missing roots. Fail closed for unreadable roots.

As per coding guidelines, error paths must return contexted results instead of hiding failures.

Also applies to: 181-183, 218-222

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install-guard/src/lib.rs` around lines 84 - 93, Update
discover_path_installations and its callers to return or propagate a contexted
error when path discovery cannot read an existing root, rather than treating it
as an empty result. In the underlying push_if_numan scan, skip only roots that
are genuinely missing; propagate all other directory-read failures so the
install guard and src/cmd/doctor.rs fail closed instead of omitting
cross-channel or install.multiple_channels findings.

Source: Coding guidelines

Detect cargo install via OUT_DIR cargo-install path when CARGO_INSTALL_ROOT is unset. Deduplicate doctor uninstall hints and fix docs capitalization.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant