diff --git a/RELEASES.md b/RELEASES.md index e9f7616..aff8cf3 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -66,9 +66,59 @@ git log --oneline dev --not origin/main # docs/brainstorms/, or docs/reviews/) stay on dev. git cherry-pick ... -# 4. Verify no guarded paths leaked through: -git diff origin/main --name-only | grep -E '^docs/(plans|solutions|brainstorms|reviews)/' && \ - echo 'FAIL: guarded docs in release branch' || echo 'OK: no guarded docs' +# 4. Triple-diff verification — belt-and-suspenders sweep that catches both +# directions of drift before the release tag goes out: +# +# A. main → release (what users will see; the intended ship surface) +# B. release → dev (should be empty for non-doc paths until the +# bump/CHANGELOG commits land, and even then should +# only list those release-prep files — anything else +# is a missed cherry-pick) +# C. dev → main (sanity: phantom commits dev "appears ahead" on +# because cherry-pick rewrites SHAs post-squash) +git diff origin/main..HEAD --stat # A +git diff HEAD..origin/dev --name-only | grep -v '^docs/' || echo "(none)" # B +git diff origin/dev..origin/main --stat | tail -5 # C +# +# Re-confirm no guarded paths leaked (this caught the original miss class): +git diff origin/main..HEAD --name-only \ + | grep -E '^(docs/plans|docs/brainstorms|docs/ideation|docs/reviews|docs/solutions|\.context)' \ + && echo "LEAKED — reset and redo" || echo "(clean — no guarded paths)" +# +# Patch-id cherry check — catches commits on dev that have NO patch-id +# equivalent on release. The file-level diff in B misses this class when +# the same content happens to land via a different commit. +# +# IMPORTANT: in a squash-merge workflow this output is noisy. Every '+' +# line needs human triage — it does NOT auto-block the release. Expected +# sources of '+' lines that are NOT real misses: +# +# 1. Historical commits squash-merged in prior releases. The squash +# commit on main has a different patch-id than the dev commits it +# consolidates, so old commits show as '+' forever. Anything older +# than the previous release tag is almost always this. +# 2. Cherry-picks where conflict resolution stripped guarded paths +# (docs/plans, docs/brainstorms, etc.) or otherwise altered the +# tree. Same source-code intent, different patch-id. +# 3. Intentionally skipped commits — docs-only commits, release-prep +# backports, revert-and-redo prep steps. +# +# A real miss looks like: a recent feat/fix/chore commit on dev whose +# *file content* is not yet on main. To triage a '+' line: +# +# git show --stat # what did it touch? +# git diff origin/main..HEAD -- # already on release? +# +# If every touched file is guarded (docs/plans/, docs/brainstorms/, etc.) +# OR the content is already on main via a prior squash, it's a false +# positive — no action. Otherwise cherry-pick the commit and re-run the +# triple-diff. +git cherry HEAD origin/dev | grep '^+' || echo "(none — release is patch-equivalent through dev)" +# +# If B lists any non-docs path you didn't expect, fetch dev, identify the +# commit (`git log dev --not origin/main`), cherry-pick it, re-run the +# triple-diff. Missed cherry-picks have shipped to main on this and sibling +# repos before — this step is the cheap way to catch them. # 5. Bump VERSION on the release branch. echo '' > VERSION diff --git a/SKILL.md b/SKILL.md index cd5340a..66f26cc 100644 --- a/SKILL.md +++ b/SKILL.md @@ -4,10 +4,15 @@ description: >- Guide to designing, building, and auditing CLI tools for use by AI agents. Pairs with [`anc`](https://github.com/brettdavies/agentnative-cli) (the canonical compliance checker) and [`agentnative-spec`](https://github.com/brettdavies/agentnative) (the canonical principle text, vendored at - `spec/`). Provides starter templates, language-specific implementation idioms, and a short - getting-started guide that points agents at `anc check --output json`. Use when designing a new CLI tool, - reviewing one for agent-readiness, or remediating findings from `anc`. Triggers on agentic CLI, agent-native, - CLI design, CLI standard, agent-first, CLI for agents, agent-friendly CLI, CLI compliance, anc. + `spec/`). Provides starter templates, language-specific implementation idioms (Rust/clap, Python Click & argparse, + Go Cobra, JS Commander/yargs/oclif, Ruby Thor), and a getting-started guide that points agents at + `anc check --output json` and `anc skill install `. Use when designing a new CLI tool, building a Rust/clap + binary intended for agents, reviewing one for agent-readiness, claiming the agent-native badge, or remediating + findings from `anc`. Triggers on agentic CLI, agent-native, CLI design, CLI standard, agent-first, CLI for agents, + agent-friendly CLI, CLI compliance, agent-readiness, anc, anc check, anc skill install, agent-native badge, + scorecard, audit-profile, Rust CLI, clap derive. SKIP when the user is building a TUI app meant for humans (use + `--audit-profile human-tui` rather than this skill), writing a non-CLI library, or asking unrelated Rust questions + not specifically about agent-readiness of a CLI. --- # Agent-Native CLI @@ -23,51 +28,27 @@ The standard for CLI tools designed to be operated by AI agents. Three artifacts The skill does **not** implement principles checking. `anc` does. The skill teaches agents to use `anc` and supplies the surrounding context (spec, idioms, templates) that `anc`'s findings reference. -## Update check +## First action: update check -On first invocation per session, run `bin/check-update`. It compares this bundle's `VERSION` against `main` on GitHub -and prints one of: - -| Output | Meaning | -| ------------------------------------ | ------------------------------------------------------------------------------- | -| (empty) | Up to date, snoozed, disabled, or check skipped (broken install, no network). | -| `UPGRADE_AVAILABLE ` | A newer release is on `main`. Surface the upgrade flow below before continuing. | +Run once per session before doing real work: ```bash bash "$(dirname "$0")/bin/check-update" ``` -Exit code is always 0; failures degrade silently. - -### Inline upgrade flow - -When stdout is `UPGRADE_AVAILABLE `, ask the user via `AskUserQuestion`: - -> `agent-native-cli` **v{remote}** is available (you're on v{local}). Upgrade now? - -Three options: - -- **"Yes, upgrade now"** — run `git -C pull --ff-only`. Report the new HEAD and the upgrade outcome. - The bundle root is the parent of ``; `git -C ../.. pull --ff-only` from `bin/` works for the default install layout - (`~//skills/agent-native-cli/`). If `--ff-only` rejects (uncommitted edits or divergent history), surface git's - error verbatim and stop — do not auto-stash. -- **"Not now"** — write `$HOME/.cache/agent-native-cli/update-snoozed` in the format ` `, where - `` is `1` (24h reminder), `2` (48h), or `3` (7 days), escalating each time the user defers. Tell the user the - next reminder window. -- **"Never ask again"** — `touch $HOME/.cache/agent-native-cli/disabled` and tell the user how to re-enable (`rm - $HOME/.cache/agent-native-cli/disabled`). - -State directory: `$HOME/.cache/agent-native-cli/`. All three files (`last-update-check`, `update-snoozed`, `disabled`) -live there; the script auto-creates the directory on first slow-path fetch. +Empty output → continue. `UPGRADE_AVAILABLE ` → prompt the user via `AskUserQuestion` (Yes / Not now / +Never). Full prompt text, snooze ladder, and state-file layout are in +[`references/update-check.md`](./references/update-check.md). Exit code is always `0`; failures degrade silently. ## Start here → **[`getting-started.md`](./getting-started.md)** — the three working loops (existing CLI / new Rust CLI / other -language), the canonical `anc check` invocations, and a "where things live" map. +language), the canonical `anc check` invocations, the `anc skill install ` installer, and a "where things live" +map. ## The seven principles -Defined in [`spec/principles/`](./spec/principles/) (vendored from `agentnative-spec` — currently `v0.2.0`; see +Defined in [`spec/principles/`](./spec/principles/) (vendored from `agentnative-spec` — currently `v0.3.0`; see [`spec/README.md`](./spec/README.md) for resync instructions). One file per principle, each with machine-readable `requirements[]` frontmatter: @@ -83,6 +64,38 @@ Defined in [`spec/principles/`](./spec/principles/) (vendored from `agentnative- Do not paraphrase the principles inside this skill — read the spec files directly. They are the source of truth. +## The anc loop: check → fix → re-check → claim badge + +Once `anc` is installed (one-line install in [`getting-started.md`](./getting-started.md)), the work is a four-step +loop: + +**1. Check.** `anc check --output json . > scorecard.json`. The JSON envelope is schema `0.5` and contains: + +- `summary` — `total / pass / warn / fail / skip / error` count. +- `coverage_summary` — `must / should / may`, each with `total` + `verified`. `must.verified == must.total` is the bar + for "no MUST violations". +- `badge.eligible` (bool), `badge.score_pct` (int), `badge.embed_markdown` (string or `null`), `badge.scorecard_url`, + `badge.badge_url`, `badge.convention_url`. **80%** is the eligibility floor; below it, `embed_markdown` is `null` and + the convention says do not advertise a badge. +- `results[]` — per-check entries citing `requirement_id`, `status`, and `evidence`. +- `audit_profile` — the exemption category in effect (or `null`). +- `tool / anc / run / target` metadata — identifies the scored tool, the `anc` build, the invocation, and the resolved + target. + +**2. Fix.** For each `fail`, look up the cited `requirement_id` (e.g. `p1-must-no-interactive`) in +`spec/principles/p-*.md`'s `requirements[]` frontmatter. Apply the fix using the implementation references below. +Re-run with `--principle ` to focus on one principle while iterating. + +**3. Re-check.** Re-run `anc check --output json .` until `summary.fail == 0` and `coverage_summary.must.verified == +coverage_summary.must.total`. Use `--audit-profile ` to suppress checks that don't apply to the tool class — +`human-tui` (TUIs that legitimately intercept the TTY), `file-traversal` (reserved), `posix-utility` (cat / sed / awk +style), `diagnostic-only` (read-only tools). Suppressed checks emit `Skip` with structured evidence so readers see what +was excluded. + +**4. Claim the badge.** Once `badge.eligible == true` (≥80%), copy `badge.embed_markdown` into the project's README. The +`text` output appends an embed hint after the summary line whenever the floor is cleared; below the floor, nothing +badge-related is printed (the convention's "do not nag" rule). + ## Implementation guidance (when fixing findings) Once `anc check` reports a failure, the agent has the cited `requirement_id` and the spec text. The next question is @@ -108,16 +121,23 @@ Drop-in starting points for greenfield Rust CLIs. Each encodes the relevant prin ## Compliance checking -Use `anc`. Install once: +Use `anc`. Install once via Homebrew, cargo, or self-install: ```bash brew install brettdavies/tap/agentnative # binary is `anc` cargo install agentnative ``` -Recommended invocations and the full agent loop are in [`getting-started.md`](./getting-started.md). Do not write shell -scripts to grep for principle violations — `anc` already implements (and supersedes) every check that approach could -produce. +To install **this skill bundle** into a host's canonical skills directory, use `anc`'s built-in installer: + +```bash +anc skill install claude_code # also: codex, cursor, factory, kiro, opencode +anc skill install --dry-run claude_code # print resolved git command without spawning +``` + +Recommended `anc check` invocations and the full agent loop are in [`getting-started.md`](./getting-started.md). Do not +write shell scripts to grep for principle violations — `anc` already implements (and supersedes) every check that +approach could produce. ## Sources diff --git a/docs/SYNCS.md b/docs/SYNCS.md new file mode 100644 index 0000000..efd90c5 --- /dev/null +++ b/docs/SYNCS.md @@ -0,0 +1,125 @@ +# Cross-repo sync map + +How spec content flows in and how the skill bundle flows out. Source of truth for the sync mechanisms that connect this +repo to its three sibling repos (`agentnative` spec, `agentnative-site`, `agentnative-cli`) and to consumer agent hosts. + +> This document is a routing map. Per-script details (env vars, fallback behavior, exit codes) live in the script +> headers. Re-vendor procedure during a release lives in [`RELEASES.md`](../RELEASES.md). Vendored-spec mechanics live +> in [`spec/README.md`](../spec/README.md). + +## Upstream — data flowing INTO this repo + +| Source | Mechanism | What's synced | Trigger / cadence | Drift check | +| -------------------------------------------------- | ------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `brettdavies/agentnative` (spec) @ latest `v*` tag | `scripts/sync-spec.sh` (manual) | `principles/p*-*.md` + `VERSION` + `CHANGELOG.md` → `spec/` | Re-run on the `release/v` branch as part of every release (see `RELEASES.md` §"Spec re-vendoring"). | None automated — relies on the release-branch checklist. The script itself is idempotent; `git status` after a re-run surfaces orphan files from spec renames. | + +**Mechanism notes:** + +- Resolution is **remote-first**: queries `https://github.com/brettdavies/agentnative.git` for the latest `v*` tag, + shallow-clones it into a temp dir, extracts files via `git show` so neither working tree is perturbed. +- Falls back to a local checkout (`$HOME/dev/agentnative-spec`, override with `SPEC_ROOT`) only when the remote query + fails. +- Mirror of `~/dev/agentnative-cli/scripts/sync-spec.sh` — only `DEST_DIR` differs. Keep them aligned when changing + either. + +## Inbound + outbound data map + +```mermaid +flowchart LR + subgraph Inbound + SPEC[brettdavies/agentnative
spec repo] + end + + SKILL[brettdavies/agentnative-skill
this repo
bundle + git tags] + + subgraph Outbound + CC[Claude Code
git clone] + CUR[Cursor
git clone] + CDX[Codex
git clone] + end + + SITE[brettdavies/agentnative-site] + + SPEC -- "scripts/sync-spec.sh
(MANUAL — NOT on spec's
repository_dispatch list)" --> SKILL + SKILL -- "release tag v<X.Y.Z>
= bundle pin" --> CC + SKILL -- "release tag v<X.Y.Z>
= bundle pin" --> CUR + SKILL -- "release tag v<X.Y.Z>
= bundle pin" --> CDX + SITE -. "git ls-remote (daily 13:00 UTC)
skill-availability.yml
visibility probe" .-> SKILL +``` + +This repo is intentionally **off** the spec's `repository_dispatch` list — re-vendoring is a deliberate release-time +act, not a webhook reflex. The site's daily probe is the only automated edge that touches this repo from outside, and +it's read-only (`git ls-remote`) — it never mutates state here. + +## Downstream — data flowing OUT of this repo + +This repo is **not** the source of truth for `skill.json`. The `agentnative-site` repo holds the canonical manifest at +`src/data/skill.json`; this repo's role downstream is (a) to be the git target that `skill.json`'s `source.url` points +at, and (b) to be the bundle that `install.` commands clone. + +### Manifest source-of-truth vs bundle content + +```mermaid +flowchart LR + subgraph ManifestPath[Manifest text — owned by agentnative-site] + SJSON[src/data/skill.json
SoT for manifest fields
incl. source.commit] + EMITTER[src/build/skill.mjs
validator + emitter] + ENDPOINT[/skill.json endpoint/] + SJSON --> EMITTER --> ENDPOINT + end + + subgraph BundlePath[Bundle content — owned by this repo] + BUNDLE[agentnative-skill
SKILL.md + spec/ + scripts/
+ git tags v<X.Y.Z>] + end + + SJSON -. "source.commit pins
a SHA from this repo
(hand-edited at release)" .-> BUNDLE +``` + +The two paths are independent artifacts that meet only at the `source.commit` pin. The site decides what the manifest +*says*; this repo decides what the bundle *is*. Editing `skill.json` here would be a category error — the field +ownership lives across the boundary in `agentnative-site/src/data/skill.json`. + +| Consumer | Mechanism | What's distributed | Trigger / cadence | Drift check | +| ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Agent hosts (Claude Code, Codex, Cursor, Factory, Kiro, OpenCode) | Plain `git clone --depth 1 https://github.com/brettdavies/agentnative-skill.git /agent-native-cli`. Update path is `git -C pull --ff-only` triggered inline by `bin/check-update`'s `UPGRADE_AVAILABLE`. | The full repo as a flat tree. Host auto-discovers `SKILL.md` at the install root and ignores producer-side files. | New release lands on `main` + tag `v`. Consumers pick it up on next `bin/check-update` invocation (first invocation per session, with snooze/disable state in `~/.cache/agent-native-cli/`). | None on the producer side — no telemetry. `bin/check-update` is the consumer-side mechanism; it compares local `VERSION` to `main` and prompts the user. | +| `brettdavies/agentnative-site` `/skill` + `/skill.json` endpoints | The site holds the source-of-truth manifest at `src/data/skill.json`. `src/build/skill.mjs` validates and emits `dist/skill.json` byte-stably during the site build. The manifest's `source.commit` is hand-co-edited at release time to pin a specific `agentnative-skill` SHA. | The `/skill.json` machine surface, `/skill.html` human page, and `/skill.md` markdown twin — all derived from the site's own `src/data/skill.json`. | On every site deploy. The site's release procedure includes hand-bumping `source.commit` (and `version`) to match the `agentnative-skill` release being announced. | Synthetic probe — `agentnative-site/.github/workflows/skill-availability.yml` runs `git ls-remote` against this repo daily at 13:00 UTC and fails the run if the repo 404s, gets renamed, or flips private. | +| `brettdavies/agentnative-cli` `src/skill_install/skill.json` | `~/dev/agentnative-cli/scripts/sync-skill-fixture.sh` pulls `src/data/skill.json` from `agentnative-site` (default ref: `dev`). The fixture is also the build-time input for `build.rs`'s host-map codegen — `cargo build` regenerates `SkillHost`, `KNOWN_HOSTS`, and `resolve_host` from it. | Whatever ships in the site's `src/data/skill.json` — the `install.` map, in particular. | Manual re-run when the site updates `src/data/skill.json`. Pre-release checklist in agentnative-cli's `RELEASES.md` captures the cadence. | `agentnative-cli/.github/workflows/skill-fixture-drift.yml` runs `scripts/sync-skill-fixture.sh --check` on every PR; non-zero exit on drift. | + +**Distribution chain summary** — `agentnative-skill` (this repo, the bundle) → `agentnative-site` `src/data/skill.json` +(the manifest, hand-edits `source.commit` to pin a SHA from this repo) → `agentnative-cli` +`src/skill_install/skill.json` (the fixture, synced from the site, drives the Rust `SkillHost` codegen). + +## Release / dispatch chain + +There is **no automated `repository_dispatch` chain** between these three repos. The producer-side workflows are CI-only +(`ci.yml`: markdownlint + shellcheck; `guard-main-docs.yml`: blocks engineering docs from `main`). Cross-repo +propagation is **manual + checklist-driven**. + +A skill release flows like this: + +1. **`agentnative-spec`** ships a new `v*` tag (independent cadence). +2. **`agentnative-skill`** (this repo) opens a `release/v` branch from `main`, cherry-picks non-docs commits from + `dev`, runs `scripts/sync-spec.sh` to re-vendor `spec/`, bumps `VERSION`, generates CHANGELOG, merges to `main`, tags + `v`, creates a GitHub Release. Consumers see the new version on next `bin/check-update`. +3. **`agentnative-site`** updates `src/data/skill.json` (`version` and `source.commit`) to point at the new + `agentnative-skill` SHA, deploys. `/skill.json` now serves the new manifest. The daily `skill-availability` probe + continues to verify the producer repo is reachable. +4. **`agentnative-cli`** runs `scripts/sync-skill-fixture.sh` to pull the updated `src/data/skill.json` from the site, + `cargo build` regenerates the host map, ships in the next `anc` release. The `skill-fixture-drift` workflow ensures + no PR lands while the fixture is stale. + +Each step is gated by a human reading the previous repo's release notes and deciding to advance the chain. There is no +webhook, no scheduled sync, no `repository_dispatch` payload between any pair. + +## Reference + +- `scripts/sync-spec.sh` — header comment has full env-var matrix and fallback behavior. +- `spec/README.md` — vendored-spec layout, attribution, and resync invocation. +- `RELEASES.md` §"Spec re-vendoring" and §"Releasing dev to main" — release-time procedure. +- `~/dev/agentnative-cli/scripts/sync-spec.sh` — parallel implementation; keep aligned. +- `~/dev/agentnative-cli/scripts/sync-skill-fixture.sh` — downstream sync from site → cli; header documents `--check` + mode and CI integration. +- `~/dev/agentnative-site/src/build/skill.mjs` — the validator/emitter that treats `src/data/skill.json` as source of + truth for the `/skill.json` endpoint. +- `~/dev/agentnative-site/.github/workflows/skill-availability.yml` — daily synthetic probe of this repo. +- `~/dev/agentnative-cli/.github/workflows/skill-fixture-drift.yml` — per-PR drift gate on the cli fixture. diff --git a/getting-started.md b/getting-started.md index 5cab6d8..d876693 100644 --- a/getting-started.md +++ b/getting-started.md @@ -18,11 +18,16 @@ anc check --output json . > scorecard.json # references/rust-clap-patterns.md (Rust/clap) # references/framework-idioms.md (Rust idioms) # references/framework-idioms-other-languages.md (Click, argparse, Cobra, Commander, yargs, oclif, Thor) -# Re-run `anc check` until the scorecard is clean. +# Re-run `anc check` until `summary.fail == 0` and +# `coverage_summary.must.verified == coverage_summary.must.total`. + +# 4. Claim the badge once `badge.eligible == true` (≥80%). +# Copy `badge.embed_markdown` from the JSON scorecard into the project's README. ``` Useful flags: `--principle N` to focus on one principle, `--audit-profile ` to suppress checks that don't -apply (e.g. `human-tui` for tools that legitimately intercept the TTY), `--binary` / `--source` to scope. +apply (`human-tui` for TUIs that legitimately intercept the TTY, `posix-utility` for cat/sed/awk-style stdin-primary +tools, `diagnostic-only` for read-only tools, `file-traversal` reserved), `--binary` / `--source` to scope. ## You're building from scratch (Rust) @@ -47,14 +52,24 @@ anc check --output json # run continuously as you build compiled binary on `PATH`. Read `spec/principles/p1-*.md` through `p7-*.md` for the language-agnostic requirements, and `references/framework-idioms-other-languages.md` for per-framework idioms. -## Installing anc +## Installing anc and this skill bundle ```bash +# 1. Install the `anc` binary. brew install brettdavies/tap/agentnative # macOS / Linux cargo install agentnative + +# 2. Install this skill bundle into your host's canonical skills directory. +# Six hosts supported: claude_code, codex, cursor, factory, kiro, opencode. +anc skill install claude_code +anc skill install --dry-run claude_code # preview the git clone +eval $(anc skill install --dry-run claude_code) # captures cleanly for scripted installs +anc skill install --output json claude_code # uniform JSON envelope (success + error) ``` -Binary name: `anc`. Prebuilt releases at . +Binary name: `anc`. Prebuilt releases at . If your host is not +yet in the binary's map, `anc skill install --dry-run ` prints a manual `git clone` you can adapt: `git +clone --depth 1 https://github.com/brettdavies/agentnative-skill.git `. ## Where things live @@ -64,6 +79,7 @@ Binary name: `anc`. Prebuilt releases at ` in Rust/clap? | `references/rust-clap-patterns.md` | | How do I implement `` in Python/Go/JS? | `references/framework-idioms-other-languages.md` | +| How does the badge eligibility threshold work? | `SKILL.md` § "The anc loop" (80% floor) | | File a spec question or proposal | | | File an `anc` bug | | | File a skill-bundle issue | | diff --git a/references/update-check.md b/references/update-check.md new file mode 100644 index 0000000..d4d5e61 --- /dev/null +++ b/references/update-check.md @@ -0,0 +1,33 @@ +# Update check — operational details + +The `bin/check-update` script compares this bundle's `VERSION` against `main` on GitHub. Exit code is always `0`; +network failures, broken installs, and disabled checks all degrade silently. SKILL.md only documents the agent-visible +surface; this file documents the script's contract for anyone debugging it. + +## Output contract + +| Stdout | Meaning | +| ------------------------------------ | ----------------------------------------------------------------------------- | +| (empty) | Up to date, snoozed, disabled, or check skipped (broken install, no network). | +| `UPGRADE_AVAILABLE ` | A newer release is on `main`. Trigger the upgrade prompt below. | + +## Prompt the user via `AskUserQuestion` + +> `agent-native-cli` **v{remote}** is available (you're on v{local}). Upgrade now? + +Three options: + +- **"Yes, upgrade now"** — run `git -C pull --ff-only`. The bundle root is the parent of `bin/`; + `git -C ../.. pull --ff-only` from `bin/` works for the default install layout (`~//skills/agent-native-cli/`). + If `--ff-only` rejects (uncommitted edits or divergent history), surface git's error verbatim and stop — do not + auto-stash. +- **"Not now"** — write `$HOME/.cache/agent-native-cli/update-snoozed` in the format ` `, where + `` is `1` (24h reminder), `2` (48h), or `3` (7 days), escalating each time the user defers. Tell the user the + next reminder window. +- **"Never ask again"** — `touch $HOME/.cache/agent-native-cli/disabled` and tell the user how to re-enable (`rm + $HOME/.cache/agent-native-cli/disabled`). + +## State directory + +`$HOME/.cache/agent-native-cli/` holds three files: `last-update-check`, `update-snoozed`, `disabled`. The script +auto-creates the directory on first slow-path fetch.