From 5d9092c8fd5a8c64f088628354b3245acc9d96dc Mon Sep 17 00:00:00 2001 From: xeri <109935338+xeri@users.noreply.github.com> Date: Fri, 17 Jul 2026 01:08:00 +1200 Subject: [PATCH] =?UTF-8?q?docs,ci:=20name=20the=20change,=20never=20the?= =?UTF-8?q?=20plan=20=E2=80=94=20naming=20rules=20+=20merge=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs/agent/naming.md holds the durability test, the naming rules for branches/PRs/commits/spec files, and the no-attribution rule (no session URLs, no assistant co-author trailers). CI gains a naming job that rejects plan jargon and attribution trailers in a PR's branch name, title, commit messages, and changed paths — the file's own razor: a rule needs a gate. CLAUDE.md routes to it from Where-everything-lives, Working practice, and Enforcement; .claude/rules/go.md's commit bullet points at it. --- .claude/rules/go.md | 3 ++- .github/workflows/ci.yml | 42 ++++++++++++++++++++++++++++++ CLAUDE.md | 11 +++++++- docs/agent/naming.md | 55 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 docs/agent/naming.md diff --git a/.claude/rules/go.md b/.claude/rules/go.md index 077cdae..549fe38 100644 --- a/.claude/rules/go.md +++ b/.claude/rules/go.md @@ -36,4 +36,5 @@ paths: double-click launch from Explorer. New CLI subcommands call `wincon.AttachParent()` at the top. - Commits: lowercase, terse, scope-prefixed ("v2 motion: …"); one concern per - commit; protocol and implementation never change in the same commit. + commit; protocol and implementation never change in the same commit. Naming + + hygiene (no plan labels, no attribution trailers): `docs/agent/naming.md`. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3612403..2355a58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -209,6 +209,48 @@ jobs: bash <(curl -sSf https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) ./actionlint -color + # docs/agent/naming.md: names describe the change, never the plan that produced + # it, and commit messages carry no assistant attribution. PR-only because the + # checked surfaces (head branch, title, commit range) only exist on PRs. + naming: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + fetch-depth: 0 + # head_ref and the PR title are attacker-controlled on fork PRs — they enter + # the script through env, never through template interpolation (zizmor). + - name: no plan jargon or attribution trailers + env: + HEAD_REF: ${{ github.head_ref }} + PR_TITLE: ${{ github.event.pull_request.title }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + jargon='\b(phase|pillar|sprint|milestone|week)[ _-]?[0-9ivx]+\b|\bbatch[ _-][a-d0-9]\b' + trailers='^(co-authored-by:.*claude|claude-session:)' + fail=0 + if printf '%s\n%s\n' "$HEAD_REF" "$PR_TITLE" | grep -qiE "$jargon"; then + echo "::error::branch name or PR title carries plan jargon - name the change, not the plan (docs/agent/naming.md)" + fail=1 + fi + if git log --format='%B' "$BASE_SHA..HEAD" | grep -qiE "$jargon"; then + echo "::error::a commit message in this PR carries plan jargon - name the change, not the plan (docs/agent/naming.md)" + git log --format='%h %s' "$BASE_SHA..HEAD" | grep -iE "$jargon" || true + fail=1 + fi + if git log --format='%B' "$BASE_SHA..HEAD" | grep -qiE "$trailers"; then + echo "::error::assistant attribution trailer in a commit message - no Claude-Session/Co-Authored-By: Claude (docs/agent/naming.md)" + fail=1 + fi + if git diff --name-only "$BASE_SHA...HEAD" | grep -iE '(phase|pillar|sprint|milestone|week|batch)[_-]?([0-9ivx]+|[a-d])([._-]|$)'; then + echo "::error::a changed file path carries plan jargon - name the file for its topic (docs/agent/naming.md)" + fail=1 + fi + exit "$fail" + build-windows: needs: frontend runs-on: windows-latest diff --git a/CLAUDE.md b/CLAUDE.md index d09eb86..15b51be 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,6 +29,8 @@ polish. Protect what works; be honest about what doesn't exist yet (Reality chec triggers** — `docs/agent/reasoning.md`. Read it before chasing any bug. - **Known UX debt** — `docs/agent/polish-backlog.md`; pull from it before inventing new polish work. +- **Naming & git hygiene** (branches, PRs, commits, spec files) — + `docs/agent/naming.md`; CI's naming job is the gate. - **Full command list + devmock UI state matrix** — `docs/agent/commands.md`. - **Design charter** — `frontend/DESIGN.md`; read it before any UI work. @@ -216,13 +218,20 @@ Nothing enforces this; it is judgment, and ignoring it has already cost a sessio and check with `git merge-base --is-ancestor` rather than assuming one exists. - **Label knowingly-incomplete work `wip:` and say what is missing.** An unlabelled stub reads as a finished decision to the next session. +- **Name branches, PRs, and commits for the change, never the plan that produced + them** — plan labels ("phase 9", "pillar 8") mean nothing outside their session. + Rules + examples: `docs/agent/naming.md`; CI's naming job is the gate. +- **No attribution in commits.** DO NOT append Claude session URLs; DO NOT include + `Co-Authored-By: Claude` trailers. The message says what changed and why, nothing + else (same gate; `.claude/settings.json` sets attribution empty at the source). ## Enforcement — what is a gate, not advice Blocks a merge → CI. `.github/workflows/ci.yml`: gofmt, vet, `go test -short` (unit + e2e + goleak + doccheck), `-race` (CI is the **only** place it runs — it needs cgo), the burst floor (own job, best-of-3 — never lower the floor to go green), -`golangci-lint` (`.golangci.yml`), a TODO/FIXME ban, `actionlint`, and a +`golangci-lint` (`.golangci.yml`), a TODO/FIXME ban, `actionlint`, the naming gate +(plan jargon + attribution trailers anywhere in a PR — `docs/agent/naming.md`), and a stale-`frontend/wailsjs` check. `.github/workflows/security.yml`: CodeQL, govulncheck, gitleaks, dependency-review, npm audit (gosec/zizmor/Scorecard are advisory → Security tab). `.github/workflows/fuzz.yml` fuzzes the parsers nightly; diff --git a/docs/agent/naming.md b/docs/agent/naming.md new file mode 100644 index 0000000..faf13a3 --- /dev/null +++ b/docs/agent/naming.md @@ -0,0 +1,55 @@ +# Naming & git hygiene — name the change, never the plan + +Plans are session-scoped; names are permanent. A branch, PR, commit, or spec file +named after a plan's internal structure ("phase 9", "pillar 8", "batch A") means +nothing the moment that session ends. Cleaning them out of this repo on 2026-07-17 +cost a 56-commit history rewrite, two branch renames, and edits to three merged +PRs — names that describe **what the change is** never need that. + +Enforced by the `naming` job in `.github/workflows/ci.yml`: every PR's head branch +name, title, commit messages, and changed file paths are rejected if they carry +plan jargon or attribution trailers. + +## The durability test + +*Would this name make sense to someone who never saw the plan that produced it?* +If decoding a name requires the plan document, the name is wrong. + +## Rules + +- **Branches** are `type/feature-slug` — `feat/`, `fix/`, `security/`, `docs/`, + `ci/`, `deps/`. The slug names the capability or fix (`feat/agent-management-gui`, + `security/agent-identity-hardening`), never a plan label, counter, or date. +- **PR titles** read like a commit subject: what the change is. **PR bodies** + describe what changed and how it was verified — plan narrative stays in the plan. + Cross-reference other work by PR number (#13) or branch name, never by plan label. +- **Commit messages, body included.** "Lands in Phase 3" rots the moment the plan + dies; point at the artifact instead ("lands with the multi-agent GUI"). Avoid bare + commit SHAs in messages and PR bodies where a PR number or filename works — SHAs + don't survive history rewrites. +- **Spec files** under docs/superpowers/specs/ are named + YYYY-MM-DD-topic-design.md — the topic, not the plan's name for the work. +- **No attribution trailers.** No Claude session URLs, no Co-Authored-By: Claude. + A commit message carries what changed and why — nothing else. + `.claude/settings.json` sets attribution empty at the source; the CI gate + catches strays. +- **Merge commits take the PR title** — a repo setting (merge_commit_title: + PR_TITLE), so never hand-write "Merge pull request #N from …" subjects. Head + branches auto-delete on merge; don't recreate long-lived ones. + +## Banned in names and messages (the CI regex, case-insensitive) + +phase/pillar/sprint/milestone/week followed by a number, "batch" followed by a +letter or number, `Claude-Session:` and `Co-Authored-By: …Claude…` trailer lines. + +## Before → after (from the 2026-07-17 cleanup) + +``` +feat/identity-phase9-finalize → feat/identity-enrollment-finalize +feat/pillar8-agent-mgmt → feat/agent-management-gui +identity phase 9: … finalize + docs → identity: enrollment, pairing, and revocation — finalize + docs +docs: Pillar 8 … Batch A design spec → docs: agent-management GUI surfacing design spec +"until Phase 3's Status.Agents" → "until the multi-agent GUI's Status.Agents lands" +2026-07-16-pillar8-agent-management-gui-design.md + → 2026-07-16-agent-management-gui-surfacing-design.md +```