Skip to content

feat(contract): agent CLI contract for every shipped script (ADR-029) - #58

Merged
sellke merged 33 commits into
mainfrom
feature/agent-cli-contract
Oct 20, 2026
Merged

sellke merged 33 commits into
mainfrom
feature/agent-cli-contract

Conversation

@sellke

@sellke sellke commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

Summary

Implements ADR-029. Writ doesn't get a writ CLI product. Instead, every script it ships to installed projects follows one agent contract, and a pytest test enforces it. Data tools print JSON. Check tools print pass/fail/unverifiable on line 1 and a summary last, and exit 0 when the check ran, 1 on fail, 2 on usage. No script spawns agents, waits without a bound, or calls the network without an opt-in. Six dev-only scripts stop shipping, and update.sh now handles stale scripts/ files correctly.

Changes

  • Contract: new .writ/docs/agent-cli-contract.md with the rules and a classification of all 34 shipped scripts. ADR-029 gets a dated amendment covering the two output shapes, the exit codes, usage errors and bounded waits.

  • Install and update: install.sh, update.sh and unlink.sh no longer ship pipeline-baseline, measure-invocation, harness-cost, lean-decision, prune-ledger or spawn-cap. update.sh now:

    • resolves scripts/ manifest paths at the project root (before, they were looked up under the platform folder and skipped);
    • removes scripts the ship filter rejects;
    • warns about a locally modified stale script, keeps it, and keeps its baseline so later updates warn again.
  • Enforcement: new scripts/tests/test_agent_cli_contract.py checks every shipped script, read from the real is_shippable_script:

    • its classification row;
    • --help and bad-flag handling;
    • AST scans for network imports, agent launches and waits;
    • shape and exit-code smokes, 41 read-only runs.

    Its KNOWN_VIOLATIONS list started at 14 entries and is now empty.

  • Script fixes: one commit per script, each carrying its callers and tests.

    • Python data tools:
      • revert-resolve, knowledge-consolidate and change-surface default to JSON, with --pretty for the old output and --json kept as a no-op.
      • boundary-map crossings prints JSON, and compute prints a blocker.
    • Python check tools: goal-emit, verdict-provenance, review-panel (all modes), gen-codex-agent-tomls and app-verify print the verdict first.
    • Shell checks: lint-skill.sh, gen-skill.sh and check-agent-parity.sh print the verdict first and handle --help and usage errors.
    • Other fixes:
      • test-integrity's parent-folder walk is bounded by a for loop.
      • roadmap-sync and goal-emit write atomically.
  • Callers:

    • commands/implement-story.md Gates 0.5, 2.5, 3 and 4.5, plus the .lean twin. The file stays at its exact byte pin.
    • revert.md, knowledge.md, create-goal.md, implement-phase.md, create-uat-plan.md, new-skill.md, refresh-command.md, update-writ.md and the adapters.
    • scripts/eval.sh and .github/workflows/eval.yml. The gen-skill CI step now requires line 1 to be pass.
  • Issues: resolves .writ/issues/features/2026-10-12-agent-facing-writ-cli.md. Files four problems found along the way:

    • update.sh deletes a modified stale file on its second run;
    • update.sh drops the Codex config baseline;
    • test-integrity's authenticity check flags every shell test;
    • whether shipped scripts should leave the project-root scripts/ folder.

Spec Reference

  • Spec: .writ/specs/2026-10-12-agent-cli-contract/spec.md (Complete)
  • Stories: story-1-contract-doc-and-adr-amendment.md, story-2-stop-shipping-dev-scripts.md, story-3-contract-test.md, story-4-conformance-fixes.md, story-5-shell-and-app-verify-conformance.md

Test Results

/ship did not run tests before the original PR was opened. The /implement-spec integration run on this branch after the final story:

  • uv run pytest: 2614 passed, 2 skipped
  • bash tests: 0 failures
  • bash scripts/eval.sh: Findings 0
  • exit-criteria verdict: met

