Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
a590a85
docs(plans): add v0.4.0 spec sync and prose tooling import plans
brettdavies May 7, 2026
48d3239
feat(spec): vendor agentnative-spec v0.4.0 + bump registry counters
brettdavies May 7, 2026
691e070
feat(checks): implement P1 and P4 checks for v0.4.0 spec sync
brettdavies May 7, 2026
0d0afb6
feat(checks): implement P2 schema trio for v0.4.0 spec sync
brettdavies May 7, 2026
7dd8a22
feat(checks): implement P6 sigterm + standard-names; suppress sigterm…
brettdavies May 7, 2026
dff880e
feat(checks): implement P8 skill bundle suite for v0.4.0 spec sync
brettdavies May 7, 2026
c122376
feat(release): bump to 0.4.0 + regenerate coverage matrix + prose-scr…
brettdavies May 7, 2026
1df0377
test(spec): bump vendored-spec integration test to v0.4.0 / 57 requir…
brettdavies May 7, 2026
c654e6d
fix(checks): inline P8 helper-result construction into run()
brettdavies May 7, 2026
90aaaa7
docs(releases): add PR body section + tighten opening prose
brettdavies May 7, 2026
87b997d
chore(matrix): remove orphaned comment from UNVERIFIED_MUSTS
brettdavies May 7, 2026
4f856e0
docs(releases): codify no-hard-line-wrap rule in PR body section
brettdavies May 7, 2026
37c696b
feat(cli): add --json global flag as short alias for --output json
brettdavies May 7, 2026
5b7c3f6
feat(cli): add anc schema subcommand satisfying p2-must-schema-print
brettdavies May 7, 2026
b41b54f
Revert "feat(cli): add anc schema subcommand satisfying p2-must-schem…
brettdavies May 7, 2026
0fa6853
test(dogfood): allowlist p2-schema-print until scorecard-schema verb …
brettdavies May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "agentnative"
version = "0.3.1"
version = "0.4.0"
edition = "2024"
description = "The agent-native CLI linter — check whether your CLI follows agent-readiness principles"
license = "MIT OR Apache-2.0"
Expand Down
102 changes: 97 additions & 5 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Releasing `agentnative`

Every change reaches production via this pipeline. Direct commits to `dev` or `main` are not permitted — every change
has a PR number in its squash commit message, which keeps the history scannable, attributable, and changelog-ready.
Every change reaches production via this pipeline. Direct commits to `dev` or `main` are not permitted. Every change has
a PR number in its squash commit message, which keeps the history scannable, attributable, and changelog-ready.

