From fa1c937d1bd51351abc6bdc50b4ad6c339719995 Mon Sep 17 00:00:00 2001 From: tonythethompson Date: Sun, 9 Aug 2026 23:43:15 -0700 Subject: [PATCH 1/4] feat(info): display commit-snapshot provenance (P1) VersionEntry gains an optional provenance field parsed from the registry index. numan info shows a note when a version's provenance is "commit-snapshot" ("built from a commit snapshot, not a tagged release"). Other fixture-only VersionEntry literals across tests/cmd updated for the new field. Ref: docs/plans/2026-08-09-intake-process-reform.md (P1) --- src/cmd/info.rs | 23 +++++++++++++++++++++++ src/cmd/search.rs | 1 + src/cmd/try_cmd.rs | 1 + src/core/package.rs | 27 +++++++++++++++++++++++++++ src/core/registry.rs | 1 + src/core/resolve.rs | 3 +++ tests/install_test.rs | 4 ++++ tests/support/active_update/mod.rs | 1 + 8 files changed, 61 insertions(+) diff --git a/src/cmd/info.rs b/src/cmd/info.rs index a6d27c8c..08faf52a 100644 --- a/src/cmd/info.rs +++ b/src/cmd/info.rs @@ -79,6 +79,10 @@ pub fn format_info(pkg: &Package, platform: &Platform, nu: Option<&NuVersion>) - )); } + if ver.provenance.as_deref() == Some("commit-snapshot") { + out.push_str(" note: built from a commit snapshot, not a tagged release\n"); + } + if let Some(ref source) = ver.source { out.push_str(&format!(" source git: {}\n", source.git)); out.push_str(&format!(" source rev: {}\n", source.rev)); @@ -161,6 +165,7 @@ mod tests { }), dependencies: BTreeMap::new(), activation: None, + provenance: None, }], } } @@ -195,6 +200,24 @@ mod tests { assert!(out.contains("cargo_name: nu_plugin_highlight"), "{out}"); } + #[test] + fn format_info_notes_commit_snapshot_provenance() { + let mut pkg = sample_plugin(false); + pkg.versions[0].provenance = Some("commit-snapshot".to_string()); + let out = format_info(&pkg, &linux_platform(), None); + assert!( + out.contains("built from a commit snapshot, not a tagged release"), + "{out}" + ); + } + + #[test] + fn format_info_omits_provenance_note_when_absent() { + let pkg = sample_plugin(false); + let out = format_info(&pkg, &linux_platform(), None); + assert!(!out.contains("commit snapshot"), "{out}"); + } + #[test] fn format_info_omits_source_lines_when_absent() { let pkg = sample_plugin(false); diff --git a/src/cmd/search.rs b/src/cmd/search.rs index ac3d9bb4..783d3046 100644 --- a/src/cmd/search.rs +++ b/src/cmd/search.rs @@ -251,6 +251,7 @@ mod tests { source: None, dependencies: BTreeMap::new(), activation: None, + provenance: None, }], } } diff --git a/src/cmd/try_cmd.rs b/src/cmd/try_cmd.rs index 18d60193..39f1bd7b 100644 --- a/src/cmd/try_cmd.rs +++ b/src/cmd/try_cmd.rs @@ -468,6 +468,7 @@ mod tests { source: None, dependencies: BTreeMap::new(), activation: None, + provenance: None, }], } } diff --git a/src/core/package.rs b/src/core/package.rs index baa44925..718f467e 100644 --- a/src/core/package.rs +++ b/src/core/package.rs @@ -112,6 +112,10 @@ pub struct VersionEntry { /// `None` for plugins, scripts, and completions. #[serde(default)] pub activation: Option, + /// Optional provenance marker, e.g. `"commit-snapshot"` for versions + /// built from a pinned commit with no upstream tag. + #[serde(default)] + pub provenance: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -269,6 +273,29 @@ mod tests { assert!(entry.source.is_none()); } + #[test] + fn parse_version_entry_defaults_provenance_to_none() { + let json = r#"{ + "version": "0.25.2", + "nu_version": ">=0.113.0 <0.114.0", + "artifact": { "kind": "binary", "targets": {} } + }"#; + let entry: VersionEntry = serde_json::from_str(json).unwrap(); + assert!(entry.provenance.is_none()); + } + + #[test] + fn parse_version_entry_with_commit_snapshot_provenance() { + let json = r#"{ + "version": "0.0.0-snapshot.20260809.5a1ca2a", + "nu_version": ">=0.114.0 <0.115.0", + "provenance": "commit-snapshot", + "artifact": { "kind": "binary", "targets": {} } + }"#; + let entry: VersionEntry = serde_json::from_str(json).unwrap(); + assert_eq!(entry.provenance.as_deref(), Some("commit-snapshot")); + } + #[test] fn parse_version_entry_with_source() { let json = r#"{ diff --git a/src/core/registry.rs b/src/core/registry.rs index a80ba0ab..41f362fe 100644 --- a/src/core/registry.rs +++ b/src/core/registry.rs @@ -406,6 +406,7 @@ mod tests { source: None, dependencies: BTreeMap::new(), activation: None, + provenance: None, }], }], } diff --git a/src/core/resolve.rs b/src/core/resolve.rs index 96d542c5..e1dcea22 100644 --- a/src/core/resolve.rs +++ b/src/core/resolve.rs @@ -726,6 +726,7 @@ mod tests { source: None, dependencies: BTreeMap::new(), activation: None, + provenance: None, }, VersionEntry { version: semver::Version::new(1, 0, 0), @@ -743,6 +744,7 @@ mod tests { source: None, dependencies: BTreeMap::new(), activation: None, + provenance: None, }, ], } @@ -1055,6 +1057,7 @@ mod tests { source: None, dependencies: BTreeMap::new(), activation: None, + provenance: None, }], } } diff --git a/tests/install_test.rs b/tests/install_test.rs index 07cb9680..50af71ab 100644 --- a/tests/install_test.rs +++ b/tests/install_test.rs @@ -137,6 +137,7 @@ fn integration_full_install_from_signed_registry() { source: None, dependencies: BTreeMap::new(), activation: None, + provenance: None, }], }; @@ -381,6 +382,7 @@ fn integration_resolve_exact_rejects_incompatible() { source: None, dependencies: BTreeMap::new(), activation: None, + provenance: None, }], }; @@ -463,6 +465,7 @@ fn integration_snapshot_before_install() { source: None, dependencies: BTreeMap::new(), activation: None, + provenance: None, }], }; @@ -533,6 +536,7 @@ fn integration_snapshot_before_install() { source: None, dependencies: BTreeMap::new(), activation: None, + provenance: None, }], }; diff --git a/tests/support/active_update/mod.rs b/tests/support/active_update/mod.rs index 1eed98d9..0e5aa745 100644 --- a/tests/support/active_update/mod.rs +++ b/tests/support/active_update/mod.rs @@ -506,6 +506,7 @@ fn write_dual_version_registry( source: None, dependencies: BTreeMap::new(), activation: None, + provenance: None, } }; From e1d28e5d693af1987bcffb0f913cec5a19ec3baa Mon Sep 17 00:00:00 2001 From: tonythethompson Date: Mon, 10 Aug 2026 01:44:02 -0700 Subject: [PATCH 2/4] test(info): assert non-commit-snapshot provenance omits snapshot note Cover the forward-compatible string contract so future provenance values do not accidentally trigger the commit-snapshot message. Co-authored-by: Cursor --- src/cmd/info.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/cmd/info.rs b/src/cmd/info.rs index 08faf52a..7972545c 100644 --- a/src/cmd/info.rs +++ b/src/cmd/info.rs @@ -218,6 +218,14 @@ mod tests { assert!(!out.contains("commit snapshot"), "{out}"); } + #[test] + fn format_info_omits_provenance_note_for_other_values() { + let mut pkg = sample_plugin(false); + pkg.versions[0].provenance = Some("tagged-release".to_string()); + let out = format_info(&pkg, &linux_platform(), None); + assert!(!out.contains("commit snapshot"), "{out}"); + } + #[test] fn format_info_omits_source_lines_when_absent() { let pkg = sample_plugin(false); From 2afc50eb08c6d0913d28e74d68c1cfd8a347ed77 Mon Sep 17 00:00:00 2001 From: tonythethompson Date: Mon, 10 Aug 2026 02:11:05 -0700 Subject: [PATCH 3/4] fix(completions): slim scripts and correct Nushell install hint Drop help subcommands from generated completion trees to cut script size, fix the invalid mkdir --all Nushell hint, update use command docs, and remind users to refresh completions after upgrading numan. Co-authored-by: Cursor --- src/cli.rs | 2 +- src/cmd/completions.rs | 50 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 7f8063e8..8c9168d9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -61,6 +61,6 @@ pub enum Commands { Setup(cmd::setup::SetupCommands), /// Try a package against your current Nu and platform, with compatibility guidance Try(cmd::try_cmd::TryArgs), - /// Switch the active managed Nu version (reserved, post-1.0) + /// Switch the active managed Nu version (`latest`, `list`, or a specific version) Use(cmd::use_cmd::UseArgs), } diff --git a/src/cmd/completions.rs b/src/cmd/completions.rs index 2564f158..1a16010c 100644 --- a/src/cmd/completions.rs +++ b/src/cmd/completions.rs @@ -51,6 +51,7 @@ pub fn execute(args: &CompletionsArgs) -> Result<()> { shell_label(args.shell), path.display() ); + println!("Re-run after upgrading numan to refresh tab completion."); if matches!(args.shell, CompletionShell::PowerShell) { println!( "Add to $PROFILE (once): . {}", @@ -186,21 +187,35 @@ numan completions powershell --print | Add-Content -Encoding utf8 $PROFILE .to_string(), CompletionShell::Nushell => "\ # Prefer: numan completions nushell -# Or manually: -mkdir --all ($nu.data-dir | path join vendor/autoload) +# Or manually (mkdir creates parent dirs by default): +mkdir ($nu.data-dir | path join vendor/autoload) numan completions nushell --print | save -f ($nu.data-dir | path join vendor/autoload/numan-completions.nu) " .to_string(), } } +/// Build the clap command tree used only for shell completion generation. +/// +/// Help subcommands are disabled recursively. `clap_complete` would otherwise +/// emit duplicate `numan help ` entries (often empty) that roughly double +/// script size without improving tab completion. +fn command_for_completions() -> clap::Command { + disable_help_subcommands_recursively(Cli::command()) +} + +fn disable_help_subcommands_recursively(cmd: clap::Command) -> clap::Command { + cmd.disable_help_subcommand(true) + .mut_subcommands(disable_help_subcommands_recursively) +} + /// Generate a completion script for `shell`. /// /// PowerShell output is rewritten so it can be appended to an existing /// `$PROFILE` that already contains statements (see /// [`make_powershell_profile_safe`]). pub fn generate_script(shell: CompletionShell) -> Result { - let mut cmd = Cli::command(); + let mut cmd = command_for_completions(); let mut buf = Vec::new(); match shell { CompletionShell::Bash => generate(Shell::Bash, &mut cmd, "numan", &mut buf), @@ -301,10 +316,14 @@ mod tests { assert!(print_hint(CompletionShell::Zsh).contains("mkdir -p ~/.zfunc")); assert!(print_hint(CompletionShell::Fish) .contains("mkdir -p \"${XDG_CONFIG_HOME:-$HOME/.config}/fish/completions\"")); + let nu_hint = print_hint(CompletionShell::Nushell); + assert!(nu_hint.contains("vendor/autoload/numan-completions.nu")); + assert!(nu_hint.contains("numan completions nushell --print")); assert!( - print_hint(CompletionShell::Nushell).contains("vendor/autoload/numan-completions.nu") + !nu_hint.contains("mkdir --all"), + "Nushell mkdir has no --all flag; it creates parents by default" ); - assert!(print_hint(CompletionShell::Nushell).contains("numan completions nushell --print")); + assert!(nu_hint.contains("mkdir ($nu.data-dir | path join vendor/autoload)")); } #[test] @@ -419,6 +438,27 @@ mod tests { !script.contains("vendor/autoload"), "hint must not be in script" ); + assert!( + !script.contains("numan help"), + "help subcommands must not bloat nushell completions" + ); + } + + #[test] + fn completions_omit_help_subcommands() { + for shell in [ + CompletionShell::Bash, + CompletionShell::Fish, + CompletionShell::Zsh, + CompletionShell::PowerShell, + CompletionShell::Nushell, + ] { + let script = generate_script(shell).expect("generate"); + assert!( + !script.contains("numan help"), + "{shell:?} completions must not include help subcommands" + ); + } } #[test] From 1ac335bd40c249fd224f4d35692a6bbaf0b4496c Mon Sep 17 00:00:00 2001 From: tonythethompson Date: Mon, 10 Aug 2026 02:47:02 -0700 Subject: [PATCH 4/4] fix(info): gate verified-upstream status for numan-maintained forks ADR 0001 treats maintained forks as distinct from verified upstream artifacts; format_info() was printing the verified-upstream status line unconditionally, contradicting the fork identity output. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/cmd/info.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cmd/info.rs b/src/cmd/info.rs index 7972545c..bda9c315 100644 --- a/src/cmd/info.rs +++ b/src/cmd/info.rs @@ -35,7 +35,11 @@ pub fn format_info(pkg: &Package, platform: &Platform, nu: Option<&NuVersion>) - } _ => {} } - out.push_str("Status: verified upstream artifact\n"); + if pkg.id.owner == "numan-maintained" { + out.push_str("Status: numan-maintained fork (not a verified upstream artifact)\n"); + } else { + out.push_str("Status: verified upstream artifact\n"); + } out.push_str(&format!("Description: {}\n", pkg.description)); out.push_str(&format!("Repository: {}\n", pkg.repo)); if !pkg.tags.is_empty() {