This /ship (2026-10-20) merged origin/main. The only conflict was the lean-command pin comments in scripts/tests/test_lean_commands.py; both histories were kept, and the hashes already matched the merged command files. Re-run before merge:

  • uv run pytest: 2614 passed, 2 skipped
  • bash scripts/tests/test_*.sh: 0 failures
  • bash scripts/eval.sh: Findings 0

Drift Report

12 Small, 8 Medium, 0 Large (drift-log.md). The Medium entries were each decided or approved by the maintainer during the run:

  • The check shape covers every mode, including modes that write files. The conformance work was split into Stories 4 and 5. (Story 1)
  • update.sh stale handling goes beyond the filter change. The spec wrongly assumed the old stale logic covered scripts/. (Story 2)
  • The exit-code rule was amended to match the existing check scripts: unverifiable exits 0. (Story 3)
  • The shell --help rule is stricter, and the smoke covers one mode per script, with modes added as they were fixed. (Story 3)
  • The live-repo parity test checks output shape, not zero drift. An AC-4.5 test-citation gap was found by eval.sh after Story 4 was marked complete, and fixed in Story 5. (Story 5)

Review Notes

No standalone /review was run. Each story passed Gate 3:

  • Stories 1–3 were reviewed by the evaluator agent.
  • Stories 4 and 5 were risk-routed to the review agent.
  • Every Gate 3 passed on iteration 1. Minor findings were fixed in follow-up commits.

The configured cross-vendor review panel (.writ/config.md) did not run. review-panel.py status --platform claude-code returns unverifiable (platform_cannot_spawn_other_vendors), and that status line was not printed in the story reports.

Things to check before merging:

  • An installed project that updates its scripts without its commands would show /knowledge users JSON instead of the markdown report. update.sh copies both together.
  • Gate 4.5 has no explicit branch for app-verify printing no verdict line, because of the byte pin.

🤖 Generated with Claude Code

sellke and others added 30 commits October 12, 2026 13:58
Promoted from .writ/issues/features/2026-10-12-agent-facing-writ-cli.md.
Four stories implementing ADR-029: contract doc and ADR amendment,
stop shipping dev-only scripts, contract test, conformance fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds .writ/docs/agent-cli-contract.md: two output shapes (data JSON,
check verdict line), usage-error rule, bounded waits, network opt-in,
and a 34-row classification of every shipped script. Amends ADR-029
rules 1 and 2. 13 doc tests pass on Python 3.9; full suite 2288 passed.
Drift: 2 Medium (strict check rule across all modes; ~15 scripts
flagged, conformance split into Stories 4 and 5), 1 Small.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ac-trace reported AC-1.5 untested: no test named it. Adds a test that
every test in the file names a Story 1 AC ID. 14 passed on Python 3.9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The maintainer kept the check-shape rule strict across every mode
(drift-log DEV-001, DEV-002). Story 4 now covers Python data and check
tools plus atomic writes; new Story 5 covers lint-skill.sh, gen-skill.sh,
check-agent-parity.sh, and app-verify.py line 1, and closes the issue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pdate (Story 2)

install.sh, update.sh and unlink.sh no longer ship pipeline-baseline,
measure-invocation, harness-cost, lean-decision, prune-ledger, spawn-cap.
update.sh now resolves scripts/ manifest paths at the project root,
treats filter-rejected scripts as stale, and warns about (and keeps the
baseline of) a locally modified stale script. New shell test covers both
upstream cases and a second update run. Bash suite green; pytest 2289
passed; eval.sh Findings 0. Drift: 1 Medium (update.sh scope, approved),
2 Small. Files three pre-existing issues found on the way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ebt (Story 3)

New scripts/tests/test_agent_cli_contract.py checks every shipped script
against .writ/docs/agent-cli-contract.md: table completeness, --help,
bad flag, network/agent-launch/wait scans, shape and exit-code smokes.
14 known violations tracked as strict xfail for Stories 4 and 5.
Amends the check-tool exit-code rule to match the scripts (maintainer
decision): exit 0 when the check ran, 1 fail, 2 usage. 269 passed,
14 xfailed on 3.9; full suite 2544 passed; eval.sh Findings 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…CLI contract, Story 4)

