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:
@@ -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 |
|
||||
|
||||
@@ -79,8 +79,8 @@ the test: `docs/architecture.md`.
|
||||
|
||||
## 4. The loop (every request, no exceptions)
|
||||
|
||||
`Clarify → Plan → Implement → Verify → Document → Report`.
|
||||
Procedure: `.claude/skills/khosra-feature-loop/SKILL.md`. The two gates people skip:
|
||||
`Clarify → Plan → Implement → Verify → Document → Commit → Report`.
|
||||
Procedure: `.claude/skills/khosra-feature-loop/SKILL.md`. The three gates people skip:
|
||||
|
||||
**Clarify.** Only questions whose answer changes the code or the bytes on disk. Max three,
|
||||
batched, up front, each with a **bold** default so silence answers. Never about naming, formatting,
|
||||
@@ -90,6 +90,18 @@ or anything `docs/conventions.md` decides. None to ask? State assumptions in one
|
||||
ran (golden file, `curl`, test name, benchmark). Never report success from reading your own diff.
|
||||
"Should work" is not a result.
|
||||
|
||||
**Commit.** Work lands in git without being asked, every time, before you report. What one commit
|
||||
contains is `docs/conventions.md` "Git": the changes that would be reverted together, bundled
|
||||
together. Never `git push`, and never `--no-verify` without saying why in the body. Asymmetry is the
|
||||
whole reason — a commit is one `git revert` away from undone, while work that only ever existed in
|
||||
the working tree is not recoverable at all.
|
||||
|
||||
**Announce it once per session, prominently, when it was not asked for.** If the human has not
|
||||
already asked for automatic commits in *this* session, the first commit is preceded by a visible
|
||||
line of its own — not a clause inside the report — saying that this change is being committed, that
|
||||
later changes will be too, and that saying so turns it off. Silence is consent only after the notice
|
||||
exists; before it, an unannounced commit is a surprise in someone else's repository.
|
||||
|
||||
## 5. Definition of done
|
||||
|
||||
- [ ] Plan's success criteria demonstrated with real output.
|
||||
@@ -97,6 +109,7 @@ ran (golden file, `curl`, test name, benchmark). Never report success from readi
|
||||
- [ ] Diff contains nothing outside the planned files.
|
||||
- [ ] `docs/state.md` updated (inventory, counters, latent items, verified-at line).
|
||||
- [ ] ADR in `docs/decisions.md` if a load-bearing choice was made.
|
||||
- [ ] Committed — one revertible unit per commit, announced if this session did not ask for it.
|
||||
- [ ] Report states LOC delta, what is now earned, what you deliberately did not do.
|
||||
|
||||
## 6. Stop conditions — halt and ask
|
||||
|
||||
+11
-3
@@ -12,7 +12,7 @@ engine — anything in it may be pulled into context on demand. `ideas/` and `re
|
||||
## How you use it
|
||||
|
||||
You ask for a feature. The skill fires by itself and runs
|
||||
`Clarify → Plan → Implement → Verify → Document → Report`.
|
||||
`Clarify → Plan → Implement → Verify → Document → Commit → Report`.
|
||||
|
||||
This runs for casual requests too — the skill fires on "the default-language files do not need the
|
||||
`.en` part" exactly as it does on "add tag pages", with a one-line plan instead of a full one. A short
|
||||
@@ -26,7 +26,7 @@ need to run there rather than pretending to have done it.
|
||||
3. **Agent:** a short plan — goal, primitive, success criteria, files, ±LOC, deps, earn-it check,
|
||||
and what it is deliberately *not* doing.
|
||||
4. **You:** "go", or edit the plan. "Just do it" skips the gate on small changes.
|
||||
5. **Agent:** implements, runs `./scripts/verify.sh`, shows real output, updates docs, reports.
|
||||
5. **Agent:** implements, runs `./scripts/verify.sh`, shows real output, updates docs, commits, reports.
|
||||
|
||||
Occasional maintenance, by you:
|
||||
|
||||
@@ -70,6 +70,14 @@ change detection. Treat a *second* raise as evidence that something belongs in `
|
||||
two ceilings exist so that "core stops growing, ext rises" is observable, and that stops being true the moment
|
||||
leaves are allowed into core.
|
||||
|
||||
**The agent commits by itself, and tells you the first time.** Work is committed as part of the loop
|
||||
rather than on request (ADR-0052) — one commit per revertible unit, never a `push`. In a session where
|
||||
you have not asked for that, the first commit is preceded by a line saying so and naming the off
|
||||
switch ("stop committing"), because a commit appearing in your repository unannounced is a surprise
|
||||
even when it is harmless. The asymmetry is the argument: a commit you did not want is one `git revert`
|
||||
away, and work that existed only in the working tree when something went wrong is gone. If you want
|
||||
the tree left dirty, say so and the loop stops at Document.
|
||||
|
||||
**What is staged has to build, not just the working tree.** Every other gate looks at your files; this one
|
||||
looks at the commit you are about to make, by building a throwaway checkout of the index. It exists because
|
||||
staging a subset — a changed signature without its caller — produced a commit that did not compile while
|
||||
@@ -90,7 +98,7 @@ In order, cheapest first:
|
||||
`docs/state.md` has fallen behind the code.
|
||||
2. `docs/state.md` — what exists, the earn-it counters, the latent list, the commit it was last verified
|
||||
against. This is the only doc that describes the present.
|
||||
3. `git log --oneline` — one feature per commit, each body saying *why* (`conventions.md`). This is the
|
||||
3. `git log --oneline` — one revertible unit per commit, each body saying *why* (`conventions.md`). This is the
|
||||
real map of how the code got here.
|
||||
4. `/refresh-docs` — reconciles every doc against the actual code and reports drift, which is exactly the
|
||||
question you have after a year.
|
||||
|
||||
@@ -40,6 +40,7 @@ that is the finding to report.
|
||||
| whether a future feature is worth building | `exploration.md` + `roadmap.md` |
|
||||
| what to build next | `.scratch/build-queue.md` if present, otherwise `git log` and `roadmap.md` |
|
||||
| how the harness works — a gate, counter, budget, the loop | `HARNESS.md` + `scripts/verify.sh` + `CLAUDE.md` |
|
||||
| what one commit contains, and when one happens | `conventions.md` "Git" (shape) + `CLAUDE.md` §4 (cadence, ADR-0052) |
|
||||
| a tool version, or why agent tooling stopped working | `toolchain.md` |
|
||||
| templates, layout, presentation, what a theme can rely on | `theme-contract.md` (+ ADR-0019, ADR-0023) |
|
||||
|
||||
|
||||
+7
-2
@@ -100,5 +100,10 @@ Page-specific styles and scripts come from the bundle (`styles`/`scripts` frontm
|
||||
this project, not an optimisation.
|
||||
|
||||
## Git
|
||||
One feature per commit. Imperative subject under 72 characters; body says *why*. Doc updates ride in
|
||||
the same commit as the code that made them true.
|
||||
One revertible unit per commit: what would be undone together belongs together, what would be undone
|
||||
alone gets its own commit. Usually that is one feature — code, test, `state.md` row and ADR in one
|
||||
commit, because reverting the code without the doc leaves a lie. A correction to something that was
|
||||
already wrong beforehand is a separate unit even when it lands in the same sitting.
|
||||
|
||||
Imperative subject under 72 characters; body says *why*, and carries the numbers the change earned.
|
||||
Committing happens by itself and needs no request (`CLAUDE.md` §4); pushing never does.
|
||||
|
||||
@@ -770,3 +770,21 @@ JPEGs, and a feature added without a case in the coverage test is still invisibl
|
||||
what "done" means.
|
||||
Revisit if: the example grows big enough to slow the test suite, or someone wants several examples — then this
|
||||
is `examples/<name>/` with the coverage test parameterised, not a second mechanism.
|
||||
|
||||
## ADR-0052 — Committing is part of the loop, and unasked-for automation is announced once
|
||||
Date: 2026-08-01 · Status: accepted
|
||||
Decision: the loop gains a seventh step — the agent commits its own work before reporting, one revertible
|
||||
unit per commit, never pushing. In a session where the human has not asked for it, the first commit is
|
||||
preceded by a prominent line naming what is happening and how to stop it; after that, silence is consent.
|
||||
Why: the human's reason, and it is the right one — a commit is trivially reverted, while work that only ever
|
||||
existed in the working tree cannot be recovered with certainty. This session lost uncommitted work once
|
||||
already, to a `git checkout` while signing was broken. The notice exists because the safety argument covers
|
||||
the *loss*, not the *surprise*: committing into someone's repository without having said you would is a
|
||||
different failure from committing too much.
|
||||
Consequence: cheap — every step of a long autonomous run is bisectable, and a bad turn costs one revert
|
||||
instead of a reconstruction. Expensive — `git log` now carries the agent's pacing, so a sloppy unit boundary
|
||||
is permanent noise; and this is behaviour no gate can check, since no script can see whether a sentence was
|
||||
said. `conventions.md` "Git" owns what one commit contains; `CLAUDE.md` §4 owns when one happens.
|
||||
Revisit if: the log fills with commits nobody would revert separately — then the unit is wrong, not the
|
||||
automation; or the human wants a review gate before anything lands, which is a different default, not a
|
||||
tweak to this one.
|
||||
|
||||
Reference in New Issue
Block a user