feat(skill-install): anc skill install <host> with hardened git clone + build-time host-map codegen - #35
Merged
Merged
Conversation
Adds typed error variants the skill install module will surface:
MissingHome (R6a — $HOME unset), GitNotFound, GitCloneFailed { code },
DestIsFile, DestNotEmpty, DestReadFailed. The first five map directly
to the JSON envelope's typed reason values
(home-not-set/git-not-found/git-clone-failed/destination-is-file/
destination-not-empty); DestReadFailed surfaces metadata-read failures
with path + source io::Error context.
Plan: docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md (U1).
src/skill_install.rs lands the static surface of the new subcommand:
- SkillHost enum (ClaudeCode/Codex/Cursor/Opencode) with clap ValueEnum
derive and rename_all = "snake_case" so surface names match
skill.json keys verbatim.
- KNOWN_HOSTS const exposed at module boundary (R-LIST seed for a
future `anc skill list` verb and shell-completion enumeration).
- SKILL_REPO_URL const — the canonical upstream URL for the bundle.
- GIT_HARDEN_FLAGS — five `-c key=value` pairs (R6c) defeating
ambient git config (credential.helper, core.askPass, protocol.allow,
http.followRedirects, url.<repo>.insteadOf).
- GIT_HARDEN_ENV_REMOVE — seven env vars stripped before spawn (never
env_clear() — it would strip PATH and break git's helper resolution).
- GIT_TERMINAL_PROMPT_{KEY,VALUE} — *set* on the spawned process so
git never prompts when credentials are missing.
- resolve_host pure function returning (url, dest_template).
- Module rustdoc carries the ASCII pipeline diagram (Mermaid lives in
the plan doc only — source comments stay ASCII per repo convention).
Tests 1 and 11 from the plan land in this commit, plus two
defense-in-depth tests: insteadOf= blocker references the same URL
resolve_host uses, and clap value names round-trip to KNOWN_HOSTS.
Plan: docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md (U1.3)
Adds the nested subcommand modeled on Commands::Generate { GenerateKind }:
Commands::Skill { cmd: SkillCmd }
SkillCmd::Install { host: SkillHost, dry_run: bool, output: OutputFormat }
The existing global Cli.quiet flag (`global = true`, env
AGENTNATIVE_QUIET) propagates automatically (D3) — no per-subcommand
wiring. main.rs dispatches via run_skill into skill_install::run_install,
which is a stub returning unimplemented!() until U1.6 lands the
pipeline. Clap's required-subcommand semantics give us test 22 for free
(bare `anc skill` prints help + exit 2 — the fork-bomb-safety
invariant).
Manual smoke (verified):
- anc skill -> help, exit 2
- anc skill install -> 'required argument <HOST>', exit 2
- anc skill install bogus_host -> 'invalid value', exit 2,
possible values listed
- anc skill install --help -> help text including manual git clone
fallback (F5)
Plan: docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md (U1.2)
Adds the two pure functions that fail-fast before `git clone` runs: - expand_tilde / expand_tilde_with — D1 passthrough contract. Inputs starting with `~` or `~/` resolve against $HOME; non-tilde inputs pass through unchanged regardless of $HOME. MissingHome only fires when the input actually begins with `~` and $HOME is unset/empty — non-tilde inputs never read the environment. Pure helper takes home as Option<&str> so tests don't mutate process env (which would race with parallel test runs). - check_destination — R9 conflict check. fs::canonicalize before the metadata read defends F4 (symlinked skills directory resolves to its real target before the check). Returns Absent / EmptyDir on success; errors with DestIsFile / DestNotEmpty / DestReadFailed on conflict. TOCTOU between check and clone is acknowledged residual risk; `git clone` itself errors on a non-empty target as backstop. - DestinationStatus enum (Absent / EmptyDir / NonEmptyDir / File) with as_envelope_str() for the JSON `destination_status` field. Conflict variants are inferred from the AppError variant in the caller. Tests 2-9 from the plan ship in this commit. Test 9 is unix-only — the `std::os::unix::fs::symlink` API is not portable. Adds tempfile to dev-dependencies; already a transitive dev-dep via insta. Plan: docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md (U1.4)
build_clone_command(url, dest) -> std::process::Command: - Applies GIT_HARDEN_FLAGS as -c key=value pairs *before* the `clone` subcommand — git's required position for top-level -c options. - Removes every GIT_HARDEN_ENV_REMOVE entry via env_remove. - Sets GIT_TERMINAL_PROMPT=0 so git never prompts. - Pure constructor: no spawn, no I/O. Test 10 introspects via get_args() / get_envs(). format_clone_command(url, dest) -> String: - User-visible single-line form for the JSON envelope's `command` field and `--dry-run --output text`. Intentionally omits hardening flags — implementation detail. Matches skill.json install commands verbatim once tildes are expanded, so users can copy-paste-modify it as a manual fallback. Test 10 from the plan ships in this commit, plus a sanity test on format_clone_command's canonical shape. Plan: docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md (U1.5)
Lands the orchestrator and the uniform JSON envelope (R-OUT, C1):
InstallEnvelope struct serializes to the schema in the plan, in field
order:
{action, host, mode, command, destination, destination_status,
status, would_succeed?, exit_code?, reason?}
skip_serializing_if = "Option::is_none" makes the three Option fields
follow plan field-presence rules:
- would_succeed only on dry-run path
- exit_code only on live install path (and only when we actually
spawned git; git-not-found leaves it absent)
- reason only on error paths, with typed values from the R-OUT
taxonomy (destination-not-empty / destination-is-file /
home-not-set / git-not-found / git-clone-failed)
compute_install_envelope() drives the full pipeline:
resolve_host -> expand_tilde -> [dry-run? -> emit] ->
check_destination -> spawn_git_clone
Each step's typed AppError variants map to envelope error cases with
the right typed reason; DestReadFailed propagates as AppError so
main.rs's top-level handler renders it on stderr with exit 2 (P4
internal-error reserved). MissingHome surfaces the unexpanded template
in the envelope's destination field so consumers see what would have
expanded.
emit_result_text returns the single-line `git clone …` form on
dry-run success (suitable for `eval $(anc skill install --dry-run
<host>)`), a short confirmation on live success, and `error: <reason>:
<path>` on failure.
emit_result_json pretty-prints via serde_json::to_string_pretty,
matching the existing anc check --output json style.
Manual smoke (verified):
- HOME=/home/test --dry-run claude_code (text + json) -> exit 0,
envelope shape matches plan example.
- HOME=/tmp/anc-smoke (with pre-populated dest) --dry-run -> exit
1, status=error, destination_status=non-empty-dir,
reason=destination-not-empty, would_succeed=false.
- HOME unset --dry-run --output json -> exit 1, status=error,
reason=home-not-set, destination shows the literal `~` template.
Plan: docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md (U1.6)
tests/fixtures/skill.json is a verbatim copy of the canonical site file at agentnative-site/src/data/skill.json. Tracked in git as the drift anchor between this binary's hardcoded host map and the authoritative site contract. Test-only — no production code path reads it. Test 12 (host_map_matches_site_skill_json) loads the fixture via include_str!() and asserts each Rust-map (url, dest_template) pair reconstructs the fixture's install.<host> command verbatim. This is the cargo-level companion to the script-based CI gate landing in U1.9 — a Rust-side drift check fails fast in `cargo test` before push, complementing scripts/sync-skill-fixture.sh --check on every PR (test 26). Plan: docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md (U1.7)
Spawns the real binary via assert_cmd::Command; introspects stdout
JSON envelope / stderr / exit code. Tests cover:
- 13/14: dry-run claude_code in text + json modes — single-line clone
command, success envelope shape (mode=dry-run, would_succeed=true).
- 15: dry-run with regular file at canonical dest — error envelope,
reason=destination-is-file, would_succeed=false, exit 1.
- 16b: `#[ignore]` end-to-end live clone from upstream. Excluded by
default; opt in via `cargo test -- --ignored` when vetting a
release. (16a is unit test 10 in src/skill_install.rs.)
- 17/18: clap surface — unknown host and missing positional both exit
2 with descriptive messages.
- 19: live install on populated dest — error envelope with
reason=destination-not-empty and exit_code absent (we never
spawned).
- 20: HOME unset — reason=home-not-set, destination shows the literal
`~` template.
- 21: PATH containing no git — reason=git-not-found, exit_code absent
(Unix only; Windows test infra would need different shape).
- 22: bare `anc skill` exits 2 + prints help — pins the
fork-bomb-safety invariant from CLAUDE.md ("Bare invocation prints
help"). Catches the regression where adding the subcommand drops
arg_required_else_help on the parent.
- 23: table-driven exit-code/reason matrix — pins the P4 exit-code
contract in one place rather than relying on per-error-path
assertions scattered across tests 15/19/20/21.
All 10 active integration tests pass; 1 ignored (live clone).
Plan: docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md (U1.8)
scripts/sync-skill-fixture.sh mirrors scripts/sync-spec.sh shape: remote-first (clones agentnative-site at SKILL_SITE_REF, default `dev` since the site uses a dev/main forever-branch flow), local fallback at $HOME/dev/agentnative-site. Two modes: scripts/sync-skill-fixture.sh # update fixture scripts/sync-skill-fixture.sh --check # CI drift gate (test 26) File-based comparison (cmp) preserves byte-for-byte parity including trailing newline boundaries — earlier variable-substitution capture ($(git show ...)) silently stripped trailing whitespace and produced phantom diffs. .github/workflows/skill-fixture-drift.yml runs the --check on every PR and on push to main/dev. Pinned to actions/checkout@<v6.0.2 SHA> per the brettdavies/.github canonical table. Companion to the cargo-level drift anchor (test 12 — host_map_matches_site_skill_json) which runs on `cargo test` without network. Manual smoke (verified): - bash scripts/sync-skill-fixture.sh --check -> ok, exit 0 - bash scripts/sync-skill-fixture.sh -> wrote fixture, no diff against committed - echo DRIFT >> fixture; --check -> error, diff shown, exit 1 Plan: docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md (U1.9)
Tests 24 and 25 from the plan — both CRITICAL. `anc check . --output
json` on this repo must show no `fail` status on p5-* (test 24) or
p2-* (test 25) for the new skill install verb to land.
Without these guards the dogfood claim that drove the binary-verb-
vs-bash-one-liner decision (Problem Frame: "Why a binary verb, not a
bash one-liner?") breaks silently. `anc` ships the agent-native CLI
standard and must score itself on P1-P6; the new `skill install` verb
adds --dry-run (P5) and --output {text,json} (P2) specifically to keep
that claim honest.
Tolerates warn; only fail breaks the guard. Surfaces the failing
check id + evidence for fast triage when a regression lands.
Plan: docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md (U1.10)
…AUDE rules README.md: new `## Install the skill` section with one-line examples per host, --dry-run inspection, JSON envelope mention, and the manual `git clone` fallback (F5) so users on a too-old anc see the escape hatch when the site adds a host between releases. RELEASES.md: insert step 7 in the dev→main release flow: `bash scripts/sync-skill-fixture.sh && git diff tests/fixtures/skill.json` to surface upstream drift before tagging. CI's skill-fixture-drift workflow already runs --check on every PR — this step pulls the latest content during release prep so we never ship a release with the hardcoded host map one revision behind. CLAUDE.md: new `## Skill Install Verb` section documenting the hardcoded-map model, the named-const hardening surface (GIT_HARDEN_FLAGS / GIT_HARDEN_ENV_REMOVE / GIT_TERMINAL_PROMPT=0 *set*), the two-layer drift detection (test 12 cargo-side, --check script CI-side), and the rules for future changes (never env_clear(), never sh -c, never reintroduce skill.json parsing, keep SkillHost/KNOWN_HOSTS/resolve_host in lockstep). Plan: docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md (U2)
Pre-merge manual smoke surfaced two bugs in the plan's hardening
surface — both invisible to introspection-only tests because they
only fail when an actual `git` binary parses the args.
1. `protocol.allow=https-only` is **not** valid git syntax. `git`
rejects it with `fatal: unknown value for config 'protocol.allow':
https-only`. The HTTPS-only intent is expressed as a default-deny
plus per-protocol allow:
-c protocol.allow=never -c protocol.https.allow=always
This is the documented git-config form for restricting transports.
2. `-c url.<repo>.insteadOf=` (empty value) does the **opposite** of
blocking. git's `url.<base>.insteadOf=<value>` rewrites URLs
starting with `<value>` to start with `<base>`. With an empty
value, every URL matches the empty prefix, so all URLs get
rewritten to start with `<base>` — doubling the clone URL into
`<repo><repo>/` which 404s on the remote. The actual defense
against `insteadOf` URL-rewriting is to disable user-controlled
git config entirely:
env: GIT_CONFIG_GLOBAL=/dev/null
GIT_CONFIG_SYSTEM=/dev/null
This blocks any `~/.gitconfig` or `/etc/gitconfig` insteadOf
stanza from firing. `env_remove` was insufficient — git falls
back to default config paths when the env var is unset.
Restructures the hardening into three named-const tables:
- GIT_HARDEN_FLAGS — five `-c` pairs (drop the broken insteadOf,
split protocol.allow into deny/allow pair).
- GIT_HARDEN_ENV_REMOVE — five env vars stripped (drop
GIT_CONFIG_{GLOBAL,SYSTEM} since we now `set` them).
- GIT_HARDEN_ENV_SET — three env vars set (the GIT_CONFIG_* pair plus
the existing GIT_TERMINAL_PROMPT=0).
Manual smoke (verified against actual git):
- HOME=/tmp/anc-skill-smoke anc skill install claude_code -> exit 0,
.git/HEAD points at refs/heads/main.
- Rerun -> envelope status=error, reason=destination-not-empty,
destination_status=non-empty-dir, exit 1 (R9 honored).
- cargo test -- --ignored skill_install -> live e2e clone passes.
Plan: docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md
(amends U1.5/U1.6 hardening surface to match git's actual config
syntax — the plan's R6c wording will need a follow-up note).
Updates the plan body to reflect the as-shipped hardening surface: - Overview: name GIT_HARDEN_ENV_SET (with the GIT_CONFIG_*=/dev/null pair) alongside GIT_HARDEN_FLAGS / GIT_HARDEN_ENV_REMOVE. - R6c: rewrite to three named-const tables (5 -c pairs / 5 env vars removed / 3 env-var pairs set). Note the two corrections vs the eng-review wording (protocol.allow=https-only is invalid syntax; url.<repo>.insteadOf= rewrites the wrong direction). - Implementation Units (U1 module shape): add GIT_HARDEN_ENV_SET to the const list; build_clone_command now applies all three tables. - Test 10: introspection test asserts every (key, value) pair in GIT_HARDEN_ENV_SET appears in the Command's env-set list. - Risks table: row reflects new table counts. - U2 docs note: CLAUDE.md content includes the GIT_HARDEN_ENV_SET triple. Adds a new `Document Review (implementation revision, 2026-04-30)` subsection capturing *why* each correction was needed, so future readers don't re-introduce the originals. Verification (cargo test, ignored e2e, manual smoke) is enumerated. The eng-review verdict and Plan Rewrite Brief below it are intentionally left unchanged — those are the audit trail of the SCOPE_REDUCED rewrite at that point in time; the body above is the authoritative as-shipped contract. Bumps frontmatter `deepened` to 2026-04-30.
… + kiro) Two changes that together prep for the build.rs codegen refactor in the next commit: 1. Move tests/fixtures/skill.json -> src/skill_install/skill.json. The fixture is about to become a build input for build.rs, but tests/ is in Cargo.toml's exclude list (so it ships nothing to crates.io). src/ is not excluded, so the new path is part of the cargo package and available at build time. The relocation appears as a 'new file' rather than a rename in the diff because git's similarity detector sees the simultaneous +2 host content update below the threshold; logically it's a move with one content tweak. 2. Re-vendor from agentnative-site/dev (which now contains the merged factory + kiro entries from agentnative-site#53). The fixture now advertises 6 hosts (was 4); CI's drift gate against upstream stays green. scripts/sync-skill-fixture.sh updates DEST_FILE to the new path and its leading comment explains the build-input role. The skill-fixture-drift.yml workflow runs the sync script unchanged — the path is internal to the script. Note: the Rust map in src/skill_install.rs still encodes the old 4-host list at this commit, so test 1 / test 11 / test 12 disagree with the fixture briefly — they would fail on cargo test. The next commit lands the codegen + replaces the Rust map and resolves all inconsistency in one motion. This intermediate commit is a test-failing checkpoint by design.
…nual sync) Replaces the hand-maintained Rust host map in src/skill_install.rs with build-time codegen from src/skill_install/skill.json. To add or change a host, edit the JSON (or run `bash scripts/sync-skill-fixture.sh`) and `cargo build` regenerates the map. No hand edit possible. build.rs::emit_skill_hosts: - Parses the manifest's install map. - Validates each command tokenizes as exactly `git clone --depth 1 <url> <dest>` (six tokens, dest not ending .git) — mirrors agentnative-site/src/build/skill.mjs validation so the two binaries reject the same malformed inputs. - Converts each snake_case JSON key to a PascalCase Rust identifier. - Emits $OUT_DIR/generated_hosts.rs with: SkillHost enum (with clap ValueEnum derive + rename_all = "snake_case"), KNOWN_HOSTS const, resolve_host fn, host_envelope_str fn. - Adds cargo:rerun-if-changed=src/skill_install/skill.json so the build cache invalidates on any JSON edit. src/skill_install.rs: - Drops hand-maintained SkillHost / KNOWN_HOSTS / resolve_host / host_envelope_str. Replaces with one include! line. - Drops SKILL_REPO_URL const. Tests that need it call resolve_host(SkillHost::ClaudeCode).0 — single source of truth. - Test 12 (host_map_matches_site_skill_json) deleted as provably redundant: both Rust map and fixture are now single-sourced from the same JSON; cargo's rerun-if-changed forces regeneration on any drift. A note in mod tests documents the deletion rationale. - Test 1 now drives off KNOWN_HOSTS so adding a host to skill.json automatically extends coverage. Cargo.toml: serde_json added to [build-dependencies]. CLAUDE.md, RELEASES.md, README.md: updated to describe codegen architecture and the new fixture path. README install section gets factory + kiro one-liners. docs/plans: plan body updated to reflect the as-shipped surface (build-time-derived map, src/skill_install/skill.json, no test 12). New `Document Review (codegen refactor, 2026-04-30)` subsection captures the rationale and verification — eng-review's hand-map proposal is left unchanged in the audit-trail sections below. Verification: - cargo test: 519 pass (one fewer than pre-codegen because test 12 was deleted), 1 ignored. - cargo clippy --all-targets -- -Dwarnings: clean. - bash scripts/sync-skill-fixture.sh --check: ok against agentnative-site dev (a2a07b6). - Manual smoke: HOME=/tmp/anc-factory-smoke anc skill install factory -> exit 0, .git/HEAD at ~/.factory/skills/agent-native-cli. Same shape verified for the 5 other hosts via --dry-run. `anc skill install --help` enumerates all 6 possible values.
U2 from the plan called for an audience-appropriate AGENTS.md update alongside CLAUDE.md; missed in the original docs commit. Adds: - `anc skill install` invocations to the Running anc example block (claude_code, codex with --dry-run, factory with --output json). - Bare `anc skill` exits 2 — extends the fork-bomb guard note. - New `## Skill install` section: 6-host roster, JSON envelope shape with field-presence rules, exit-code convention, hardening surface (GIT_HARDEN_FLAGS / GIT_HARDEN_ENV_REMOVE / GIT_HARDEN_ENV_SET), and the build.rs codegen architecture (host map auto-generated from src/skill_install/skill.json — no hand edits to skill_install.rs). AGENTS.md voice is a tier terser than CLAUDE.md: enough for an agent reading this to know what to invoke and what comes back; full prescriptive rules + rationale stay in CLAUDE.md.
Writes the follow-up plan named in the parent plan's Pattern Documentation Note: docs/plans/2026-04-30-001-feat-spec-output- envelope-shoulds-plan.md. Scope (Modest, 7 implementation units): - agentnative-spec: 4 new SHOULDs (3 P2, 1 P4) codifying the output-envelope contract anc skill install already enforces. Tag + version bump. - agentnative-cli: re-vendor spec, bump drift counters (registry_size_matches_spec 46→50; level_counts_match_spec Should 16→20), add 4 behavioral checks under src/checks/behavioral/, one per new SHOULD (SRP-style — 4 distinct check IDs, no bundled conformance check), update SUPPRESSION_TABLE (output-applies-to-every-subcommand fills the empty FileTraversal slot), extend tests/dogfood.rs with per-ID assertions, regenerate coverage matrix. Key decisions captured: - Behavioral layer for all four checks; error-path elicitation via the existing bad-arg injection pattern (bad_args.rs) combined with --output json. This combination is genuinely new ground — the first time anc probes an error path on a target binary. - output-applies-to-every-subcommand is Conditional on "CLI uses subcommands" (same gate as p6-must-global-flags). Other three are Universal. - output-envelope-schema-uniform is distinct from existing p2-should-consistent-envelope: the existing one addresses cross-command consistency; the new one addresses success-vs-error consistency within a single command. - p2-must-json-errors says "errors emitted as JSON to stderr" but anc's shipped envelope goes to stdout. The new SHOULD permits either stream during the reconciliation window; the MUST text reconciliation is deferred to a separate spec PR. Parent plan updated: the Pattern Documentation Note now lists the two follow-ups (this spec-SHOULDs plan = done; envelope solutions doc = pending /ce-compound after PR merge) instead of describing them as future work. Plan written via /ce-plan; user-confirmed synthesis on the scope before research dispatch. Research used ce-repo-research-analyst and ce-learnings-researcher in parallel; findings cited in the plan's Context & Research section. Not implementing today — this commit lands the plan only.
The Pattern Documentation Note's two follow-ups are now both resolved: 1. Spec follow-up plan — done (committed 7ddc0e0 on this branch). 2. Solutions doc — done (refreshed in place at solutions-docs/architecture-patterns/anc-cli-output-envelope- pattern-2026-04-29.md, commit 10fcb4b in solutions-docs). Both ACs in the parent plan now reference shipped artifacts. Solo updates remain on the parent plan: status: active -> completed at final shipping (Phase 4 of ce-work). That flip happens when the PR merges to dev.
Plan body refreshed to as-shipped reality and frontmatter flipped to status: completed (matches the convention from 2026-04-23-001 spec- vendor and 2026-04-29-001 scorecard-schema-metadata plans). Body changes: - Frontmatter: status active -> completed; added completed: 2026-04-30 and shipped_in: "PR #35". - New `## Acceptance Status (post-shipment, 2026-04-30)` section near the top with [x] checkboxes for every R-ID, U-ID, test scenario, and pre-merge checklist item. The body below remains the as-shipped contract; the Document Review subsections at the bottom preserve the audit trail of corrections that didn't make it to the final shape. - R2 variant list: 4 -> 6 (factory + kiro added via agentnative-site#53 before the codegen refactor). - Module shape SkillHost / KNOWN_HOSTS lists: 4 -> 6, annotated as build-generated. - Open Questions / Resolved During Planning: "Map source" updated from hardcoded constants to build-time codegen; "Drift handling" notes that fixture-vs-Rust-map drift is now structurally impossible; "Hosts at launch" updated to six. - Context & Research / Relevant Code and Patterns + Institutional Learnings: replaced tests/fixtures/ pattern reference with the spec- vendor src/principles/spec/ build-input pattern. - External References: dropped the "Rust map is hand-maintained to match" line; replaced with the codegen-from-vendored-fixture description. - System-Wide Impact / Interaction graph: added build.rs codegen hook, src/skill_install/skill.json, Cargo.toml dep changes, sync script, and CI workflow to the touched-files list. Pattern Documentation Note follow-ups (top-level visibility): - Spec-SHOULDs follow-up plan: docs/plans/2026-04-30-001-feat-spec- output-envelope-shoulds-plan.md (queued). - Solutions doc: refreshed in solutions-docs commit 10fcb4b. Squash merge to dev follows.
brettdavies
added a commit
that referenced
this pull request
May 1, 2026
) ## Summary The `git cherry HEAD origin/dev` check added in #45 is noisier than its comment implied. In a squash-merge workflow, every historical commit ever consolidated into a release squash shows as `+` forever — the squash commit's patch-id never matches the individual dev commits it absorbed. During v0.3.0 release prep this check produced **55 `+` lines**. All audited; zero real misses. Sources: - **Historical squash patch-id mismatch** — pre-v0.2.0 commits squashed into prior release tags - **Conflict-resolution drift** — e.g. `#35`'s cherry-pick stripped `docs/plans` files; same intent, different patch-id - **Intentional skips** — docs-only commits, the `abf1c6a` v0.2.0 backport, the `70fc42c` URL-revert prep step This PR expands the comment to (a) name the three expected noise sources, (b) define what a real miss actually looks like, and (c) give the two-command triage recipe (`git show --stat <sha>` then `git diff origin/main..HEAD -- <those-files>`). The check is now correctly framed as "review me, don't autoblock." Same edit applied to all 7 sibling brettdavies repos with `RELEASES.md` (working-tree only, no commits) — those land via separate per-repo PRs. ## Changelog ### Documentation - `RELEASES.md` § "Releasing dev to main" step 4 — expanded the `git cherry` patch-id check comment with squash-merge triage guidance (three expected noise sources, what a real miss looks like, and a two-command triage recipe). Discovered during v0.3.0 prep when the check produced 55 noisy `+` lines that all turned out to be expected; the original comment didn't explain that this is normal in a squash-merge workflow. ## Type of Change - [x] `docs`: Documentation update ## Related Issues/Stories - Story: discovered immediately after merging #45 — the new `git cherry` step produced 55 `+` lines on the v0.3.0 release branch, all of which audited as expected (squash-merge patch-id mismatch, conflict-resolution drift, intentional skips). The original comment didn't make this clear, so a future operator could read the output as a release-blocker when it isn't. - Issue: n/a — caught during release ceremony. - Architecture: `RELEASES.md` § "Releasing dev to main" step 4 — operator-facing flow, not runtime behavior. - Related PRs: builds on #45 (which introduced the `git cherry` check). This release (v0.3.0) cherry-picks this PR after merge so both the check and its triage guidance ship together. ## Testing - [ ] Unit tests added/updated - [ ] Integration tests added/updated - [x] Manual testing completed - [x] All tests passing **Test Summary:** - Unit tests: n/a — operator-facing markdown only - Integration tests: n/a - Coverage: n/a. Manual: ran `git cherry HEAD origin/dev | grep '^+'` against the v0.3.0 release branch live; output produced exactly the noise classes described in the new comment (historical squash + conflict-resolution + intentional skips). The triage recipe (`git show --stat <sha>` + `git diff origin/main..HEAD -- <files>`) was used to validate every flagged line; zero real misses. ## Files Modified **Modified:** - `RELEASES.md` — `git cherry` patch-id check comment expanded with squash-merge triage guidance. **Created:** - None. **Renamed:** - None. **Deleted:** - None. ## Breaking Changes - [x] No breaking changes Pure comment-prose expansion; the command is unchanged. ## Deployment Notes - [x] No special deployment steps required ## Checklist - [x] Code follows project conventions and style guidelines - [x] Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) - [x] Self-review of code completed - [x] Tests added/updated and passing - [x] No new warnings or errors introduced - [x] Changes are backward compatible (or breaking changes documented)
brettdavies
added a commit
that referenced
this pull request
May 1, 2026
) ## Summary The `git cherry HEAD origin/dev` check added in #45 is noisier than its comment implied. In a squash-merge workflow, every historical commit ever consolidated into a release squash shows as `+` forever — the squash commit's patch-id never matches the individual dev commits it absorbed. During v0.3.0 release prep this check produced **55 `+` lines**. All audited; zero real misses. Sources: - **Historical squash patch-id mismatch** — pre-v0.2.0 commits squashed into prior release tags - **Conflict-resolution drift** — e.g. `#35`'s cherry-pick stripped `docs/plans` files; same intent, different patch-id - **Intentional skips** — docs-only commits, the `abf1c6a` v0.2.0 backport, the `70fc42c` URL-revert prep step This PR expands the comment to (a) name the three expected noise sources, (b) define what a real miss actually looks like, and (c) give the two-command triage recipe (`git show --stat <sha>` then `git diff origin/main..HEAD -- <those-files>`). The check is now correctly framed as "review me, don't autoblock." Same edit applied to all 7 sibling brettdavies repos with `RELEASES.md` (working-tree only, no commits) — those land via separate per-repo PRs. ## Changelog ### Documentation - `RELEASES.md` § "Releasing dev to main" step 4 — expanded the `git cherry` patch-id check comment with squash-merge triage guidance (three expected noise sources, what a real miss looks like, and a two-command triage recipe). Discovered during v0.3.0 prep when the check produced 55 noisy `+` lines that all turned out to be expected; the original comment didn't explain that this is normal in a squash-merge workflow. ## Type of Change - [x] `docs`: Documentation update ## Related Issues/Stories - Story: discovered immediately after merging #45 — the new `git cherry` step produced 55 `+` lines on the v0.3.0 release branch, all of which audited as expected (squash-merge patch-id mismatch, conflict-resolution drift, intentional skips). The original comment didn't make this clear, so a future operator could read the output as a release-blocker when it isn't. - Issue: n/a — caught during release ceremony. - Architecture: `RELEASES.md` § "Releasing dev to main" step 4 — operator-facing flow, not runtime behavior. - Related PRs: builds on #45 (which introduced the `git cherry` check). This release (v0.3.0) cherry-picks this PR after merge so both the check and its triage guidance ship together. ## Testing - [ ] Unit tests added/updated - [ ] Integration tests added/updated - [x] Manual testing completed - [x] All tests passing **Test Summary:** - Unit tests: n/a — operator-facing markdown only - Integration tests: n/a - Coverage: n/a. Manual: ran `git cherry HEAD origin/dev | grep '^+'` against the v0.3.0 release branch live; output produced exactly the noise classes described in the new comment (historical squash + conflict-resolution + intentional skips). The triage recipe (`git show --stat <sha>` + `git diff origin/main..HEAD -- <files>`) was used to validate every flagged line; zero real misses. ## Files Modified **Modified:** - `RELEASES.md` — `git cherry` patch-id check comment expanded with squash-merge triage guidance. **Created:** - None. **Renamed:** - None. **Deleted:** - None. ## Breaking Changes - [x] No breaking changes Pure comment-prose expansion; the command is unchanged. ## Deployment Notes - [x] No special deployment steps required ## Checklist - [x] Code follows project conventions and style guidelines - [x] Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) - [x] Self-review of code completed - [x] Tests added/updated and passing - [x] No new warnings or errors introduced - [x] Changes are backward compatible (or breaking changes documented)
brettdavies
added a commit
that referenced
this pull request
May 1, 2026
## Summary v0.3.0 promotes 15 PRs from `dev` to `main`. Headline: scorecard schema bumps `0.3` → `0.5` (cumulative — `anc.commit` removed before tag), a new `anc skill install <host>` subcommand with hardened `git clone` + build-time host-map codegen, and a published agent-native badge surface (text-mode hint + JSON `badge` block) gated on a 80% eligibility floor. Plus PII fix on `target.path`, source-quality cleanup, README refresh, cross-repo sync map, and tooling-side hardening (PR template `**Renamed:**` subsection, `sync-spec` modernization, `sync-skill-fixture` drift gate, triple-diff release-flow check with squash-merge triage guidance). The release-prep also surfaced four corrections that came in via the canonical PR-to-dev flow during this ship: `#43` resynced the `skill.json` build-time fixture against upstream `agentnative-site/dev` (drift was blocking every open PR); `#42` corrected cross-repo URLs in `.github/ISSUE_TEMPLATE/` (broken since the v0.1.1 squash on `main`); `#45` replaced the release-flow's single-direction leak check with a triple-diff plus `git cherry` patch-id check (the latter caught the URL drift that motivated the change); `#46` added squash-merge-noise triage guidance to the `git cherry` step (the check produced 55 noisy `+` lines on first run, all expected, but the original comment didn't explain that). `#47` dropped the `anc.commit` field from the scorecard JSON before the v0.3.0 tag, since the `cargo:rerun-if-changed` `.git/` watches made cached builds fragile and `anc.version` is sufficient build identity. ## Changelog <!-- Per the changelog generation rules, the canonical changelog is in CHANGELOG.md. This release section reflects the same content; generate-changelog.sh extracts from each contributing PR's ## Changelog block. The bullets below are a curated highlight set for the PR body itself. --> ### Added - `anc skill install <host>` subcommand for six hosts (`claude_code`, `codex`, `cursor`, `factory`, `kiro`, `opencode`) with `--dry-run` and `--output {text,json}` flags and a uniform JSON envelope across success, error, and dry-run paths (#35). - Scorecard `--output json` self-describing metadata: four top-level blocks (`tool`, `anc`, `run`, `target`) + a `badge` block with `eligible`, `score_pct`, `embed_markdown`, `scorecard_url`, `badge_url`, `convention_url` (#34, #36). - `--output text` post-summary agent-native badge embed hint when the tool clears the 80% eligibility floor; "do not nag" rule below (#36). ### Changed - Scorecard `schema_version` bumped `0.3` → `0.4` → `0.5` (cumulative — `anc.commit` field removed before the v0.3.0 tag, see #47) (#34, #36). - `sync-spec.sh` modernized — remote-first tag resolution, `SPEC_REF` env override removed (#33). - `p7-naked-println` source check now exempts `build.rs` at any crate root (#38). - README refreshed for current state (schema 0.5, badge block, `--audit-profile`, basename `target.path`); `rust-version` bumped `1.87` → `1.88` (#34, #40). - `--output json` scorecard `anc` block no longer includes a `commit` field — `anc.version` is the build identity (#47). ### Fixed - `target.path` in `--output json` now emits the basename of the resolved target instead of the canonicalized absolute path — eliminates a home-dir / username PII leak that flowed into committed scorecards, badge URLs, and agent-posted artifacts (#39). - Eliminated four `.unwrap()` calls on infallible paths across `src/skill_install.rs` and `build.rs`, replaced with `.expect("…")` naming the contract (#38). - Cross-repo URLs in `.github/ISSUE_TEMPLATE/` corrected: spec → `agentnative`, site → `agentnative-site`, double-`cli` typo → `agentnative-cli` (#42). ### Documentation - `scripts/SYNCS.md` cross-repo sync map (#41). - `RELEASES.md` § "After publish — sync `dev` with the release" + new `sync-dev-after-release.sh` script formalizing the post-publish backport convention (#37). - `RELEASES.md` § "Releasing dev to main" step 4 replaced with a triple-diff verification block (A: main→release, B: release→dev, C: dev→main) plus a `git cherry` patch-id check (#45), with squash-merge-noise triage guidance added in #46. - README `## Install the skill` section + `[![agent-native]]` badge row (#35, #40). ## Type of Change - [x] `feat`: New feature (non-breaking change which adds functionality) - [x] `fix`: Bug fix (non-breaking change which fixes an issue) - [x] `chore`: Maintenance tasks (dependencies, config, etc.) - [x] `docs`: Documentation update ## Related Issues/Stories - Story: v0.3.0 release — twelve PRs across two days of work post-v0.2.0 plus four release-prep PRs (#42, #43, #45, #46) opened during the release ceremony to fix issues caught by the new triple-diff verification, plus #47 dropping `anc.commit` before the tag. - Issue: n/a - Architecture: scorecard schema evolution (`0.3 → 0.4 → 0.5`) covered in CLAUDE.md `## Scorecard v0.5 Fields`; skill-install architecture covered in CLAUDE.md `## Skill Install Verb`; release ceremony in `RELEASES.md`. - Related PRs: #30, #33, #34, #35, #36, #37, #38, #39, #40, #41, #42, #43, #45, #46, #47 — all merged to `dev` and cherry-picked here in chronological order. ## Testing - [x] Unit tests added/updated - [x] Integration tests added/updated - [x] Manual testing completed - [x] All tests passing **Test Summary:** - Unit tests: 535+ passing across all #34, #35, #36, #38, #39 PRs (counts varied as PRs landed). #47 simplified to ~520+ after dropping the `anc_commit_when_present_looks_like_short_sha` test. - Integration tests: full suite green at every cherry-pick step. New tests added: `tests/scorecard_metadata_security.rs` (red-team regressions for hostile binaries), `tests/scorecard_schema_v05.rs` (schema drift guard with `target.path` regression test), `tests/skill_install.rs` (10 active + 1 ignored e2e), `tests/dogfood.rs` (p2/p5 dogfood guards). - Coverage: pre-push hook (CI mirror) green at every step — fmt, clippy `-Dwarnings`, full test suite, cargo-deny, Windows compat (libc grep + cross-target clippy). - Triple-diff verification (the new step from #45) confirmed only `docs/plans/`, `docs/brainstorms/`, `docs/ideation/` (guarded paths) and the `docs/solutions` symlink remain on dev. Release-prep files (`CHANGELOG.md`, `Cargo.{toml,lock}`, `completions/*`) will backport to dev via `scripts/sync-dev-after-release.sh` post-publish per the convention from #37. The `git cherry` patch-id check produced 55 `+` lines on first run; all audited as expected (squash-merge patch-id mismatch + conflict-resolution drift + intentional skips), zero real misses — motivated #46's triage guidance. ## Files Modified **Modified:** - `Cargo.toml`, `Cargo.lock` — version `0.2.0 → 0.3.0`; `time = "=0.3.47"` added; `rust-version` bumped. - `CHANGELOG.md` — v0.3.0 release notes prepended. - `README.md`, `AGENTS.md`, `CLAUDE.md` — updated for schema 0.4/0.5, skill-install architecture, audit-profile, basename `target.path`, badge surface, `anc.commit` removal. - `RELEASES.md` — post-publish backport step (#37); skill-fixture sync step; step 4 replaced with triple-diff + `git cherry` check (#45); squash-merge triage guidance added (#46). - `build.rs` — `emit_skill_hosts` codegen, `ANC_VERSION` emission (no longer Git-SHA-aware after #47). - `src/build_info.rs` — `ANC_VERSION` re-export; `ANC_COMMIT` and its test removed in #47. - `src/main.rs`, `src/cli.rs`, `src/error.rs`, `src/scorecard/mod.rs`, `src/argv.rs` — schema 0.4/0.5 metadata, skill-install dispatch, badge computation, basename helper. `AncInfo` shrinks to one field after #47. - `src/checks/source/rust/naked_println.rs` — build.rs exemption. - `src/skill_install/skill.json` — fixture refresh (`source.commit` pin and `verify` block removed by upstream). - `scripts/sync-spec.sh` — remote-first, `SPEC_REF` removed. - `scripts/hooks/pre-push` — Windows cross-clippy step. - `.github/pull_request_template.md` — `**Renamed:**` sub-header. - `.github/ISSUE_TEMPLATE/{config,false-positive,feature-request,scoring-bug}.yml` — corrected cross-repo URLs. - `completions/anc.{bash,zsh,fish,elvish,powershell}` — regenerated. **Created:** - `src/output.rs`, `src/skill_install.rs`, `src/skill_install/skill.json`. - `tests/dogfood.rs`, `tests/scorecard_metadata_security.rs`, `tests/scorecard_schema_v05.rs`, `tests/skill_install.rs`, `tests/fixtures/hostile-{hang,nonzero-exit,stdout-flood}/probe.sh`. - `scripts/sync-skill-fixture.sh`, `scripts/sync-dev-after-release.sh`, `scripts/SYNCS.md`. - `.github/workflows/skill-fixture-drift.yml`. - `.github/ISSUE_TEMPLATE/00-blank.yml`. **Renamed:** - `tests/scorecard_schema_v04.rs` → `tests/scorecard_schema_v05.rs` (drift guard now covers `badge.*` keys). - `tests/fixtures/skill.json` → `src/skill_install/skill.json` (codegen needs to read from a path inside cargo's package). **Deleted:** - `.github/ISSUE_TEMPLATE/grade-a-cli.yml`, `pressure-test.yml`, `spec-question.yml` — duplicates of the spec repo's set; routed via `config.yml`. - `src/build_info.rs::ANC_COMMIT` const (and its test) — dropped before tag, see #47. ## Breaking Changes - [x] No breaking changes The scorecard `schema_version` bumps (`0.3` → `0.4` → `0.5`) are additive within the documented `0.x` pre-launch policy. Pre-`0.4` consumers feature-detect the four metadata blocks; pre-`0.5` consumers feature-detect the `badge` key. `target.path` value semantics changed (basename instead of absolute path) — schema shape is unchanged, so consumers reading the field at all will continue to parse it; consumers using it for cross-host correlation should already have migrated to `tool.name`. `anc.commit` was added in 0.4 but removed before the v0.3.0 tag (#47); no public consumer of the field exists pre-launch. ## Deployment Notes - [x] No special deployment steps required Standard pipeline: tag push triggers crates.io publish (Trusted Publishing, OIDC), GitHub Release with all 5 platform archives + sha256sums, then dispatch to `brettdavies/homebrew-tap` for formula update. After `finalize-release.yml` flips `make_latest: true`, run `./scripts/sync-dev-after-release.sh v0.3.0` to backport `Cargo.toml`, `Cargo.lock`, `CHANGELOG.md` to dev (new convention from #37). ## Checklist - [x] Code follows project conventions and style guidelines - [x] Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) - [x] Self-review of code completed - [x] Tests added/updated and passing - [x] No new warnings or errors introduced - [x] Changes are backward compatible (or breaking changes documented)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
anc skill install <host>: a single-verb subcommand that clones theagentnative-skillbundle into a host's canonical skills directory.Six hosts at v0.1 (
claude_code,codex,cursor,factory,kiro,opencode); the host map isbuild-time-generated by
build.rs::emit_skill_hostsfromsrc/skill_install/skill.json, so adding or changing ahost is a single-file edit (or
bash scripts/sync-skill-fixture.sh) followed bycargo build. No hand-maintainedRust map.
The verb closes
anc's own dogfood loop on P2 (--output {text,json}) and P5 (--dry-run). Thegit cloneinvocationruns with named-const hardening (
GIT_HARDEN_FLAGS,GIT_HARDEN_ENV_REMOVE,GIT_HARDEN_ENV_SET; the last includesGIT_CONFIG_GLOBAL=/dev/nullandGIT_CONFIG_SYSTEM=/dev/nullto disable user-controlled git config). JSON envelope isuniform across success and error and across both modes.
Changelog
Added
anc skill install <host>subcommand to install theagentnative-skillbundle into a host's canonical skills directory. Six hosts:claude_code,codex,cursor,factory,kiro,opencode.--dry-runflag (P5): prints the resolvedgit clonecommand without spawning. Captures cleanly viaeval $(anc skill install --dry-run <host>).--output {text,json}flag (P2): JSON envelope is uniform across success and error and across dry-run / live install. Typedreasonon error (destination-not-empty,destination-is-file,home-not-set,git-not-found,git-clone-failed).Documentation
## Install the skillsection to README with one-line examples per host and the manualgit clonefallback for hosts not yet in the binary's map.Type of Change
feat: New feature (non-breaking change which adds functionality)Related Issues/Stories
docs/plans/2026-04-29-002-feat-skill-subcommand-plan.mddocs/plans/2026-04-30-001-feat-spec-output-envelope-shoulds-plan.md. Proposes 4 new SHOULDs toagentnative-speccodifying the envelope contract this PR ships.docs/solutions/architecture-patterns/anc-cli-output-envelope-pattern-2026-04-29.md(committed insolutions-docs).factoryandkiroto the canonicalskill.json.Testing
Test Summary:
tests/skill_install.rsintegration: 10 active + 1#[ignore](live e2e clone; passes on opt-in run)tests/dogfood.rs: 2 pass (nop2-*/p5-*failagainst this repo)tests/integration.rs: 51 pass (existing P1–P7 surface, no regressions)Manual smoke (verified before merge):
--dry-run: each prints the canonicalgit clonecommand for its destination.HOME=/tmp/anc-{claude_code,codex,factory}-smoke anc skill install <host>→ exit 0,.git/HEADwritten, bundle files present.status: error,reason: destination-not-empty, exit 1 (R9 honored).HOMEunset → envelopereason: home-not-set, exit 1.PATHwithoutgit→ envelopereason: git-not-found,exit_codeabsent, exit 1.Local CI parity (pre-push hook): fmt, clippy
-Dwarnings, full test, cargo-deny audit, Windows compat. All green.Files Modified
Created:
src/skill_install.rs: module skeleton, hardening tables, orchestrator, envelope (~830 LOC including tests).src/skill_install/skill.json: vendored copy ofagentnative-site/src/data/skill.json; build-time codegen input.build.rsadditions:emit_skill_hostscodegen function (parses skill.json, emits$OUT_DIR/generated_hosts.rs).scripts/sync-skill-fixture.sh: remote-first vendor +--checkdrift mode (mirrorssync-spec.sh)..github/workflows/skill-fixture-drift.yml: CI drift gate (runs--checkon every PR).tests/skill_install.rs: 11 integration tests.tests/dogfood.rs: p2/p5 dogfood guards againstancitself (CRITICAL).docs/plans/2026-04-30-001-feat-spec-output-envelope-shoulds-plan.md: follow-up plan.Modified:
src/cli.rs:Commands::Skill { SkillCmd::Install }clap surface.src/main.rs:Commands::Skilldispatch arm.src/error.rs: 6 newAppErrorvariants (MissingHome,GitNotFound,GitCloneFailed{code},DestIsFile,DestNotEmpty,DestReadFailed).Cargo.toml:tempfiledev-dep,serde_jsonbuild-dep.README.md: install section.AGENTS.md,CLAUDE.md: skill-verb sections (codegen architecture, hardening surface).RELEASES.md: pre-release skill-fixture sync step.docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md: body refreshed to as-shipped reality; added twoDocument Review (... revision, 2026-04-30)subsections capturing the manual-smoke + codegen revisions.Renamed:
tests/fixtures/skill.json→src/skill_install/skill.json(move + content update;tests/is in cargoexclude, blocking build.rs read).Key Features
build.rs::emit_skill_hostsreadssrc/skill_install/skill.json, validates each install command tokenizes asgit clone --depth 1 <url> <dest>, and emitsSkillHostenum +KNOWN_HOSTS+resolve_host+host_envelope_strinto$OUT_DIR/generated_hosts.rs.cargo:rerun-if-changedinvalidates the build cache on JSON edits. Eliminates the Rust↔fixture drift class entirely (the original test 12 was deleted as provably redundant after the refactor).GIT_HARDEN_FLAGS(5-cpairs),GIT_HARDEN_ENV_REMOVE(5 vars),GIT_HARDEN_ENV_SET(3 pairs includingGIT_CONFIG_GLOBAL=/dev/nullandGIT_CONFIG_SYSTEM=/dev/null, the actual defense against user-configinsteadOfrewriting). Two manual-smoke bugs in the eng-review's R6c wording were caught and corrected before merge:protocol.allow=https-onlyis invalid git syntax (replaced with default-deny + per-protocol-allow);-c url.<repo>.insteadOf=rewrites in the wrong direction (dropped, defense routed through env-set).success/erroranddry-run/install.Optionfields use#[serde(skip_serializing_if = "Option::is_none")]so absence is silence, notnull. Field-presence rules:would_succeed(dry-run only);exit_code(live install only AND only when git was actually spawned);reason(error only)..github/workflows/skill-fixture-drift.ymlrunsscripts/sync-skill-fixture.sh --checkon every PR; clonesagentnative-siteandcmps the live blob against the committed fixture. Drift fails CI loudly, never users silently.tests/dogfood.rsrunsanc check . --output jsonon this repo and asserts nofailonp2-*orp5-*. Without these, the dogfood claim that drove the binary-verb-vs-bash-one-liner decision (Plan: Problem Frame § "Why a binary verb, not a bash one-liner?") would break silently.Benefits
anc skill install --output json --dry-run claude_codeemits a parseable envelope agents can branch on without regex; live install too.insteadOfcannot redirect the clone; HTTPS-only is enforced by default-deny + per-protocol-allow; credential/askpass paths are blocked; terminal prompts disabled.Document Review (revision)subsections capture why the manual-smoke and codegen revisions happened, so future readers don't re-litigate or re-introduce the originals.Breaking Changes
Deployment Notes
The pre-release checklist in
RELEASES.mdgains one step:bash scripts/sync-skill-fixture.sh && git diff src/skill_install/skill.jsonbefore tagging, to surface upstream site changes sincedevwas branched. The Rust map regenerates automatically on the nextcargo build.Checklist
Additional Context
agentnative-site#53addedfactoryandkiroto the canonicalskill.json. CI'sskill-fixture-driftworkflow on this PR is green againstagentnative-site/dev.docs/plans/2026-04-29-002-feat-skill-subcommand-plan.md. The plan went through/ce-doc-review,/plan-eng-review(SCOPE_REDUCED), Outside-Voice review, two implementation revisions (manual-smoke fix + codegen refactor), and final synthesis. The plan body documents the as-shipped contract; theDocument Reviewsections at the bottom preserve the audit trail of decisions that didn't make it to the final shipped form.architecture-patterns/anc-cli-output-envelope-pattern-2026-04-29.md(insolutions-docs) to fold the manual-smoke + codegen revisions into the canonical convention doc.status: active → completedhappens at squash-merge per the ce-work shipping convention.