_py_search_roots walked parent folders with `while True`, which the
contract test reads as an unbounded wait. Iterate over the folder and its
`parents` instead, so the filesystem root bounds the loop. Output is
unchanged.

- scripts/test-integrity.py: for-loop over (parent, *parents)
- scripts/tests/test_test_integrity.py: pin the walk (nested file, file
  outside the project)
- test_agent_cli_contract.py: delist (test-integrity.py, bounded_wait)
- agent-cli-contract.md: drop the test-integrity note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(agent CLI contract, Story 4)

revert-resolve.py is a data tool, but its default success output was a
human summary and JSON sat behind --json. JSON (indent 2, unchanged) is
now the default; --pretty prints the former summary; --json is a hidden
no-op so older command files keep working.

- scripts/revert-resolve.py: flags and docstring CLI block
- scripts/tests/test_revert_resolve.py: JSON default equals the old
  --json document, --json no-op, --pretty equals the human render
- commands/revert.md: drop --json from the resolver call
- test_agent_cli_contract.py: delist (revert-resolve.py, shape)
- agent-cli-contract.md: drop the revert-resolve note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eport (agent CLI contract, Story 4)

knowledge-consolidate.py is a data tool, but its default output was the
markdown report and JSON sat behind --json. Compact JSON is now the
default; --pretty prints the report byte for byte as before; --json is a
hidden no-op, so commands/retro.md's `--json` call keeps working
unedited.

- scripts/knowledge-consolidate.py: flags and docstring usage block
- scripts/tests/fixtures/knowledge-consolidate/: fixture ledger plus the
  dry-run and apply reports captured from the unmodified script
- scripts/tests/test_knowledge_consolidate.py: --pretty matches each
  capture; JSON default; --json no-op; dry run writes nothing
- commands/knowledge.md: --pretty on the dry-run and apply calls so the
  user sees the same report
- test_agent_cli_contract.py: delist (knowledge-consolidate.py, shape)
- agent-cli-contract.md: drop the knowledge-consolidate note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…contract, Story 4)

change-surface.py is a data tool, but `classify` printed one bare class
word. It now prints one JSON object `{"class": CLASS}`; --pretty prints
the bare word; --json is a hidden no-op. Classification logic and exit
codes are unchanged.

- scripts/change-surface.py: JSON output, --pretty, docstring
- scripts/tests/test_change_surface.py: read the class from JSON; add
  JSON-shape, --pretty, and --json no-op cases
- commands/implement-story.md + .lean.md: Gate 2.5 reads
  `change_surface` from the JSON `class` field
