diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..cbffb85 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +msg_file="$1" +subject=$(head -n 1 "$msg_file") + +case "$subject" in + Merge\ * | Revert\ * | fixup!* | squash!*) exit 0 ;; +esac + +if ! printf '%s' "$subject" | grep -qE '^(([A-Z][A-Z0-9]*-[0-9]+|#[0-9]+) )?(feat|fix|refactor|docs|test|chore|style|perf)(\([^)]+\))?: .+'; then + echo "commit-msg: bad subject '$subject'" >&2 + echo "commit-msg: expected 'TICKET type(SLUG): description' — see the Git memory section in CLAUDE.md" >&2 + exit 1 +fi + +type=$(printf '%s' "$subject" | sed -E 's/^(([A-Z][A-Z0-9]*-[0-9]+|#[0-9]+) )?([a-z]+).*/\3/') +case "$type" in + feat | fix | refactor) + if ! sed -n '2,$p' "$msg_file" | grep -v '^#' | grep -q '[^[:space:]]'; then + echo "commit-msg: warning — $type commit without a body; if it carries a decision, amend with the why (Git memory, CLAUDE.md)" >&2 + fi + ;; +esac + +exit 0 diff --git a/CLAUDE.md b/CLAUDE.md index d53f985..dc4cf81 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,6 +48,7 @@ claude --plugin-dir . ## Conventions - **Content language**: English by default. Skills adapt to the ticket/input language, or follow the project-level definition in CLAUDE.md / AGENTS.md. +- **Commits**: `TICKET type(SLUG): description`; a commit that carries a decision gets a prose body plus decision trailers (`Constraint:`, `Rejected:`, `Directive:`, `Related:`) — the git memory, see `skills/gca/reference/commit-convention.md`. Before modifying a file, read its history per `skills/gca/reference/history-reading.md`. Subject shape is enforced by `.husky/commit-msg`. - **Files and directories**: kebab-case - **Versioning**: semver in `plugin.json` (source of truth for the version) - **marketplace.json**: required top-level fields — `name`, `owner` (object with `name`), `plugins[]` diff --git a/skills/bootstrap/agents/claude-md-generator.md b/skills/bootstrap/agents/claude-md-generator.md index f96ac27..d5ed952 100644 --- a/skills/bootstrap/agents/claude-md-generator.md +++ b/skills/bootstrap/agents/claude-md-generator.md @@ -56,7 +56,7 @@ You are the CLAUDE.md generator: create or extend the project's file. - Non-obvious decisions and gotchas (extract from DOC_CONTENT — workarounds, limitations; from `DOMAIN_FINDINGS.CODE_WORKAROUNDS` — code-level workarounds) - Workflows and processes (extract from DOC_CONTENT — CI/CD, deploy, release flow) 4. If DOMAIN_FINDINGS and DOC_CONTENT overlap, prefer DOC_CONTENT (don't duplicate). -5. Add an "Artifact root (`.yoke/`)" section (see below for required content). +5. Add the "Artifact root (`.yoke/`)" and "Git memory (commit convention)" sections (see below for required content). 6. Write CLAUDE.md to the project root ### 4. Enrich mode (CLAUDE_MD_EXISTS = true) @@ -69,6 +69,7 @@ You are the CLAUDE.md generator: create or extend the project's file. - Non-obvious — are gotchas/workarounds captured? - Conventions — are project-specific conventions described? - Artifact root — is the `.yoke/` layout documented? (add if absent) + - Git memory — is the commit convention documented? (add if absent) 3. For each missing section — add it via Edit, using DOC_CONTENT as the source: - Project description — from README.md and other documentation files - Non-obvious decisions and gotchas — from CONTRIBUTING.md, docs/ @@ -104,6 +105,32 @@ Yoke stores all AI-generated artifacts under `.yoke/` at the project root: If the section already exists with equivalent content, do not duplicate it. +## Git memory (commit convention) section — required content + +Every generated or enriched CLAUDE.md must also contain this section (place it +next to the Artifact root section). When the project already documents a +commit format, merge — keep the project's ticket/type specifics and add the +memory rules; do not leave two competing commit sections. + +```markdown +## Git memory (commit convention) + +Commit messages are this project's long-term decision memory, written for +coding agents as much as for people. + +- Subject: `TICKET type(SLUG): description` — ticket first when one exists. +- A commit that carries a decision (chosen approach, rejected alternative, + non-obvious constraint) gets a prose body explaining **why**, then optional + decision trailers: `Constraint:`, `Rejected:`, `Directive:`, `Related:`. + Mechanical commits stay one-liners. +- Never add identity trailers (`Co-Authored-By`, `Signed-off-by`). +- Before modifying a file, read its memory: `git log -n 5 -- `. + Respect `Constraint:`, heed `Directive:`, do not re-propose `Rejected:` + approaches without new evidence. +- A `commit-msg` hook validates the subject shape and warns when a + feat/fix/refactor commit lacks a body. +``` + ## Rules - Preserve user content — sections added manually stay on update @@ -114,7 +141,7 @@ If the section already exists with equivalent content, do not duplicate it. - On create, use Write - Don't verify commands — the verifier does that - Reach Grade A against quality-criteria (90+ points) -- The "Artifact root (`.yoke/`)" section is mandatory in every output (create and enrich) +- The "Artifact root (`.yoke/`)" and "Git memory (commit convention)" sections are mandatory in every output (create and enrich) ## Response format diff --git a/skills/bootstrap/reference/bootstrap-pipeline.md b/skills/bootstrap/reference/bootstrap-pipeline.md index d9e265f..2f9fcd9 100644 --- a/skills/bootstrap/reference/bootstrap-pipeline.md +++ b/skills/bootstrap/reference/bootstrap-pipeline.md @@ -248,6 +248,12 @@ Dispatch 3 agents **in parallel** via the Agent tool: Result → RECOMMENDATIONS (list of automation recommendations). +4. **Git memory hook** (orchestrator, no agent) — after the agents return, + scaffold the `commit-msg` hook per `reference/hooks-patterns.md` § Git + memory commit-msg hook: into `.husky/commit-msg` when the project uses + husky, else `.git/hooks/commit-msg` (`chmod +x`). A `commit-msg` hook + already exists → do not touch it; record the fact in VERIFY_NOTES. + Wait for all 3. Mark in TodoWrite: `[x] Generate`. Transition → Phase 4. --- @@ -406,6 +412,9 @@ git add CLAUDE.md .yoke/ git commit -m "chore: bootstrap yoke flow context and .yoke/ scaffold" ``` +When Phase 3 wrote `.husky/commit-msg`, stage it too — `.git/hooks/` stays +local by nature and is never staged. + **Local-only** — `.yoke/` is gitignored; commit only the repo-level artifacts. `.yoke/flow.md` and `.yoke/yoke-context.md` stay on disk, untracked: diff --git a/skills/bootstrap/reference/hooks-patterns.md b/skills/bootstrap/reference/hooks-patterns.md index b1266fa..a2e416d 100644 --- a/skills/bootstrap/reference/hooks-patterns.md +++ b/skills/bootstrap/reference/hooks-patterns.md @@ -101,6 +101,49 @@ Claude Code supports hooks via `hooks.json` in a plugin or `settings.json` local } ``` +## Git memory commit-msg hook + +Enforces the commit convention from the "Git memory" section bootstrap adds to +CLAUDE.md: a malformed subject **blocks** the commit; a feat/fix/refactor +commit without a body gets a **warning** on stderr — an agent sees it and +amends, a human may ignore it. The hook never judges content, only shape and +presence. + +Location: `.husky/commit-msg` when the project uses husky (tracked, reaches +the whole team), otherwise `.git/hooks/commit-msg` with `chmod +x` (local +only — note that in the summary). Never overwrite an existing `commit-msg` +hook — leave it and report instead. + +```sh +#!/usr/bin/env sh +msg_file="$1" +subject=$(head -n 1 "$msg_file") + +case "$subject" in + Merge\ * | Revert\ * | fixup!* | squash!*) exit 0 ;; +esac + +if ! printf '%s' "$subject" | grep -qE '^(([A-Z][A-Z0-9]*-[0-9]+|#[0-9]+) )?(feat|fix|refactor|docs|test|chore|style|perf)(\([^)]+\))?: .+'; then + echo "commit-msg: bad subject '$subject'" >&2 + echo "commit-msg: expected 'TICKET type(SLUG): description' — see the Git memory section in CLAUDE.md" >&2 + exit 1 +fi + +type=$(printf '%s' "$subject" | sed -E 's/^(([A-Z][A-Z0-9]*-[0-9]+|#[0-9]+) )?([a-z]+).*/\3/') +case "$type" in + feat | fix | refactor) + if ! sed -n '2,$p' "$msg_file" | grep -v '^#' | grep -q '[^[:space:]]'; then + echo "commit-msg: warning — $type commit without a body; if it carries a decision, amend with the why (Git memory, CLAUDE.md)" >&2 + fi + ;; +esac + +exit 0 +``` + +No extra CLAUDE.md note is needed — the Git memory section already mentions +the hook. + ## Git hooks by stack ### Node.js — Prettier + ESLint diff --git a/skills/do/SKILL.md b/skills/do/SKILL.md index f5f2f85..4125730 100644 --- a/skills/do/SKILL.md +++ b/skills/do/SKILL.md @@ -91,6 +91,12 @@ These hold for every mode run: - **Commits by convention.** Format, ticket ID, and git initiative — from `${CLAUDE_PLUGIN_ROOT}/skills/gca/reference/commit-convention.md` (see its "Git initiative and defaults"). Do not ask "commit?" mid-run. +- **Read the git memory first.** Before modifying a file, read its recent + commit history per + `${CLAUDE_PLUGIN_ROOT}/skills/gca/reference/history-reading.md`. A + `Constraint:` or `Directive:` that conflicts with the plan is surfaced, + not silently overridden; a `Rejected:` approach is not re-proposed + without new evidence. - **Review after each task.** Spec compliance → code quality. Mandatory. - **Context isolation.** A sub-agent receives only its own task text, not the whole plan. diff --git a/skills/draft/SKILL.md b/skills/draft/SKILL.md index 2566dd9..5d64a3c 100644 --- a/skills/draft/SKILL.md +++ b/skills/draft/SKILL.md @@ -65,6 +65,10 @@ These hold for every run: `.yoke/ai//`. - **Commits by convention** per `${CLAUDE_PLUGIN_ROOT}/skills/gca/reference/commit-convention.md`. +- **Read the git memory first** per + `${CLAUDE_PLUGIN_ROOT}/skills/gca/reference/history-reading.md` — a Marker + must not re-propose what a `Rejected:` trailer already dismissed, and + Markup respects active `Constraint:` entries. - Language: match the ticket/input language, or follow the project-level definition in CLAUDE.md / AGENTS.md. diff --git a/skills/gca/reference/commit-convention.md b/skills/gca/reference/commit-convention.md index e3cfa88..638bc9e 100644 --- a/skills/gca/reference/commit-convention.md +++ b/skills/gca/reference/commit-convention.md @@ -1,6 +1,9 @@ # Commit Convention -Commit format for yoke skills and standalone invocations. +Commit format for yoke skills and standalone invocations. This is the write +side of the **git memory** (ADR-0012): commit messages carry the project's +decision history, written for coding agents as the primary readers. The read +side — how to recover that history — lives in `history-reading.md`. --- @@ -11,7 +14,7 @@ Plugin-wide contract for when the agent may run git and how commits are shaped. - **Authorization.** A skill invocation (`/do`, `/merge`, `/gca`, `/gp`, `/pr`) authorizes the git operations that skill performs — commit, push, PR — with no mid-run "commit?" questions. - **Initiative.** Outside skill runs the agent never commits or pushes on its own initiative — only on an explicit user command. - **Language.** Commit messages default to English; a project may override via `.yoke/flow.md` or its `CLAUDE.md`. -- **Trailers.** Never add trailer lines (`Co-Authored-By`, `Signed-off-by`, etc.). +- **Trailers.** Never add identity trailers (`Co-Authored-By`, `Signed-off-by`, and the like). Decision trailers from the Body section are different — they carry memory, use them. - **Identity.** Never fabricate committer identity (no `git -c user.email=...`); when identity is missing, ask the user. --- @@ -44,6 +47,69 @@ R2-220 fix(R2-220-fix-doubled-stats): restrict analytics --- +## Body — the git memory + +The subject says what; the body says **why**. The body is the Decision +Shadow — the context that lived in the author's head when the code was +written and dies with the session unless it lands here. A future agent +recovers it with `git log -- ` (see `history-reading.md`). + +### When a body is required + +By content, not by type. Write a body when the commit carries a decision — a +chosen approach, a rejected alternative, a non-obvious constraint. The test: +**will a reader six months out wonder "why"?** Mechanical commits (dependency +bumps, formatting, generated files) stay one-liners. + +### Shape + +1. **Prose** — one or two short paragraphs of reasoning: why this approach, + what constraint drove it, what almost worked. Plain sentences, no headings, + no diff narration. +2. **Decision trailers** — after the prose, from the fixed vocabulary: + +| Trailer | Carries | +| ------------- | --------------------------------------------------------------- | +| `Constraint:` | An active rule the code must keep respecting | +| `Rejected:` | An approach considered or tried and dismissed — with the reason | +| `Directive:` | A warning to whoever touches this code next | +| `Related:` | A pointer: ticket, commit hash, or `.yoke/` artifact path | + +Every trailer is optional — write one only when there is real content; repeat +a key for multiple entries. Only these four keys: a fixed vocabulary is what +keeps the history greppable (`git log --grep="^Rejected:"`). + +### Example + +``` +#86 feat(86-black-jack-page): add SSE endpoint + +Polling was dropped: the kiosk network kills idle HTTP/1.1 +connections after 30s, SSE with retry survives it. Score state +lives server-side because the client is untrusted. + +Rejected: WebSocket — no proxy support on kiosks +Constraint: client is untrusted, never move score calc there +Related: #84 +``` + +### Body anti-patterns + +``` +# WRONG — diff narration, not a decision: +Added handleRetry() that retries the request and updated the tests. + +# WRONG — ritual trailers with no content: +Constraint: none +Confidence: high # not in the vocabulary; self-assessment is noise + +# RIGHT — the decision and its reason: +Retry lives in the client because the gateway strips Retry-After; +see the rejected server-side attempt in a1b2c3d. +``` + +--- + ## Ticket ID Priority cascade: @@ -158,6 +224,8 @@ Commits for yoke flow artifacts (format `TICKET docs(SLUG): description`): - One commit — one logical change. - Ticket ID first in the message (if present). +- A commit that carries a decision gets a prose body and, when there is + content, decision trailers — see "Body — the git memory". - Avoid `wip`, `temp`, `misc`. - Staging/exclusion policy lives in `staging-strategy.md`: gca excludes only untracked secrets, keys, and >1MB binaries, and always commits tracked files, git-crypt included. Never exclude a file by authorship. - Git initiative, message language, trailers, and committer identity follow "Git initiative and defaults". diff --git a/skills/gca/reference/history-reading.md b/skills/gca/reference/history-reading.md new file mode 100644 index 0000000..56836c4 --- /dev/null +++ b/skills/gca/reference/history-reading.md @@ -0,0 +1,72 @@ +# History Reading + +The read side of the **git memory** (ADR-0012): how to recover the decision +history that `commit-convention.md` writes into commit messages. Run this +before touching or judging code — the history of a file carries constraints, +rejected approaches, and warnings that never made it into the code itself. + +--- + +## Before modifying a file + +```bash +git log -n 20 --format="%h %s" -- # quick pass over the intents +git log -n 5 -- # full messages: bodies + trailers +``` + +Act on what comes back: + +- `Constraint:` — an active rule. Respect it; when the plan conflicts with + it, surface the conflict instead of silently overriding. +- `Directive:` — a warning from a previous author. Heed it. +- `Rejected:` — a dead end already explored. Do not re-propose it without + new evidence; when you do, cite what changed. +- Prose bodies — the why behind the current shape. Read them before + concluding the code is wrong or accidental. + +## Before proposing an approach + +Check whether it was already tried and dismissed: + +```bash +git log --grep="^Rejected:" --format="%h %s%n%b" -- +``` + +`--grep` is line-oriented: `^Rejected:` anchors to a trailer line. Drop the +`-- ` to search the whole history. + +## Understanding one line + +```bash +git blame -L , # who last shaped these lines +git show # the full message — body and trailers +``` + +`git blame` gives the hash; `git show` gives the memory. Two steps from a +puzzling line to the decision behind it. + +## Searching wider + +```bash +git log --grep="" # search all messages +git log --follow -- # history across renames +``` + +## Trailer-aware output + +```bash +git log --format="%h %s%n%(trailers:key=Constraint,key=Directive,valueonly=false)" -- +``` + +`%(trailers:key=...)` prints only the named trailers — useful for harvesting +every active `Constraint:` over a directory in one pass. + +--- + +## Depth + +Ten subjects and the last five full messages per touched file is the default; +dig deeper (`--follow`, `--grep` over all history) only when a body references +older context or the file is central to the change. Older projects have +pre-memory history — a one-line commit with no body is normal there and +carries no signal beyond its subject. diff --git a/skills/grill-docs/SKILL.md b/skills/grill-docs/SKILL.md index 250dcb2..553a16f 100644 --- a/skills/grill-docs/SKILL.md +++ b/skills/grill-docs/SKILL.md @@ -29,6 +29,8 @@ If a question can be answered by exploring the codebase, explore the codebase in ## Domain awareness +During codebase exploration, also read the git memory: the affected files' commit history per `${CLAUDE_PLUGIN_ROOT}/skills/gca/reference/history-reading.md` — a past `Rejected:` or `Constraint:` trailer is grill material, cite it when challenging the plan. + During codebase exploration, also look for existing documentation: ### File structure diff --git a/skills/grill/SKILL.md b/skills/grill/SKILL.md index 2bcca0c..4272670 100644 --- a/skills/grill/SKILL.md +++ b/skills/grill/SKILL.md @@ -48,5 +48,6 @@ Stop when a full pass down the tree surfaces no new open decisions, or when the - Always include the recommended answer, listed first. - Resolve dependencies in order — don't jump branches. - Prefer exploring the codebase over asking when the codebase holds the answer. +- Exploring the codebase includes its git memory: read the affected files' commit history per `${CLAUDE_PLUGIN_ROOT}/skills/gca/reference/history-reading.md` — a past `Rejected:` or `Constraint:` trailer is grill material, cite it when challenging the plan. - For deeper grilling that also captures terminology and decisions into `.yoke/context.md` and ADRs, use `/yoke:grill-docs`. - Language: match the user's language, or follow the project-level definition in CLAUDE.md / AGENTS.md. diff --git a/skills/issues/SKILL.md b/skills/issues/SKILL.md index e86fedf..d82e833 100644 --- a/skills/issues/SKILL.md +++ b/skills/issues/SKILL.md @@ -20,7 +20,7 @@ Work from whatever is already in the conversation context. If the user passes an ### 2. Explore the codebase (skip if already explored this session) -If the codebase has not been explored this session, explore it now to understand its current state. Follow the domain-doc consumer rules in `${CLAUDE_PLUGIN_ROOT}/skills/grill-docs/reference/domain-docs.md`: read `.yoke/context.md` and relevant `.yoke/adr/`, give issue titles and descriptions the glossary's vocabulary, and flag any ADR a slice contradicts. +If the codebase has not been explored this session, explore it now to understand its current state. Follow the domain-doc consumer rules in `${CLAUDE_PLUGIN_ROOT}/skills/grill-docs/reference/domain-docs.md`: read `.yoke/context.md` and relevant `.yoke/adr/`, give issue titles and descriptions the glossary's vocabulary, and flag any ADR a slice contradicts. Also read the git memory of the areas the slices touch per `${CLAUDE_PLUGIN_ROOT}/skills/gca/reference/history-reading.md` — a slice that re-proposes a `Rejected:` approach or crosses a `Constraint:` must say so. ### 3. Draft vertical slices diff --git a/skills/prd/SKILL.md b/skills/prd/SKILL.md index a378abf..b6dbfec 100644 --- a/skills/prd/SKILL.md +++ b/skills/prd/SKILL.md @@ -14,7 +14,7 @@ Turn the current conversation and codebase understanding into a PRD. Do NOT inte ## Process -1. **Explore the repo** to understand the codebase, if not already done. Follow the domain-doc consumer rules in `${CLAUDE_PLUGIN_ROOT}/skills/grill-docs/reference/domain-docs.md`: read `.yoke/context.md` and the relevant `.yoke/adr/`, use the glossary's vocabulary throughout the PRD, and flag any ADR the PRD contradicts. +1. **Explore the repo** to understand the codebase, if not already done. Follow the domain-doc consumer rules in `${CLAUDE_PLUGIN_ROOT}/skills/grill-docs/reference/domain-docs.md`: read `.yoke/context.md` and the relevant `.yoke/adr/`, use the glossary's vocabulary throughout the PRD, and flag any ADR the PRD contradicts. Also read the git memory of the modules the PRD touches per `${CLAUDE_PLUGIN_ROOT}/skills/gca/reference/history-reading.md` — active `Constraint:` entries and past `Rejected:` approaches belong in Implementation Decisions. 2. **Sketch the major modules** to build or modify. Look for deep modules to extract that can be tested in isolation. A deep module encapsulates substantial functionality behind a simple, testable interface that rarely changes. Decide the module breakdown and which modules are test-worthy yourself, and record both in the PRD's Implementation Decisions and Testing Decisions sections — do not stop to interview the user. diff --git a/skills/review/SKILL.md b/skills/review/SKILL.md index ec3c665..eaabf3f 100644 --- a/skills/review/SKILL.md +++ b/skills/review/SKILL.md @@ -34,4 +34,5 @@ The full pipeline — scope resolution, the code-reviewer dispatch, finding clas - **Report location.** Write the report to `.yoke/ai//-review.md`. - **Every fork is one question.** For any decision — fix scope, next action — ask a single AskUserQuestion with the recommended option listed first. - **Delegate the work.** File edits, bash, and analysis go to sub-agents; each receives only its own data. +- **Read the git memory.** Pull decision context from the affected files' commit history per `${CLAUDE_PLUGIN_ROOT}/skills/gca/reference/history-reading.md` — a finding that contradicts a `Constraint:` or re-proposes a `Rejected:` approach must cite the commit it argues with. - Language: match the ticket/input language, or follow the project-level definition in CLAUDE.md / AGENTS.md.