```text
feature branch → PR to dev (squash merge)
Expand All @@ -16,12 +16,12 @@ feature branch → PR to dev (squash merge)
| -------------------------------------- | --------------------------------------- | ------------------------------------------- | ------------------------------------ |
| `main` | Production. Only release commits. | Forever. | `.github/rulesets/protect-main.json` |
| `dev` | Integration. All feature PRs land here. | Forever. Never delete. | `.github/rulesets/protect-dev.json` |
| `feat/*`, `fix/*`, `chore/*`, `docs/*` | Feature work. | One PR's worth. Auto-deleted on merge. | None — squash into dev freely. |
| `feat/*`, `fix/*`, `chore/*`, `docs/*` | Feature work. | One PR's worth. Auto-deleted on merge. | None. Squash into dev freely. |
| `release/*` | Head of a dev → main PR. | One release's worth. Auto-deleted on merge. | None. |

`dev` is a **forever branch**. Never delete it locally or remotely, even after a `release/* → main` merge. The next
release cycle reuses the same `dev`. The repo's `deleteBranchOnMerge: true` setting doesn't touch `dev` as long as `dev`
is never the head of a PR — using a short-lived `release/*` head is what keeps the setting compatible with a forever
is never the head of a PR. Using a short-lived `release/*` head is what keeps the setting compatible with a forever
integration branch.

## Daily development (feature → dev)
Expand All @@ -38,6 +38,47 @@ gh pr create --base dev --title "feat(scope): what changed"
- **Commit style**: [Conventional Commits](https://www.conventionalcommits.org/).
- **PR body**: follow `.github/pull_request_template.md`. The `## Changelog` section is the source of truth for
user-facing release notes — `git-cliff` extracts these bullets verbatim into `CHANGELOG.md` during release prep.
- **PR body prose scrub**: `gh pr create` and `gh pr edit` send body text directly to GitHub; no automated prose check
sees it. Save the body to `/tmp/`, run Vale + LanguageTool + unslop, fix findings, then submit via `--body-file`. See
[§ Prose scrubbing](#prose-scrubbing).

## PR body

Every PR — feature, fix, docs, release — uses `.github/pull_request_template.md` verbatim. Six sections, no inventions:
`## Summary`, `## Changelog`, `## Type of Change`, `## Related Issues/Stories`, `## Files Modified`, `## Testing`.

- **Summary** is the NEW user-facing substance the PR ships. What is changing for the consumer that was not already
there. One short paragraph fits. Do NOT recap the workflow (cherry-pick / regenerate / pre-push gate / CI behavior is
documented in this file and `.github/`). Do NOT paste triple-diff output, pre-push gate results, or CI check status
into the body. Those are author verification artifacts that stay local; anomalies get fixed before push, not
audit-trailed in the body.
- **Changelog** subsections (`### Added` / `### Changed` / `### Fixed` / `### Documentation`) hold the user-facing
entries. The template's RULES (in the HTML comment at the top of the section) are literal: 1-5 bullets, delete empty
subsections entirely, each bullet starts with a verb. Prose-only edits leave the section empty or omit it.
- **Type of Change** is one checkbox. Prefer `feat` / `fix` over `chore` when the change has any user-observable effect
(config defaults, env vars, default behaviors). `cliff.toml` skips `^chore` (and `^style` / `^test` / `^ci` /
`^build`) regardless of body content; mistyping a user-facing change as `chore` silently strips it from release notes.
- **Related Issues/Stories** has four labels (`Story:` / `Issue:` / `Architecture:` / `Related PRs:`). All four are
required even when empty — write `- None.` or `n/a` rather than deleting the label.
- **Files Modified** has four sub-headers (`**Modified:**` / `**Created:**` / `**Renamed:**` / `**Deleted:**`). All four
are required even when empty — `Renamed: None.` / `Deleted: None.`
- **Internal tooling commits** (`chore(cliff): ...`, `chore(prose-check): ...`, etc.) do NOT appear in the PR body's `##
Changelog`. They are not user-facing.
- **Release PRs** repeat the entries from the upstream feature PRs they cherry-pick. The repetition is intentional and
harmless: `cliff.toml`'s `^release` skip prevents the release-PR squash commit from being double-counted in any future
regeneration.
- **No AI attribution.** Never append `Co-Authored-By: Claude …`, `🤖 Generated with [Claude Code]`, or any similar
AI-attribution trailer to PR bodies or commit messages. Commits and PRs stand on their own technical content.
- **No hard line wraps.** Author each paragraph and each bullet as one logical line, however long. GitHub soft-wraps for
display; hard wraps within prose produce visible mid-sentence breaks in some renderers and interfere with the
prose-check pipeline (Vale's line-anchored output reports findings against split lines, LanguageTool's input handling
can choke on certain control-char interactions). The auto-format hook skips `/tmp/` paths so the body keeps its
authored shape — don't undo that with manual wrapping during composition. The same rule applies to commit messages
composed via heredoc and to any markdown that ships verbatim to GitHub.

The PR body is read by humans reviewing what shipped. Workflow mechanics, verification output, and tool-fix provenance
are noise from that perspective; they belong in this file (`RELEASES.md`), the script outputs, and the commit history
respectively.

## Releasing dev to main

Expand Down Expand Up @@ -139,7 +180,12 @@ git add src/skill_install/skill.json && \
./scripts/generate-changelog.sh

# 9. Review CHANGELOG.md. See "CHANGELOG is generated, never hand-written" below
# for the cliff.toml chore-skip footgun and how to recover. When clean, commit:
# for the cliff.toml chore-skip footgun and how to recover. Then scrub the
# generated content through Vale + LanguageTool + unslop — CHANGELOG.md is a
# generated artifact built from upstream PR bodies and inherits whatever prose
# those PR bodies carry. See "Prose scrubbing" below for the procedure. Fix
# findings on the upstream PR body and re-run scripts/generate-changelog.sh,
# not by hand-editing CHANGELOG.md. When clean, commit:
git add CHANGELOG.md && git commit -m "docs: update CHANGELOG.md for v0.2.0"

# 10. Push and open the PR:
Expand Down Expand Up @@ -253,6 +299,52 @@ A PR that has no user-facing impact (pure refactor, test-only, CI-only) should l
omit it. See "CHANGELOG is generated, never hand-written" above for how the script consumes these sections at release
time and the cliff.toml chore-skip footgun.

## Prose scrubbing

Three release-flow artifacts live outside any automated prose check and need a manual scrub before they ship:

- **PR bodies.** `gh pr create` and `gh pr edit` send body text directly to GitHub; no automated prose check has reach
there.
- **`CHANGELOG.md`.** A generated artifact built from upstream PR bodies — it inherits whatever prose those PR bodies
carry, so scrubbing happens at generation time on the release branch.
- **Release-PR bodies.** The `release/v<version>` PR to `main` gets wrap-up text contributors edit after `CHANGELOG.md`
has been generated, and the same out-of-repo gap applies.

The canonical Vale + LanguageTool rule packs and orchestrator behavior live in the spec repo at
[`~/dev/agentnative-spec/docs/architecture/voice-enforcement.md`](../agentnative-spec/docs/architecture/voice-enforcement.md).
Until those packs are vendored into this repo (a deferred follow-up tracked in the spec plan; expected to extend
`scripts/sync-spec.sh`), point Vale at the spec checkout via `--config`.

The scrub procedure:

```bash
# 1. Save the artifact to /tmp/. The auto-format hook skips /tmp paths, so the
# body keeps its authored shape and no soft-wrapping is injected.
gh pr view <num> --json body --jq .body > /tmp/body.md # for PR body edits
# cp CHANGELOG.md /tmp/body.md # for changelog scrub

# 2. Vale (against the spec's rule packs — until vendored locally, point at the spec checkout).
vale --no-global --config ~/dev/agentnative-spec/.vale.ini --output=line --minAlertLevel=error /tmp/body.md

# 3. LanguageTool (blocking categories: TYPOS|GRAMMAR|CONFUSED_WORDS, mirrors the orchestrator's whitelist).
curl -sS -X POST "${LANGUAGETOOL_URL:-http://pool.tail42ba87.ts.net:8081}/v2/check" \
--data-urlencode "language=en-US" --data-urlencode "text@/tmp/body.md" \
| jaq '.matches[] | select(.rule.category.id | test("^(TYPOS|GRAMMAR|CONFUSED_WORDS)$"))'

# 4. unslop (em-dash density and AI-unique structural patterns Vale + LT do not catch).
~/.claude/skills/unslop/scripts/score.py /tmp/body.md

# 5. Apply fixes per finding. Re-run until 0 blocking and unslop score is 0.

# 6. Apply the cleaned version:
gh pr edit <num> --body-file /tmp/body.md # for PR body edits
# ./scripts/generate-changelog.sh # for CHANGELOG.md (re-runs the
# # PR-body fetch from GitHub)
```

For a `CHANGELOG.md` finding, fix the upstream PR body (which `generate-changelog.sh` re-fetches every run) and
regenerate. Hand-editing `CHANGELOG.md` directly produces drift the next regeneration overwrites.

## Branch protection

Two rulesets are committed under `.github/rulesets/` and applied to the repo via the GitHub API:
Expand Down
135 changes: 129 additions & 6 deletions completions/anc.bash
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ _anc() {
anc,help)
cmd="anc__help"
;;
anc,skill)
cmd="anc__skill"
;;
anc__generate,coverage-matrix)
cmd="anc__generate__coverage__matrix"
;;
Expand All @@ -52,17 +55,35 @@ _anc() {
anc__help,help)
cmd="anc__help__help"
;;
anc__help,skill)
cmd="anc__help__skill"
;;
anc__help__generate,coverage-matrix)
cmd="anc__help__generate__coverage__matrix"
;;
anc__help__skill,install)
cmd="anc__help__skill__install"
;;
anc__skill,help)
cmd="anc__skill__help"
;;
anc__skill,install)
cmd="anc__skill__install"
;;
anc__skill__help,help)
cmd="anc__skill__help__help"
;;
anc__skill__help,install)
cmd="anc__skill__help__install"
;;
*)
;;
esac
done

case "${cmd}" in
anc)
opts="-q -h -V --quiet --help --version check completions generate help"
opts="-q -h -V --quiet --json --help --version check completions generate skill help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand All @@ -76,7 +97,7 @@ _anc() {
return 0
;;
anc__check)
opts="-q -h --command --binary --source --principle --output --include-tests --audit-profile --quiet --help [PATH]"
opts="-q -h --command --binary --source --principle --output --include-tests --audit-profile --quiet --json --help [PATH]"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down Expand Up @@ -106,7 +127,7 @@ _anc() {
return 0
;;
anc__completions)
opts="-q -h --quiet --help bash elvish fish powershell zsh"
opts="-q -h --quiet --json --help bash elvish fish powershell zsh"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand All @@ -120,7 +141,7 @@ _anc() {
return 0
;;
anc__generate)
opts="-q -h --quiet --help coverage-matrix help"
opts="-q -h --quiet --json --help coverage-matrix help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand All @@ -134,7 +155,7 @@ _anc() {
return 0
;;
anc__generate__coverage__matrix)
opts="-q -h --out --json-out --check --quiet --help"
opts="-q -h --out --json-out --check --quiet --json --help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down Expand Up @@ -198,7 +219,7 @@ _anc() {
return 0
;;
anc__help)
opts="check completions generate help"
opts="check completions generate skill help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down Expand Up @@ -281,6 +302,108 @@ _anc() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
anc__help__skill)
opts="install"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
anc__help__skill__install)
opts=""
if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
anc__skill)
opts="-q -h --quiet --json --help install help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
anc__skill__help)
opts="install help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
anc__skill__help__help)
opts=""
if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
anc__skill__help__install)
opts=""
if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
anc__skill__install)
opts="-q -h --dry-run --output --quiet --json --help claude_code codex cursor factory kiro opencode"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "text json" -- "${cur}"))
return 0
;;
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
esac
}

Expand Down
Loading
Loading