- scripts/tests/test_gate_replay.py: read the replayed class from the JSON
- scripts/tests/test_lean_commands.py: repin implement-story.md (prose
  kept byte-neutral for the governor's command budget)
- test_agent_cli_contract.py: delist (change-surface.py, shape)
- agent-cli-contract.md: drop the change-surface note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a blocker (agent CLI contract, Story 4)

boundary-map.py is a data tool. `crossings` printed verdict/route/reason
text lines, and `compute` exited 1 with empty stdout on a malformed
story.

- crossings prints one JSON object {verdict, route, reasons, summary};
  --pretty prints the former lines byte for byte; --json is a hidden
  no-op. Exit codes are unchanged (0 when it ran, 2 on usage).
- compute prints {"blocker": {"code": "malformed_story", "summary": ...}}
  on a story error, still exit 1.
- load_map treats a saved blocker as an unreadable map, so the Gate 0.5
  tee of a blocker still routes `unverifiable` / `map_unreadable` rather
  than reading as an empty map that flags every file.
- commands/implement-story.md: Gate 0.5 treats a blocker as empty; Gate
  2.5 reads `gate3_route` from `route`; Gate 3 passes `reasons` as
  boundary_overlap_summary (byte-neutral for the command budget).
  .lean.md: Gate 0.5 blocker sentence (it has no crossings step).
- scripts/tests/test_boundary_map.py: crossings cases read the JSON;
  --pretty, --json no-op, saved-blocker map, and compute blocker cases
- scripts/tests/test_quality_gate_wiring.py, test_lean_commands.py:
  follow the prose change and repin
- test_agent_cli_contract.py: smokes may carry a mode (keyed
  `<script> <mode>`), one default per script; adds the
  `boundary-map.py crossings` smoke; delists (boundary-map.py, shape)
- agent-cli-contract.md: drop the boundary-map note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (agent CLI contract, Story 4)

goal-emit.py is a check tool, but a `pass` emit printed the /goal invoke
text after the summary line, so the summary was not last. The invoke
text now sits between the verdict line and the summary. GOAL.md and
VERIFY.md are each written to a temp file in the emit directory and
renamed over the target (os.replace), so an interrupted write leaves
the old file whole and no temp file behind; written files keep the
umask permissions a plain write gave them.

- scripts/goal-emit.py: _emit detail block, _write_atomic, docstring
- scripts/tests/test_goal_emit.py: invoke between verdict and summary;
  interrupted rewrite keeps old files; interrupted first write leaves
  nothing; permissions
- scripts/tests/test_goal_emit_gold.py: read the invoke between the
  verdict and summary lines
- commands/create-goal.md, implement-phase.md + .lean.md,
  adapters/claude-code.md: the invoke text is above the summary
  (implement-phase.md byte-neutral)
- scripts/tests/test_lean_commands.py: repin implement-phase.md
- test_agent_cli_contract.py: add the `goal-emit.py emit` pass-path smoke
- agent-cli-contract.md: drop the goal-emit note; the State sentence now
  names only roadmap-sync.py as an in-place writer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ast (agent CLI contract, Story 4)

verdict-provenance.py is a check tool, but it printed findings, then
notes, then a `gates: ...` summary, with no verdict line. It now prints
`pass` or `fail` alone on line 1, then the findings and notes as before,
then `verdict-provenance: <verdict> (gates: ...)` last. Exit codes are
unchanged.

- scripts/verdict-provenance.py: verdict line, summary prefix, docstring
- scripts/eval.sh: check_verdict_provenance skips the verdict line and
  relays the summary's metrics as the same `Metrics: gates: ...` note
- scripts/tests/test_verdict_provenance.py: findings sit between the
  verdict and the summary; clean-run shape case
- test_agent_cli_contract.py: delist (verdict-provenance.py, shape)
- agent-cli-contract.md: drop the verdict-provenance note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…LI contract, Story 4)

review-panel.py is a check tool. `status` and `tally` already printed a
verdict first, but `trial-init`, `trial-prepare`, `trial-record`, and
`trial-label` printed `initialized`, `prepared`, `recorded`, or
`labeled` on line 1. Each now prints `pass` on line 1 when it did its
job; detail lines and the `review-panel:` summary follow unchanged.
Refusals still exit 2. No command or adapter parses these words.

The contract doc named only trial-init and trial-prepare; trial-record
and trial-label had the same defect and are fixed with them, since the
check shape covers every subcommand.

- scripts/review-panel.py: `pass` line 1 in the four trial subcommands
- scripts/tests/test_review_panel.py: line-1 verdict for init, prepare,
  record, and label
- test_agent_cli_contract.py: smokes may carry a `setup(tmp)` hook; add
  `review-panel.py trial-init` and `review-panel.py trial-prepare` smokes
  (prepare runs against a throwaway git repo built in the temp dir)
- agent-cli-contract.md: drop the review-panel note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…CLI contract, Story 4)

gen-codex-agent-tomls.py is a check tool. --check already conformed, but
write mode printed bare `wrote <path>` lines, and an unmapped stem exited
1 with only a stderr message. Write mode now prints `pass`, the `wrote`
lines, and `gen-codex-agent-tomls: pass (<n> written, 0 unmapped)` last.
An unmapped stem still writes nothing; it prints `fail`, one
`reason: unmapped <stem>` line each, and the summary, exit 1. The
generated TOMLs are unchanged. Callers (commands/refresh-command.md,
adapters/codex.md) only run the command and need no edit.

