commit as part of the loop, and announce it when unasked

The loop gains a seventh step. Work was being committed all session on a
verbal instruction, which means it was a property of one conversation
rather than of the harness: a fresh session would have left everything in
the working tree and reported success.

Why automatic: a commit is one `git revert` from undone, and work that
only ever existed in the working tree is not recoverable — this session
already lost uncommitted work to a `git checkout` while signing was
broken. Why announced: the safety argument covers the loss, not the
surprise, so a session that did not ask for this gets one prominent line
before the first commit and the off switch, once.

Ownership split so nothing is stated twice: conventions.md "Git" owns
what one commit contains — now one *revertible* unit rather than one
feature, which is the human's correction to the "never bundle two passes"
reading — and CLAUDE.md §4 owns when a commit happens. ADR-0052 records
both, including that no gate can check this: no script can see whether a
sentence was said.

Docs 6 files, +72/-6 lines. No code, no counters moved.
This commit is contained in:
Claude Opus 5
2026-08-01 01:41:11 +06:00
committed by bdeshi
parent de1ce73430
commit 8256c72180
6 changed files with 92 additions and 9 deletions
+40 -2
View File
@@ -1,6 +1,6 @@
# The feature loop
Six steps, in order: **Clarify → Plan → Implement → Verify → Document → Report.**
Seven steps, in order: **Clarify → Plan → Implement → Verify → Document → Commit → Report.**
`CLAUDE.md` holds the rules; this file holds the procedure.
Scale, do not skip. A one-sentence request gets one line of plan and a short report — and still gets
@@ -135,7 +135,41 @@ grep -rn '<old form>' docs CLAUDE.md HARNESS.md ideas reference .claude scripts
a sentence that is merely now untrue, or a concept renamed in one place — that sweep is the author's,
and skipping it is how a doc ends up contradicting the file it points at.
## 6. Report
## 6. Commit
Not optional and not asked for: the work is committed before the report, every time. `CLAUDE.md` §4
holds the rule; this is how.
**The notice, if this session did not ask for automatic commits.** Before the first commit only, on
its own line, visible:
```
**Committing this to git** — and every later batch of changes in this session. Say "stop committing"
and I will leave the tree dirty instead.
```
Once said, it is not repeated. If the human asked for automatic commits — in this session or by
having put the rule here — skip the notice entirely; repeating a notice for something already agreed
is noise that trains them to skim the next one.
**What goes in one commit.** The changes that would be reverted together (`conventions.md` "Git").
Bundle freely inside one unit: code with its test, its `state.md` row and its ADR are one commit
because reverting the code without the doc leaves a lie. Split when a second pass is independently
revertible — a fix to something you noticed afterwards, or a doc correction that was already wrong
before this change. "Which of these would I want to undo alone?" answers it faster than any rule.
**How.**
- `git status` first: an unexpected file is a stop condition, not something to sweep into `git add -A`.
- Stage what the change touched, then check `git diff --cached --stat` against the plan's file list.
- The pre-commit hook runs `verify.sh`, so a red gate blocks the commit. That is the design — fix the
gate or the code, do not reach for `--no-verify`.
- Subject imperative, under 72 characters. Body says *why*, plus the numbers the change earned
(LOC, counters, a benchmark) since `git log` is the map when no agent is available.
- Never `git push`. Publishing is the human's; committing is bookkeeping.
- Scratch files, `ideas/`, `reference/` and anything under the scratchpad stay out.
## 7. Report
Short — one line each, no prose unless a conflict or a stop condition needs explaining. Drop the lines
that are genuinely n/a rather than padding them.
@@ -145,6 +179,7 @@ Did: what now works, in the user's terms
Evidence: the command you ran and its result
Diff: files touched, ±LOC
Propagated: content / code / templates / docs — each done or n/a, with content files named
Committed: the commit subjects this produced, one line each
Earned: counters after this change; anything now due for extraction
Skipped: what you deliberately did not do, and the latent items you logged
Conflicts: hard ones surfaced and how they were settled, soft ones deviated from, or "none"
@@ -171,3 +206,6 @@ Swept: the old form you grepped for after a rename, or "n/a"
| Made a field optional, left the ADR mandating it | Contradicted, not stale | Supersede the ADR |
| Treated a one-sentence request as a narrow change | Articulation size is not blast radius | Walk the propagation table |
| Splitting the difference between request and policy | A compromise nobody chose | Surface it; the human picks |
| Ending a turn with the work only in the working tree | The one copy is the one that gets lost | Commit before reporting |
| One commit per file, or per doc touched | Shredding a single revertible unit | Bundle what would be undone together |
| Committing without ever saying you would | Consent assumed, not given | The notice, once, before the first one |