- scripts/gen-codex-agent-tomls.py: write-mode output, docstring
- scripts/tests/test_gen_codex_agent_tomls.py: write-mode shape; the
  unmapped abort reads the fail shape on stdout
- test_agent_cli_contract.py: delist (gen-codex-agent-tomls.py, shape)
- agent-cli-contract.md: drop the gen-codex-agent-tomls note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Story 4)

The contract's State rule says each state file is written through a temp
file and a rename. append-row rewrote .writ/product/roadmap.md in place,
so an interrupted write could leave a truncated roadmap. It now writes a
temp file in the roadmap's folder and os.replace()s it over the target,
keeping the file's mode; on any failure the temp file is removed and the
old roadmap is untouched. Output is unchanged, so commands/release.md,
eval.sh, and eval-roadmap-sync.py need no edit.

- scripts/roadmap-sync.py: _write_atomic
- scripts/tests/test_roadmap_sync.py: an interrupted rename keeps the
  roadmap and leaves no temp file; the file mode survives
- agent-cli-contract.md: drop the State section's in-place-writer
  sentence (goal-emit.py went atomic earlier in this story)
- test_agent_cli_contract.py: drop the empty Story 4 heading in
  KNOWN_VIOLATIONS; note the `<script> <mode>` key in the docstring

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Story file, README, drift log (4 Small), and context for the ten
per-script fixes aaf1bc1..ad75824. KNOWN_VIOLATIONS holds only Story 5's
7 entries. Full suite 2586 passed, 7 xfailed on 3.9; eval.sh Findings 0.
Story 5 gains review-panel tally/trial-report verdict words and the
Gate 2.5 --surface wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ontract, Story 5)

Story 4 reads Completed, and ac-trace counts AC-4.5 as untested: the
atomic-write tests exist, but no test-shaped file attributes an AC-4.5
token to 2026-10-12-agent-cli-contract. review-override therefore failed
Story 4 and bash scripts/eval.sh reported 2 findings at 8dffc8f.

The comment above the roadmap-sync interrupted-write test now names the
spec and AC-4.5. No test logic changes. eval.sh --check=review-override
reports 0 findings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…I contract, Story 5)

lint-skill.sh printed one line per file and a trailing ❌ tally, had no
--help (it linted a file named --help), and exited 2 when a file was
missing. It now prints the check shape:

  line 1  pass | fail | unverifiable
  then    one `reason:` line per clean file, finding, warning, or missing
          file (indented Line/Description/Remediation details follow)
  last    lint-skill: <verdict> (<counts>)

Exit 0 on pass or unverifiable (a file was not found), 1 on fail, 2 on a
usage error: no files or an unknown flag, with usage on stderr and stdout
empty. --help prints usage and exits 0. Findings keep their ❌/⚠️/✅
markers, so the existing lint tests and eval-skill-lifecycle.py read the
output unchanged.

- scripts/tests/test_shell_check_shape.sh: new; pass, fail, missing-file,
  mixed, usage, and --help cases
- commands/new-skill.md: Phase 2 and the exit criteria read line 1;
  unverifiable never proceeds
- commands/refresh-command.md: Phase 5 branches on line 1
- test_lint_entry_level.sh: finding_count no longer filters the old tally
- agent-cli-contract.md: drop the lint-skill.sh note
- test_agent_cli_contract.py: delist lint-skill.sh help and shape

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ct, Story 5)

--check printed a bare diff (nothing when clean), write mode reported on
stderr only, an unknown flag exited 1, a missing manifest exited 2, and a
missing SKILL.md exited 3. Every mode now prints the check shape: line 1
pass | fail | unverifiable, `reason:` and detail lines, and a
`gen-skill: <verdict> (...)` summary last.

- --check: pass when SKILL.md matches; fail with a drift reason and the
  diff as detail lines (exit 1)
- write: pass after writing SKILL.md through a temp file beside it that
  keeps its mode (mktemp in /tmp made it 0600 and crossed filesystems)
- --dry-run: pass, the generated catalog, then the summary
- any mode: a manifest that fails validation is fail with one
  `reason: YAML error in .writ/manifest.yaml: ...` line (exit 1); write
  mode leaves SKILL.md untouched
- missing manifest or SKILL.md: unverifiable, exit 0
- unknown flag: usage on stderr, stdout empty, exit 2

Callers: commands/new-skill.md reads line 1 in its exit criteria, Step
3.3, and Completion; the CI step in .github/workflows/eval.yml and
test_model_tier_migration.sh require `pass` instead of exit 0, so a
missing SKILL.md no longer passes. eval.sh's --dry-run exit-code check
still means "manifest invalid" and is unchanged.

Tests: test_shell_check_shape.sh runs every mode on a minimal manifest
tree in a temp copy. The contract test smokes write mode the same way: a
Smoke setup may return a script copy to run in place of scripts/<script>.
Delists gen-skill.sh shape; drops its contract-doc note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent CLI contract, Story 5)

check-agent-parity.sh printed ⚠️ lines or a `parity OK` line with no
verdict, ignored every argument (--help ran the check), and always
exited 0. It now prints the check shape:

  line 1  pass | fail | unverifiable
  then    one `reason:` line per missing counterpart
  last    check-agent-parity: <verdict> (...)

Exit 0 on pass or unverifiable (no agents/ folder), 1 on fail (drift),
2 on any argument other than --help (usage on stderr, stdout empty).
--help prints usage and exits 0.

The caller keeps drift a warning. commands/refresh-command.md Phase 6
reads line 1, says `fail` exits 1 and never blocks, and gains an
`unverifiable` branch; its Cross-References row no longer says "always
exit 0". adapters/codex.md's maintainer checklist reads the `reason:`
lines.

- test_shell_check_shape.sh: aligned, drift, no agents/, usage, --help,
  and this repo, from a temp copy
- test_agent_cli_contract.py: delist check-agent-parity.sh help,
  bad_flag, and shape
- agent-cli-contract.md: drop the check-agent-parity.sh note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…CLI contract, Story 5)

app-verify.py printed its `app-verify:` summary on line 1, `touched`
printed bare feature IDs, and unverifiable runs (no recipe, invalid
recipe, refused, unsupported platform, no runnable feature) exited 2.
Every subcommand now prints the check shape:

  line 1  pass | fail | unverifiable
  then    `reason:` lines (validate findings, failed run features, the
          unverifiable or refused reason); `touched` prints one
          `feature: <id>` line per matched feature instead
  last    the `app-verify:` summary (unchanged wording; a refusal still
          reads `app-verify: refused (...)`)

Exit 0 when the check ran (pass or unverifiable), 1 on fail. --json
still prints one object after the summary; its `verdict` values (valid,
ok, refused, ...) are unchanged.

Callers, in this commit:
- commands/implement-story.md Gate 4.5 and the .lean twin branch on line
  1: `unverifiable` replaces exit 2, and "no `feature:` lines" replaces
  "printed no IDs". Gate 2.5 now says to pass the classifier's `class`
  as `--surface`. Two Gate 1 clauses were tightened (a "positioned"
  aside and a "see agents/coding-agent.md" pointer the gate header
  already names) to keep the file at 36973 bytes.
- commands/create-uat-plan.md Steps 1.3, 4.3, 4.4 read line 1 and the
  `feature:` lines.
- .writ/docs/app-verification-format.md: validate's verdicts.
- scripts/eval.sh check_app_verify expects pass/0, fail/1, and
  unverifiable/0 (refused) and checks line 1; test_eval_app_verify.sh
  stubs print the new shape, plus a summary-first stub that must yield a
  line-1 finding per recipe.
- test_lean_commands.py: implement-story.md hash repinned with a dated note.
agents/coding-agent.md, agents/visual-qa-agent.md, and adapters/codex.md
name the script without quoting its output; codex TOMLs stay in sync.

Contract test: smokes `app-verify.py touched` on the pass fixture
recipe, delists app-verify.py shape (KNOWN_VIOLATIONS is now {}), adds
AC-5.4 and AC-5.5 regression tests (no Story 5 script listed; no caller
quotes the retired output), and lets each test cite a Story 3 or Story 5
AC. The app-verify contract note now records only what remains specific:
`feature:` lines, refused as unverifiable, the --json order, and the
network/wait allowances.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…agent CLI contract, Story 5)

`tally` printed `block` and `trial-report` printed `keep` or `remove` on
line 1, words outside the check vocabulary. A panel word that is not a
verdict now follows as the first `reason:` line:

  tally         fail, reason: block (exit 1, unchanged)
  trial-report  pass, reason: keep | reason: remove (exit 0, unchanged)

`pass` and `unverifiable` print as before. Summaries and --json are
unchanged; the JSON `verdict` still holds the panel word.

Callers: Gate 3's review-panel paragraph in commands/implement-story.md
and the .lean twin reads "`fail` (`reason: block`) is a Gate 3 FAIL";
the rest of the paragraph, Gate 3.5's panel-block sentence, and the
advisory/pass/unverifiable/skipped/off list are unchanged. To keep
implement-story.md at 36973 bytes, the Gate 0.5 /prototype note names
the command instead of its file path and Gate 2's build-smoke sentence
drops "structural"; Gate 4.5 reads "its line says" again and "no
`feature:` line". eval.sh check_review_panel pins the new sentence;
test_lean_commands.py repins the hash with a dated note.

- test_review_panel.py: tally block, combination rule, trial-report
  keep/remove expectations, and the eval pin-mutation literal
- test_agent_cli_contract.py: smokes `review-panel.py tally` (two-vendor
  fixture, fail/exit 1) and `trial-report` (a finished keep trial in
  {tmp}); the AC-5.4 stale-caller list adds "`block` is a Gate 3 FAIL"
- agent-cli-contract.md: Notes now say every shipped script conforms;
  add a review-panel note for the reason-line panel word; drop the
  arch-lint note that only said it conforms

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ation issue (agent CLI contract, Story 5)

- features/2026-10-12-agent-facing-writ-cli.md: append `## Resolution`
  (2026-10-12, branch feature/agent-cli-contract, spec
  .writ/specs/2026-10-12-agent-cli-contract/spec.md): the contract doc
  and ADR-029 amendment, the six dev-only scripts no longer shipping
  with update.sh's stale-copy handling, the contract test, and every
  shipped script conforming with KNOWN_VIOLATIONS empty.
- improvements/2026-10-12-script-install-location.md: new, in the
  /create-issue template (Type: Improvement). Asks whether shipped
  scripts should leave the project-root scripts/ folder the way pstack
  keeps tools in the skill folder, citing ADR-029's negative consequence.
  install.sh keeps a same-named project file as "assumed modified", so
  commands then run the project's file; --force overwrites it.

No existing issue covered the install location.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- gen-skill write mode prints the check shape when SKILL.md is read-only
- create-uat-plan handles line 1 unverifiable from app-verify validate
- eval.sh reads gen-skill --dry-run line 1 instead of the exit code
- contract doc and BR4: --json may follow the summary (app-verify)
- live-repo parity test holds the shape, not zero drift
- AC-4.5 citations: goal-emit atomic writes and a doc-notes test

2614 passed on 3.9; bash loop green; eval.sh Findings 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Story file, README, drift log (2 Medium, 3 Small), and context for
d29bf3d..615865f. KNOWN_VIOLATIONS is empty: every shipped script
follows the agent CLI contract. 2614 passed on 3.9; eval.sh Findings 0
after the status flip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sellke and others added 2 commits October 12, 2026 19:16
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
exit-criteria implement-spec verdict met (5/5 stories terminal; full
suite 2614 passed after the final story). Drift this run: 12 small,
8 medium, 0 large.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sellke
sellke force-pushed the feature/agent-cli-contract branch from 1d6278e to 631e043 Compare October 19, 2026 21:46
Keep both lean-command pin notes. The merged command files hash to
create-spec from the prototype reframe and implement-phase /
implement-story from the agent CLI contract.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sellke
sellke marked this pull request as ready for review October 20, 2026 19:29
@sellke
sellke merged commit c9f84ad into main Oct 20, 2026
2 checks passed
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