diff --git a/changelog.d/lean-full-records.md b/changelog.d/lean-full-records.md new file mode 100644 index 0000000..b452aa6 --- /dev/null +++ b/changelog.d/lean-full-records.md @@ -0,0 +1 @@ +- **A full-mode feature keeps one plan file and one closing record.** The owner's request is `plan.md` § Request (no `feature.md`). The retro lives in the feature's learnings file, with § What went wrong and action items only when something did (no `retro.md`). After QA pass the main chat reads QA's record instead of re-running the checks, Full-stack updates the project fact docs as plan steps, and the close takes three steps instead of a retro file and its checks. The plan checklist's group index moved into `harness/TECHNICAL_LEAD.md`, so the main chat prints only the groups a plan needs, in one call. diff --git a/scripts/kit-selftest.sh b/scripts/kit-selftest.sh index b0729e1..b3c27a9 100755 --- a/scripts/kit-selftest.sh +++ b/scripts/kit-selftest.sh @@ -1079,6 +1079,70 @@ def check_doc_sections(bad, t): bad(f"only {seen} doc-section.sh calls found in the rendered docs: harness/TECHNICAL_LEAD.md § Full mode, step by step names the sections to read") +def check_lean_records(bad, t): + """I3: a full-mode feature keeps one plan file (the request is its § Request) and one closing record (the + learnings file, with the retro inside it and § What went wrong only when something did); the main chat prints + only the checklist groups the plan needs, in one call, from the index in TECHNICAL_LEAD.md.""" + for gone in ("harness/templates/feature.md", "harness/templates/retro.md"): + if os.path.exists(os.path.join(t, gone)): + bad(f"{gone} is still installed: the request is plan.md § Request and the retro is in the learnings file") + for path in files_under(t, exts=(".md",)): + rel = os.path.relpath(path, t) + # _harness/LEARNINGS.md is a run record an update never rewrites, so its intro keeps the old wording + if rel.startswith(("_harness/features", "node_modules", "updates/", "CHANGELOG")) or rel == "_harness/LEARNINGS.md": + continue + m = re.search(r"[\w/]*(?:feature|retro)\.md", read(path)) + if m: + bad(f"{rel} still names {m.group(0)}: full mode has no feature.md or retro.md") + plan = read(os.path.join(t, "harness/templates/plan.md")) + req = plan.split("\n## Request\n", 1)[1].split("\n## ", 1)[0] if "\n## Request\n" in plan else "" + for want in ("**What we want:**", "**Why:**", "**Success looks like:**"): + if want not in req: + bad(f"harness/templates/plan.md § Request doesn't ask for {want!r}") + if "project fact docs" not in plan.split("\n## Implementation steps\n", 1)[-1].split("\n## ", 1)[0]: + bad("harness/templates/plan.md § Implementation steps doesn't make the project fact docs Full-stack steps") + entry = read(os.path.join(t, "harness/templates/learnings-entry.md")) + went = entry.split("\n## What went wrong\n", 1)[1].split("\n## ", 1)[0] if "\n## What went wrong\n" in entry else "" + if not went or "`None.`" not in went or "**Retro:**" in entry or "Agent runs" in entry: + bad("harness/templates/learnings-entry.md isn't the one closing record: § What went wrong, `None.` when nothing did, no retro link") + setup = read(os.path.join(t, "harness/SETUP.md")) + after = setup.split("\n## After QA pass\n", 1)[-1].split("\n## ", 1)[0] + if "don't run the checks again" not in after: + bad("harness/SETUP.md § After QA pass doesn't stop the main chat re-running the checks QA recorded") + retro = setup.split("\n## Retrospective\n", 1)[-1].split("\n## ", 1)[0] + if "**Close in few calls:**" not in retro or "writes `None.`" not in retro: + bad("harness/SETUP.md § Retrospective doesn't close in one call, or still asks for a retro when nothing went wrong") + # the group index in TECHNICAL_LEAD.md names every group of PLAN_CHECKLIST.md with its item range + tl = read(os.path.join(t, "harness/TECHNICAL_LEAD.md")) + rows = dict(re.findall(r"^\| ([A-Z][^|]+?) \| (\d+-\d+) \|", tl.split("\n## Before asking for approval\n", 1)[-1], re.M)) + pc = read(os.path.join(t, "harness/PLAN_CHECKLIST.md")).split("\n## Before asking for approval\n", 1)[-1] + groups = re.split(r"^### ", pc, flags=re.M)[1:] + if not groups: + bad("harness/PLAN_CHECKLIST.md § Before asking for approval has no groups") + for g in groups: + name = g.split("\n", 1)[0].strip() + nums = [int(n) for n in re.findall(r"^(\d+)\. ", g, re.M)] + want = f"{min(nums)}-{max(nums)}" if nums else "?" + if rows.get(name) != want: + bad(f"harness/TECHNICAL_LEAD.md § Before asking for approval's group index has {rows.get(name)!r} for {name!r}, " + f"PLAN_CHECKLIST.md has items {want}: keep the index in step") + skip = dict(re.findall(r"^\| ([A-Z][^|]+?) \| \d+-\d+ \| ([^|]+?) \|", tl, re.M)) + for name in ("Planning and scope", "Testing", "Process"): + if "always run it" not in skip.get(name, ""): + bad(f"harness/TECHNICAL_LEAD.md's group index lets a plan skip {name!r}: it always runs") + for name, want in (("Security", ("adds or changes", "permission", "route", "access gap")), ("Data", ("migrates",)), + ("Release and operations", ("adds or changes", "ships separately"))): + if not all(w in skip.get(name, "") for w in want): + bad(f"harness/TECHNICAL_LEAD.md's group index skips {name!r} on a narrower condition than before ({want!r})") + steps = re.findall(r"^(\d+)\. (.*)$", after, re.M) + if dict(steps).get("4", "").split("**")[1:2] != ["Human-only steps."]: + bad("harness/SETUP.md § After QA pass step 4 isn't the human-only steps: other docs cite it as step 4") + if len(rows) != len(groups): + bad(f"harness/TECHNICAL_LEAD.md's group index has {len(rows)} rows for {len(groups)} checklist groups") + if '"Group index"' in tl: + bad("harness/TECHNICAL_LEAD.md still prints PLAN_CHECKLIST.md § Group index: the index is in TECHNICAL_LEAD.md") + + def check_risk_review(bad, t): """H2: the code review before QA runs for every full or bug plan on a risk path, not only at xhigh; xhigh is for sign-in, secrets and money (harness/SETUP.md § Code review before QA, § Choosing a level).""" @@ -1679,6 +1743,7 @@ def check(name, fx, t): check_small_bugs(bad, t) check_risk_review(bad, t) check_doc_sections(bad, t) + check_lean_records(bad, t) # 6. named checks have commands, and no row names an unknown check cfg = lambda key: run(["bash", "-c", f'. scripts/lib/config.sh; config_get {key}'], t).stdout.strip() names = set() @@ -2270,6 +2335,18 @@ def check(name, fx, t): bad(f"evidence.sh printed a summary that left most entries out (exit {r.returncode}): {r.stdout!r}") open(lf, "w").write(keep) os.remove(pf) + # I3: a bug-mode learnings file has no retro line; its Escaped line marks the mode + bf = os.path.join(t, "_harness/learnings/selftest-bug-v1.md") + open(bf, "w").write("# selftest-bug v1 learnings\n\n### selftest-bug v1, 2026-01-03\n- **Verdict:** pass; QA rounds: 1; review round: no\n" + "- **Escaped:** notes-list v1, AC 3 missed the tie\n\n## What went wrong\n\n1. (cause: plan gap) the tie; AC 3\n") + r = run(["bash", "scripts/evidence.sh", "--json"], t) + try: + modes = json.loads(r.stdout).get("modes_unfolded", {}) + except ValueError: + modes = {} + if modes.get("bug") != 1: + bad(f"evidence.sh doesn't count a bug-mode learnings file (one with an Escaped line) as bug mode: {modes!r}") + os.remove(bf) # evidence.sh options are stable (STABILITY.md § Stable item 7): the field names of --json (none renamed # or removed; new ones may be added), --out files, a bad option fails r = run(["bash", "scripts/evidence.sh", "--json"], t) diff --git a/skills/morph-init/references/catalog.md b/skills/morph-init/references/catalog.md index 1c4124a..4186e64 100644 --- a/skills/morph-init/references/catalog.md +++ b/skills/morph-init/references/catalog.md @@ -56,7 +56,7 @@ it after install rather than guessing a repo name. | Supabase | Supabase | user connector | Read-only checks on remote (list tables, advisors, logs) in data-access stage B. Account-wide: record the project ref to target in `project/STACK.md`. | | Error tracking in use (Sentry, PostHog, etc.) | That vendor's connector | user connector | Lets the TL and QA read real errors when planning a fix. | | Hosting, DNS, storage or app builds with an official MCP (Vercel, Cloudflare, Expo, etc.) | That vendor's connector | user connector | Read deploy status and logs, and let the Technical Lead do setup work (create a project, set a non-secret variable, add a domain) instead of handing it to you. Every write is asked in chat first, one approved action at a time. | -| Issue tracker (Linear, Jira, GitHub Issues) | That vendor's connector | user connector | Lets the TL pull the ask and acceptance notes straight into `feature.md`. | +| Issue tracker (Linear, Jira, GitHub Issues) | That vendor's connector | user connector | Lets the TL pull the ask and acceptance notes straight into `plan.md` § Request. | A remote connector (such as Context7's hosted server) can't be pinned: its text comes from the vendor's server at each call. Say so when suggesting it. diff --git a/template/_harness/README.md b/template/_harness/README.md index 1542388..ccbc014 100644 --- a/template/_harness/README.md +++ b/template/_harness/README.md @@ -9,10 +9,8 @@ features/ / / Plan/ - feature.md - plan.md + plan.md # TL: the request, the plan and its acceptance criteria review.md # TL: code review findings (risk path or Full-stack xhigh) - retro.md # TL: retrospective (after QA pass or escalation) Research/ .md # researcher: optional research before the plan Legal/ diff --git a/template/_harness/learnings/README.md b/template/_harness/learnings/README.md index b79692c..7bdaf56 100644 --- a/template/_harness/learnings/README.md +++ b/template/_harness/learnings/README.md @@ -3,7 +3,7 @@ Each feature's retro entry and its new action items live in their own file here, `-v.md`, so two feature branches never edit the same lines (a shared learnings file is the most common merge conflict between parallel branches). - **Written by** the Technical Lead with the feature's retro, on the feature branch ([harness/SETUP.md](../../harness/SETUP.md) § Retrospective). -- **Format:** a `# v learnings` title, the entry in the format of [`LEARNINGS.md`](../LEARNINGS.md) § Entry format, then `## Action items` with a table in the columns of `LEARNINGS.md` § Action items → Open (`From retro | # | Type | Action | Why raised | Status | Note`), or "None". +- **Format:** [harness/templates/learnings-entry.md](../../harness/templates/learnings-entry.md): a `# v learnings` title, the entry, `## What went wrong` (one line per miss, or `None.`), then `## Action items` with a table in the columns of `LEARNINGS.md` § Action items → Open (`From retro | # | Type | Action | Why raised | Status | Note`), or "None". - **Decisions:** the approve / skip / defer is the Technical Lead's, except an item that adds a step for the owner, changes product behaviour or customer-facing copy, or costs money, which the owner decides. It is recorded in the Status column of this file; an item applied later names the feature or file in Note. - **Prune counter:** the number of `*-v*.md` files here is the number of features since the last prune (`bash scripts/prune-counter.sh` counts them with the integration branch's and prints the line for the hand-off); the prune is due at `loop.prune_every` in `harness.config.yml`. - **Folded by** the prune: it condenses each entry to one line in `LEARNINGS.md` § Entries, moves each action item row to the matching `LEARNINGS.md` table, and deletes the file. diff --git a/template/harness/HANDOFFS.md b/template/harness/HANDOFFS.md index fc2ffd0..33be8c0 100644 --- a/template/harness/HANDOFFS.md +++ b/template/harness/HANDOFFS.md @@ -4,16 +4,15 @@ | File | Created by | May edit | Notes | |------|------------|----------|--------| -| `Plan/feature.md` | Human (draft OK with Tech Lead) | Human; Tech Lead before plan approved | Plain-language intent | | `Plan/plan.md` | Technical Lead | Tech Lead until approved; then frozen except Status, Steps file, Approval, Open questions, Decisions, and a small plan's § Check | Includes acceptance criteria | | Steps file | Technical Lead | Technical Lead; the human ticks items | The human's steps after QA pass, or none (§ Steps file below; [SETUP.md](./SETUP.md) § After QA pass, step 4) | | `Plan/review.md` | Technical Lead | Technical Lead only | Pre-QA code review findings: plans on a risk path or at Full-stack `xhigh` ([SETUP.md](./SETUP.md) § Code review before QA) | -| `Plan/retro.md` | Technical Lead | Technical Lead only | Retrospective after pass or escalation ([templates/retro.md](./templates/retro.md), [SETUP.md](./SETUP.md) § Retrospective) | | `Legal/legal-copy.md` | Legal writer | Legal writer only | Exact legal sentences and their checks; only when the plan says **Legal wording: yes** ([modules/legal/](./modules/legal/README.md)) | | `_harness/research/-.md`, `Research/.md` | Technical Lead | TL, all of it: above the first line and below the second (§ Check, § Decision, § How it went) in its own words, and between the two lines the sections of the researcher's reply, word for word (the researcher has no edit tools; [templates/research.md](./templates/research.md)) | One question with cited findings ([MODES.md](./MODES.md) § Research mode) | | `Development/progress.md` | Full-stack engineer | Full-stack engineer only | Tracker and validation | +| Project fact docs (`project/PROJECT.md` facts, domain docs) | Install, then plans | Full-stack for the facts a plan's steps name; the Technical Lead otherwise | Rule and process docs in `project/` stay the Technical Lead's ([agents/fullstack-engineer.md](./agents/fullstack-engineer.md) § Ownership limits) | | `QA/qa-result.md` | QA engineer | QA engineer only | Verdict and evidence | -| `_harness/learnings/-v.md` | Technical Lead | Technical Lead; the prune folds it | Retro entry and action items ([templates/learnings-entry.md](./templates/learnings-entry.md)) | +| `_harness/learnings/-v.md` | Technical Lead | Technical Lead; the prune folds it | The feature's closing record: verdict, cost, what went wrong and action items ([templates/learnings-entry.md](./templates/learnings-entry.md)) | **Application code:** Full-stack engineer, or the main chat itself in light and small mode, where no subagent runs ([MODES.md](./MODES.md) § Working alone). **No product code edits:** QA engineer, the Technical Lead in full and bug mode, researcher. One exception: in bug mode QA writes the regression test the plan assigns, before the fix ([MODES.md](./MODES.md) § Bug mode). @@ -128,9 +127,9 @@ Read: /Plan/plan.md (must be approved). Implement the plan in the application codebase. Add or update a test for each behaviour the change adds or changes (.claude/rules/testing.md): one that fails when that behaviour breaks. Path rules: . Read them before the first edit. -Update only: /Development/progress.md +Update only: /Development/progress.md, and the project fact docs the plan's steps name (never their source-path list, never a doc of rules or process) Include Started and Ended in progress.md when handing off. -Do not edit plan.md, feature.md, qa-result.md, the steps file or Legal/legal-copy.md. +Do not edit plan.md, qa-result.md, the steps file or Legal/legal-copy.md. Run every before_qa check in harness/CONTROLS.md § Before Full-stack → QA handoff (commands: project/CONTROLS.md), scoped by § Scoping by what changed; record each check, its scope and exit code in progress.md § Validation (required before handoff). The checks.on_stop checks are recorded as end-of-task hook: they run when you finish (harness/CONTROLS.md § End-of-task checks). Do not mark Ready for QA until validation is recorded. Run long checks to completion before handing back. A check longer than the shell's foreground limit runs as a background command with a long enough time limit, and you wait for it to finish in the same turn. A job that outlives your turn is not allowed. diff --git a/template/harness/MODES.md b/template/harness/MODES.md index 9a6b794..3e0e8b3 100644 --- a/template/harness/MODES.md +++ b/template/harness/MODES.md @@ -21,7 +21,7 @@ Working alone, the main chat follows the builder's rules as well as its own: 1. Create the feature folder under `_harness/features///`. 2. Technical Lead → Full-stack engineer → QA engineer per [STRATEGY.md](./STRATEGY.md) and [SETUP.md](./SETUP.md). QA is lean: the Technical Lead hands it the plan, the diff and the check results, and it reviews the diff and makes one pass over the criteria, at `medium` unless Full-stack is `xhigh` ([agents/qa-engineer.md](./agents/qa-engineer.md) § Lean QA). On a risk path, or at `xhigh`, the Technical Lead's code review runs between them ([SETUP.md](./SETUP.md) § Code review before QA). 3. Run the checks from [CONTROLS.md](./CONTROLS.md) before each handoff. -4. Retro and learnings file at the end ([SETUP.md](./SETUP.md) § Retrospective). +4. The learnings file at the end, with the retro inside it ([SETUP.md](./SETUP.md) § Retrospective). ## Light mode @@ -43,10 +43,10 @@ Working alone, the main chat follows the builder's rules as well as its own: The main chat plans, builds and checks the change itself (§ Working alone); the plan and the merge gate stay (learned: on small, fully specified changes, full mode cost many times a plain session for the same result). -1. **Plan.** Read the code the change touches, then write `_harness/features///Plan/plan.md` from [templates/small-plan.md](./templates/small-plan.md): the risk check's output, the change, the files, 3 to 8 acceptance criteria with their tests, and the limit. No `feature.md` and no plan checklist run. The human approves it as any plan. +1. **Plan.** Read the code the change touches, then write `_harness/features///Plan/plan.md` from [templates/small-plan.md](./templates/small-plan.md): the risk check's output, the change, the files, 3 to 8 acceptance criteria with their tests, and the limit. No plan checklist run. The human approves it as any plan. 2. **Build.** Make the change and write the tests the criteria name, then run the `before_qa` checks. A change that needs more than § Limit allows stops before the change: write a full plan with this one linked. 3. **Check.** On the final commit, run the `before_signoff` checks in full and fill the plan's § Check: each check with its exit code (`end-of-task hook` for the `checks.on_stop` checks, which run when the turn ends: [CONTROLS.md](./CONTROLS.md) § End-of-task checks), and each criterion with the test or output that shows it holds. A criterion nothing shows is not met: fix it or stop and tell the human. No `progress.md` or `qa-result.md`. -4. **Close.** § After QA pass as in full mode, with § Check in place of the QA verdict. No `retro.md`: the learnings file has the verdict and cost lines and **Retro:** `none (small mode)`, with an action item only when something went wrong. It counts toward the prune counter, so the prune sees whether small mode lets problems through. +4. **Close.** § After QA pass as in full mode, with § Check in place of the QA verdict. The learnings file is the look-back, as in full mode: the verdict and cost lines, and § What went wrong with its action items only when something did. It counts toward the prune counter, so the prune sees whether small mode lets problems through. ### Additive migration @@ -87,12 +87,12 @@ Not for wrong copy or a typo that nobody reported as broken (small or light mode The three agents and the merge gate stay as in full mode; what changes is the order and the plan. The test that proves the bug comes before any fix, the cause is written down before the code changes, and the fix is the smallest one that makes the test pass. -1. **Plan (Technical Lead).** When production is broken right now, first tell the human whether a rollback ([RELEASE.md](./RELEASE.md) § Rollback, theirs to run) would stop the damage while the fix is made. Create the feature folder `_harness/features///` and write `Plan/plan.md` from [templates/bug-plan.md](./templates/bug-plan.md) (it replaces `feature.md` and the full plan): the symptom, where it was seen, what should happen and the source that says so, the reproduction steps, the code path the TL read itself, the regression test QA writes, the fix limit, and the branch (the integration branch, or a hotfix). Run the pre-approval checklist as in full mode: § Planning and scope always (item 5, reproduce and read, is the heart of a bug plan), other groups only when the fix area needs them, the rest as one "n/a" line per group. The human approves the plan, including the branch; a hotfix is never the TL's call alone. +1. **Plan (Technical Lead).** When production is broken right now, first tell the human whether a rollback ([RELEASE.md](./RELEASE.md) § Rollback, theirs to run) would stop the damage while the fix is made. Create the feature folder `_harness/features///` and write `Plan/plan.md` from [templates/bug-plan.md](./templates/bug-plan.md) (it replaces the full plan): the symptom, where it was seen, what should happen and the source that says so, the reproduction steps, the code path the TL read itself, the regression test QA writes, the fix limit, and the branch (the integration branch, or a hotfix). Run the pre-approval checklist as in full mode: § Planning and scope always (item 5, reproduce and read, is the heart of a bug plan), other groups only when the fix area needs them, the rest as one "n/a" line per group. The human approves the plan, including the branch; a hotfix is never the TL's call alone. 2. **Reproduce (QA).** The Technical Lead launches QA with the bug-reproduction brief ([HANDOFFS.md](./HANDOFFS.md) § Subagent prompt blocks). QA writes the regression test the plan assigns, runs it, and records in `QA/qa-result.md` § Reproduction that it fails for the reported reason, with the failure's tail. It fixes nothing. The Technical Lead commits the test alone (`WIP(): failing test`), so the history shows it red before the fix. When QA can't reproduce it, it records what it tried; the Technical Lead takes that to the human (more detail, research mode, or close as not reproduced) and nothing gets fixed blind. This run is not a QA round. 3. **Root cause and fix (Full-stack).** The Technical Lead launches Full-stack with the bug-mode brief. Full-stack writes `Development/progress.md` § Root cause first (the `file:line`, why it happens, since when, why no test caught it, and the same pattern elsewhere with the grep and its hit count), then makes the smallest fix that turns the regression test green, edge inputs included, without editing the test, and runs the `before_qa` checks as usual. A fix that would cross the plan's **Fix limit** (new behaviour, a migration, a public API change, a risk area the plan doesn't name, files beyond the plan) stops before the change and hands back: the Technical Lead re-plans (a full plan, with the bug plan linked) and asks the human. The Technical Lead checks § Root cause against the code (`file:line`) before QA, like any subagent finding ([SETUP.md](./SETUP.md) § Findings from a review subagent), then runs the code review before QA on the fix's diff when the risk check named a risk path (a fix off them, a hotfix or one raised by file count, skips it; [SETUP.md](./SETUP.md) § Code review before QA). With the `security` module, a fix that changes who can sign in or read or change data, moves money, handles secrets or deletes or shares people's data gets the security review before QA whatever the effort ([modules/security/](./modules/security/README.md) § Security review before QA). 4. **Re-run (QA).** The normal QA pass on the bug plan's ACs: the regression test, unchanged since the failing-test commit, now passes with its edge inputs; the reproduction steps no longer show the symptom; each same-pattern place is fixed or out of scope with a reason; and the `before_signoff` checks. QA rounds, rework and escalation are as in full mode. The failing-test commit is the negative run [testing.md](../.claude/rules/testing.md) asks for. 5. **After QA pass.** [SETUP.md](./SETUP.md) § After QA pass as in full mode: the steps file (for a hotfix, at least the check in production after the merge), the PR marked ready. On a hotfix branch, the rest is [RELEASE.md](./RELEASE.md) § Hotfix: the `Hotfix vX.Y.Z` title, the tag, and bringing the fix back into the integration branch. -6. **Retro line (Technical Lead).** No `retro.md`. The learnings file `_harness/learnings/-v.md` ([templates/learnings-entry.md](./templates/learnings-entry.md)) is the look-back: the verdict and cost lines, **Escaped** (which feature let the bug through, and the AC, check or review that should have caught it, from § Root cause), **Retro:** `none (bug mode)`, and one action item for the escape: the rule, checklist item or test that would have caught it, or one line on why none would; a rule it adds ends with `(from , )` ([MORPHING.md](./MORPHING.md) § Rule provenance). An escape is the one miss QA never saw, so it is the retro's best source of rules (learned: a run of clean retros added no rules while a bug of the same kind sat on a new route). The fix also checks the sibling paths for the same gap (other routes or pages that read the same data) and closes them in the same branch. The Technical Lead decides items as in § Retrospective. It counts toward the prune counter like a feature, so the prune sees what keeps escaping. The hand-off message ends with the cost and prune counter lines. +6. **Retro (Technical Lead).** The learnings file `_harness/learnings/-v.md` ([templates/learnings-entry.md](./templates/learnings-entry.md)) is the look-back: the verdict and cost lines, **Escaped** (which feature let the bug through, and the AC, check or review that should have caught it, from § Root cause), the escape as a line in § What went wrong, and one action item for it: the rule, checklist item or test that would have caught it, or one line on why none would; a rule it adds ends with `(from , )` ([MORPHING.md](./MORPHING.md) § Rule provenance). An escape is the one miss QA never saw, so it is the retro's best source of rules (learned: a run of clean retros added no rules while a bug of the same kind sat on a new route). The fix also checks the sibling paths for the same gap (other routes or pages that read the same data) and closes them in the same branch. The Technical Lead decides items as in § Retrospective. It counts toward the prune counter like a feature, so the prune sees what keeps escaping. The hand-off message ends with the cost and prune counter lines. ## Research mode diff --git a/template/harness/MORPHING.md b/template/harness/MORPHING.md index 2d8cd1e..2830938 100644 --- a/template/harness/MORPHING.md +++ b/template/harness/MORPHING.md @@ -28,7 +28,7 @@ When a lesson fits two places, it goes to the one owner and the other links to i ## How rules change -- **Retro action items** ([SETUP.md](./SETUP.md) § Retrospective; one that changes how a role works runs as a trial first, § Trials): as many as the feature's findings call for, usually up to `loop.retro_max_items` (more with one line saying why, none is fine), each with its **Why raised**: the finding it answers and the evidence. The TL decides each (approve, skip, defer) and lists the decisions in the hand-off. An item that adds a step for the human, changes product behaviour or customer-facing copy, costs money, or is tagged `cause: environment` goes to the human. A small approved doc or harness item rides the finished feature's PR as its own commit; a large one gets its own plan; a code item gets its own feature. +- **Retro action items** ([SETUP.md](./SETUP.md) § Retrospective; one that changes how a role works runs as a trial first, § Trials): as many as the lines in the learnings file's § What went wrong call for, usually up to `loop.retro_max_items` (more with one line saying why, none is fine), each with its **Why raised**: the finding it answers and the evidence. The TL decides each (approve, skip, defer) and lists the decisions in the hand-off. An item that adds a step for the human, changes product behaviour or customer-facing copy, costs money, or is tagged `cause: environment` goes to the human. A small approved doc or harness item rides the finished feature's PR as its own commit; a large one gets its own plan; a code item gets its own feature. - **Prune decisions** ([SETUP.md](./SETUP.md) § Prune): the TL proposes keep, merge, shorten, move to on-demand or remove for each audited rule; the human approves per row. Only approved rows change anything, applied as a light-mode docs change. - **The human, any time:** the owner may edit any harness file directly. The TL records the change as a decision in the current plan, or as a light-mode commit with a one-line reason. @@ -51,7 +51,7 @@ A trial is a change whose value is uncertain (most often a model or effort varia **Trial by default.** A retro item that changes how a role works (it lands in `harness/agents/.md`, `project/agents/.md`, a role's model or effort mapping, or a rule a role follows) is approved as a trial, not as a permanent rule: the TL writes the trial file (step 1) in the same commit that adds the rule, which ends with `(trial: )`, and the item's Decision reads `approve (trial)`. These stay permanent without a trial: a fact about this project (`project/*` other than agent files), a mechanism (a script, hook, test or check that does the job), a fix to a kit file that names a failure that already happened twice, and anything the owner marks `approve (permanent)` with one line of why. A role-changing item that skips the trial says which of these it is. The window is the one below: no new setting. 1. **Start.** The owner decides it (a trial that costs money or changes who reviews what always goes to the owner), or an approved retro item proposes it. Write `_harness/trials/.md` from [templates/trial.md](./templates/trial.md): the hypothesis, the exact rule text or variant, the feature it starts with, its length (N features), which features qualify, the metric (rework rounds, QA fails, cost from `scripts/feature-cost.sh`), the baseline (the last window's numbers and one comparable earlier feature, so the verdict is a before and after, not an impression), and the pass and revert conditions. A model or effort trial also adds its variant file to `.claude/agents/`, its row to `project/HARNESS_AGENTS.md` and the variant to `harness.config.yml` → `agents`. A rule trial puts its rule text in the owner file with `(trial: )` at its end instead of `(from …)`. -2. **Run.** Each qualifying feature's plan names the trial in § Agent effort (or its plan, for a rule trial). Its retro answers the Trials line, and its learnings file gets `**Trial:** `: that line is the result. The numbers come from the same file's **Verdict** and **Cost** lines, so the retro writes nothing into the trial file (parallel features would conflict there). `bash scripts/prune-counter.sh` prints `Trial : N/ features` in every hand-off. +2. **Run.** Each qualifying feature's plan names the trial in § Agent effort (or its plan, for a rule trial). Its learnings file gets `**Trial:** `: that line is the result. The numbers come from the same file's **Verdict** and **Cost** lines, so the retro writes nothing into the trial file (parallel features would conflict there). `bash scripts/prune-counter.sh` prints `Trial : N/ features` in every hand-off. 3. **Stop early** on the revert condition (for example the first second QA round from an implementation cause). Record it on the learnings file's Trial line and set the trial file's **Status** to `reverted`. 4. **Decide at the prune** ([SETUP.md](./SETUP.md) § Prune, step 1) once it has run its length: `bash scripts/prune-counter.sh --prune` prints each trial's rework rounds, QA fails and cost over its features, and the TL proposes `keep` (the rule loses its `(trial: …)` tag and gets `(from , )`) or `revert` (the variant goes back to the previous default), with those numbers next to the window's. The owner decides. A trial that hasn't run its length carries on, and so does one that has but whose escape window is still open: a trial is decided only once one prune window (`loop.prune_every` features) has finished after its last qualifying feature, because a bug that QA missed shows days later, not at the hand-off (learned: a QA effort change was kept on a "0 escapes" reading taken hours after deploy, and the next window logged 4). The decision reads the numbers above plus the `Escaped` lines of bug-fix and fix-PR learnings files (small mode's included) whose source feature is one of the trial's features (blame the fix's lines to the feature that wrote them); an escape traced to a trial feature counts against the trial, and without an escape reading the proposal says `carry on`. When the prune folds the learnings files, each trial feature becomes a row in its trial file's § Results, so the count survives the fold. diff --git a/template/harness/PLAN_CHECKLIST.md b/template/harness/PLAN_CHECKLIST.md index af1bc0e..c824c92 100644 --- a/template/harness/PLAN_CHECKLIST.md +++ b/template/harness/PLAN_CHECKLIST.md @@ -6,7 +6,7 @@ These are the seed principles: each one comes from a failure that reached a buil ## How to use it -- Work through every item of each group the index below doesn't skip. For each, record in `plan.md` § References → **Greps** the grep or check you ran and its hit count, or "n/a" with a few words on why the item doesn't apply: one line per kit item (`K1`, `K2`, …, applying the project's specifics for that item from `project/PLAN_CHECKLIST.md`) and one per project item (`P1`, `P2`, …). A plan with no Greps record is not ready for approval. +- Work through every item of each group the index doesn't skip. For each, record in `plan.md` § References → **Greps** the grep or check you ran and its hit count, or "n/a" with a few words on why the item doesn't apply: one line per kit item (`K1`, `K2`, …, applying the project's specifics for that item from `project/PLAN_CHECKLIST.md`) and one per project item (`P1`, `P2`, …). A plan with no Greps record is not ready for approval. - **A whole group can be n/a in one line.** When a group doesn't fit the project or the plan (a CLI tool, a library or a data pipeline has no UI; a plan that stores nothing has no Data items), write one line for the group with its range and the reason, for example `K14–K20 n/a: no user interface (CLI tool)`. The line under each group heading says when that happens. A group that applies in part gets one line per item as usual. - Item numbers are stable once the project cites them in retros and prunes ("checklist item 12"). A new item takes the next free number inside its group's range or at the end; a removed item leaves its number unused. - Each item ends with the reason it exists in brackets. When a retro sharpens an item, add the sentence and the feature slug that taught it. @@ -14,19 +14,7 @@ These are the seed principles: each one comes from a failure that reached a buil ## Group index -Read this table first. A group whose "skip" condition holds takes one `n/a` line in `plan.md` (§ How to use it) and its items are not read; print the other groups in full, in one call: `bash scripts/doc-section.sh harness/PLAN_CHECKLIST.md "" …`. - -| Group | Items | Skip the group when | -|-------|-------|---------------------| -| Planning and scope | 1-8 | always run it | -| Contracts and sources | 9-13 | the plan assumes nothing about a framework, library or service | -| UI and interaction | 14-20 | the plan has no UI | -| Testing | 21-24 | always run it | -| Data | 25-28 | the plan stores or migrates nothing | -| Security | 29-32 | the plan adds no input, route, credential or permission | -| Copy, privacy and legal | 33-36 | the plan adds no user-facing copy, personal data or terms | -| Release and operations | 37-38 | the plan changes nothing that ships separately or runs on a schedule | -| Process | 39-40 | always run it | +The index is in [TECHNICAL_LEAD.md](./TECHNICAL_LEAD.md) § Before asking for approval, so the Technical Lead, which reads that file in full, prints only the groups a plan needs in one call. The line under each group heading below says the same; keep the two in step when a group changes. ## Before asking for approval @@ -63,7 +51,7 @@ Read this table first. A group whose "skip" condition holds takes one `n/a` line ### Testing -*Items 21–24 are about tests that already exist; n/a as a group when the plan changes no behavior an existing test covers.* +*Always run the group. Items 21–24 are about tests that already exist, so each is n/a when the plan changes no behavior an existing test covers.* 21. **Tests that assert changed text.** For each user-facing message the plan changes or says must not change, grep unit and end-to-end tests for the current text, for changed attributes (title, accessible label) and for empty-state text. When a default label or name is renamed, classify each hit as created data (update it) or seeded legacy data that still stores the old text (leave it). Check: the grep per string and the classified hit list are in Greps. [Renamed defaults left tests stale, while a seeded legacy row correctly kept the old name.] 22. **Removed, moved or reordered controls.** The test-update sweep greps every test file for each control the plan removes or moves; no file is excluded by name. When it removes, adds or reorders an option of a group, it also greps every other label of the group, the test-helper parameters that select options, constants that list the options, and assertions that count them. When a validation rule is relaxed, it greps for the field's error element, its invalid-state attribute and submits that leave the field empty. Check: one grep per label, helper, list and count. [Specs used other labels of the same group; a test counted the options; a test submitted a blank field to get the old error.] @@ -72,7 +60,7 @@ Read this table first. A group whose "skip" condition holds takes one `n/a` line ### Data -*n/a as a group when the plan writes no stored data, seeds, fixtures or scheduled jobs.* +*n/a as a group when the plan stores, migrates or seeds nothing: no stored data, schema change, fixture or scheduled job.* 25. **Seeds, fixtures and raw resets.** When a plan adds a column an action sets, locks or restricts a field, validates a format, or changes a time window or threshold, it greps seeds, fixtures, test helpers and raw SQL in tests for writes to the same rows and for every spelling of the affected value (for an age: every unit and arithmetic form, on either side of the old and new values), in every test folder. Check: one grep per value and folder, every hit in the steps. [Raw resets left a new timestamp set; seeded keys contained a character the new format refused; an age seed written in hours was missed.] 26. **Relaxed constraints and per-entity values.** A plan that drops or loosens a uniqueness, ownership or tenancy constraint states who can trigger the newly allowed state and how the vendor or system matches the record, with a source. A rule that keeps a stored per-entity value (a timestamp, counter, status) says "only for the same entity id". Check: the trigger and match are named; every fallback is scoped to the entity. [A dropped unique id opened a cross-account path; a literal fallback copied one record's timestamp onto another.] @@ -81,7 +69,7 @@ Read this table first. A group whose "skip" condition holds takes one `n/a` line ### Security -*n/a as a group when the plan adds or changes no limit, permission, identity check, wrapped input or cross-origin code, and closes no access gap.* +*n/a as a group when the plan adds or changes no input, route, credential, permission, limit, identity check or cross-origin code, and closes no access gap.* 29. **Enforcement on every write path.** For each new limit or permission, the plan's **Enforcement** line names every write path (server actions, route handlers, direct database or API access, editors and import paths found by grep) and what blocks it, the status codes, and how the caller's identity is verified where access depends on it. A rule on stored data lists every writer of that data and every stored field the rule reads. A header or field condition is stated as non-empty, not present, when the checking helper treats empty as missing. Check: grep every writer of the affected data; each is on the Enforcement line. [A direct API path bypassed a limit; a code review found an editor save path the rule had missed.] - **Read paths too, when closing an access gap.** A plan that closes who can see some data lists every route that returns that data (single reads, lists, search, counts and stats, exports and downloads, sibling routers) and closes each one in the same plan. Keeping an output's format (a CSV contract, a report layout) is not a reason to keep who can see its rows. When closing a path might break someone who relies on it and the human can't be asked, close it and name it in the hand-off; never leave a known leak open waiting for a decision. Check: grep every reader of the data; each is on the Enforcement line as closed. [A privacy fix left the CSV export showing every user's notes because the README said to keep its format.] @@ -100,7 +88,7 @@ Read this table first. A group whose "skip" condition holds takes one `n/a` line ### Release and operations -*n/a as a group when the plan adds no schedule, platform limit, CI workflow or third-party setting.* +*n/a as a group when the plan adds or changes no schedule, platform limit, CI workflow or third-party setting, and nothing that ships separately.* 37. **Platform tier for schedules, limits and CI cost.** A plan that sets a platform schedule, limit or quota (a cron frequency, a function duration, a rate limit) reads the vendor page for the account's current tier and names it with the source; "Full-stack to confirm" is not enough. A plan that adds a CI workflow or a long CI step times the main command locally and states the billed-minute estimate from that timing. Check: the tier cite and the measured timing. [An hourly schedule was rejected at deployment by the account's tier; a CI estimate was off by 2x.] 38. **Prerequisites only a human can set.** Each third-party console screen the plan needs is listed with its precondition (domain verification, public pages reachable through a gate), and each production setting the feature depends on (an auth setting, a role, a secret, a host) is a human step in § QA scope, ordered before the release that needs it, unless a connected official MCP can do it: then it is a **By connector** line, asked in chat before it runs ([SKILLS_AND_MCPS.md](./SKILLS_AND_MCPS.md) § Connector actions). Check: one human step or **By connector** line per console screen and production setting, and each service it names has a current row in `project/SKILLS_AND_MCPS.md` § Service tools. [A first production release failed five times on setup that no plan had listed.] diff --git a/template/harness/README.md b/template/harness/README.md index 2f068a6..45af924 100644 --- a/template/harness/README.md +++ b/template/harness/README.md @@ -19,7 +19,7 @@ This folder is the **kit**. It was installed from Morphness, but it now belongs | [TECHNICAL_LEAD.md](./TECHNICAL_LEAD.md) | The Technical Lead's hard rules, read when a full or bug mode plan starts | | [GUARD.md](./GUARD.md) | What the guard hook denies, asks and does not see, and its switches | | [PLAN_CHECKLIST.md](./PLAN_CHECKLIST.md) | The Technical Lead's checklist before asking for plan approval | -| [templates/](./templates/) | `feature.md`, `plan.md`, `small-plan.md`, `bug-plan.md`, `progress.md`, `qa-result.md`, `retro.md`, `prune.md`, `research.md`, `trial.md`, `release-qa-result.md`, `version.md`, `version-checklist.md`, `learnings-entry.md`, `steps.md` | +| [templates/](./templates/) | `plan.md`, `small-plan.md`, `bug-plan.md`, `progress.md`, `qa-result.md`, `prune.md`, `research.md`, `trial.md`, `release-qa-result.md`, `version.md`, `version-checklist.md`, `learnings-entry.md`, `steps.md` | | [agents/](./agents/) | Generic role bodies for the Full-stack, QA, legal-writer and researcher subagents (project addenda in `project/agents/`) | | [modules/](./modules/) | Optional parts, switched on in `harness.config.yml` → `modules` | diff --git a/template/harness/SETUP.md b/template/harness/SETUP.md index db5caaa..b34d385 100644 --- a/template/harness/SETUP.md +++ b/template/harness/SETUP.md @@ -7,7 +7,7 @@ Operational guide for the harness. Role definitions: [STRATEGY.md](./STRATEGY.md ## Design goals - **Separation:** plan, implement and validate with different agents. -- **Traceability:** every full-mode feature version has `feature.md` → `plan.md` → `progress.md` → `qa-result.md`; a small-mode one has `plan.md` with its § Check. +- **Traceability:** every full-mode feature version has `plan.md` (with the owner's request in § Request) → `progress.md` → `qa-result.md` → its learnings file; a small-mode one has `plan.md` with its § Check. - **Human gate:** no implementation until `plan.md` is approved. - **Bounded QA:** at most `loop.qa_max_rounds` QA rounds per version (default two), then the human and a new version. - **A harness that learns:** every feature ends with a retro; every `loop.prune_every` features the rules are audited ([MORPHING.md](./MORPHING.md)). @@ -73,8 +73,8 @@ See [MODES.md](./MODES.md). Summary: |------|------------------------------|--------------| | **Full** | Yes | Yes | | **Light** | No | None: the main chat makes the change and runs the named checks | -| **Small** | Yes, with the short small plan | None: the main chat builds, runs the checks and fills the plan's § Check; a learnings line instead of `retro.md`. A bug fix off the risk paths writes no plan: failing test first, the record in its commits and PR | -| **Bug** (fix on a risk path, or a hotfix) | Yes, with the short bug plan | Yes: QA writes the failing test first, then Full-stack fixes, then QA re-runs; a learnings line instead of `retro.md` | +| **Small** | Yes, with the short small plan | None: the main chat builds, runs the checks and fills the plan's § Check; the learnings file is the look-back. A bug fix off the risk paths writes no plan: failing test first, the record in its commits and PR | +| **Bug** (fix on a risk path, or a hotfix) | Yes, with the short bug plan | Yes: QA writes the failing test first, then Full-stack fixes, then QA re-runs; the learnings file is the look-back | | **Research** | `_harness/research/` only | Researcher only; no code, no QA, no retro | --- @@ -150,14 +150,13 @@ Deferred ACs go into the feature's steps file ([HANDOFFS.md](./HANDOFFS.md) § S ## First feature checklist -1. `_harness/features//1/Plan/feature.md`: human intent. -2. Technical Lead: `plan.md`, [PLAN_CHECKLIST.md](./PLAN_CHECKLIST.md) → `awaiting-approval`. -3. Human approves → Technical Lead sets `approved` in `plan.md` and commits → Full-stack implements → `progress.md` complete. -4. A risk path or Full-stack `xhigh` (or, with the `security` module, a security feature): Technical Lead review → `Plan/review.md` (§ Code review before QA). -5. QA: `qa-result.md` → pass or fail (within `loop.qa_max_rounds`). -6. Pass: Technical Lead writes the steps file (or none) → `passed`. Last allowed fail: `failed-escalated`. -7. Technical Lead: `Plan/retro.md` and the feature's `_harness/learnings/` file (§ Retrospective). -8. Human merges the ready PR into the integration branch (or the Technical Lead, per `merge_by_agent`). Production comes later, in a release ([RELEASE.md](./RELEASE.md)). +1. Technical Lead: `_harness/features//1/Plan/plan.md`, the human's intent in its § Request, [PLAN_CHECKLIST.md](./PLAN_CHECKLIST.md) → `awaiting-approval`. +2. Human approves → Technical Lead sets `approved` in `plan.md` and commits → Full-stack implements → `progress.md` complete. +3. A risk path or Full-stack `xhigh` (or, with the `security` module, a security feature): Technical Lead review → `Plan/review.md` (§ Code review before QA). +4. QA: `qa-result.md` → pass or fail (within `loop.qa_max_rounds`). +5. Pass: Technical Lead writes the steps file (or none) → `passed`. Last allowed fail: `failed-escalated`. +6. Technical Lead: the feature's `_harness/learnings/` file (§ Retrospective). +7. Human merges the ready PR into the integration branch (or the Technical Lead, per `merge_by_agent`). Production comes later, in a release ([RELEASE.md](./RELEASE.md)). --- @@ -165,7 +164,7 @@ Deferred ACs go into the feature's steps file ([HANDOFFS.md](./HANDOFFS.md) § S ```mermaid flowchart TD - H[Human: feature.md] --> TL[Tech Lead: plan.md + checklist] + H[Human: the request] --> TL[Tech Lead: plan.md + checklist] TL --> A{Approved?} A -->|no| H A -->|yes| FS[Full-stack: code + progress.md] @@ -178,11 +177,11 @@ flowchart TD TL2 --> QA[QA: tests + qa-result.md] QA --> V{Verdict} V -->|pass| VF[Tech Lead: steps file] - VF --> RT[Tech Lead: retro.md + learnings file] + VF --> RT[Tech Lead: learnings file] RT --> H2[Human: ship decision] V -->|fail| R{Rounds left?} R -->|yes| FS - R -->|no| RT2[Tech Lead: retro.md + learnings file] + R -->|no| RT2[Tech Lead: learnings file] RT2 --> H3[Human: new version] H3 --> TL ``` @@ -193,9 +192,9 @@ flowchart TD Technical Lead: -1. Confirm the `before_signoff` checks are recorded in `qa-result.md` (small mode: `plan.md` § Check) on the final commit ([CONTROLS.md](./CONTROLS.md) § Merge gate). -2. Update any **project** docs the plan listed (`project/PROJECT.md`, domain docs). A status line the Full-stack engineer wrote as "Pending QA" becomes "QA pass". -3. For a UI change, confirm the screenshots from `progress.md` § Screenshots (small mode: § Check) exist; the reply to the human links every one of them. +1. Confirm the `before_signoff` checks are recorded in `qa-result.md` (small mode: `plan.md` § Check) on the final commit ([CONTROLS.md](./CONTROLS.md) § Merge gate). Read the record; don't run the checks again: QA ran them on that commit, and the end-of-task hook re-runs what it needs (learned: the main chat re-ran the database and unit suites after a QA pass, a whole turn that changed nothing). +2. The project fact docs (what the product does and holds: `project/PROJECT.md` and the domain docs) were updated by Full-stack as plan steps with their own acceptance criteria, so QA checked them against the code; the Technical Lead edits the rule and process docs (`project/CONTROLS.md` for new risk paths, `project/TESTING.md`, `project/PLAN_CHECKLIST.md`, a retro item). +3. For a UI change, the reply to the human links every screenshot `progress.md` § Screenshots lists (small mode: § Check). They were checked before QA; don't check them again. 4. **Human-only steps.** Write this feature's block to **its own steps file** from [templates/steps.md](./templates/steps.md): what you did through a connector (§ Done for you), every step only the human can do (with its expected result and why no connector did it), every AC `qa-result.md` (small mode: § Check) marks `deferred: local` (tell the human the feature isn't fully verified until those pass), and for a feature with migrations or remote config changes the ones to apply on the integration environment after the merge. The block heading links `qa-result.md` ([HANDOFFS.md](./HANDOFFS.md) § Steps file). None of these means no file. Where the file goes: - **Roadmap module off:** `_harness/features///steps.md`. - **Roadmap module on:** `//steps/.md`, next to the next version's checklist, never into the checklist itself, so parallel feature branches don't conflict. Finding the next version, creating a missing version doc or checklist, and items that belong to a later version: [modules/roadmap/](./modules/roadmap/README.md). When the roadmap has no single next version, ask the human. The release and the prune fold these files into the checklist. @@ -207,29 +206,25 @@ Technical Lead: ## Retrospective -Every full-harness feature ends with a short Technical Lead retro. No retro in light mode; in small and bug mode the learnings file alone is the look-back, and a small-mode bug fix writes one only when something went wrong ([MODES.md](./MODES.md) § Small mode step 4, § Bug mode step 6). +Every full-harness feature ends with its learnings file, `_harness/learnings/-v.md` from [templates/learnings-entry.md](./templates/learnings-entry.md): the one closing record, with the retro inside it, and never an entry in `_harness/LEARNINGS.md` itself, so parallel feature branches don't conflict there (learned: the shared learnings file was the most common merge conflict). No retro in light mode; a small-mode bug fix writes the file only when something went wrong ([MODES.md](./MODES.md) § Fixing a bug). - **When:** after the QA pass hand-off, or on `failed-escalated`, before the next plan. Failed features need it most. -- **Inputs:** `feature.md`, `plan.md` (with its open questions and answers), `progress.md`, `review.md` if any, `qa-result.md`, the steps file if any, the agents' completion notices and any human corrections in the conversation. -- **Output:** `Plan/retro.md` from [templates/retro.md](./templates/retro.md), with its action items, each typed and routed. The TL proposes and decides them (§ Per-feature review below). -- **How many items:** as many as the feature's findings call for, and never padded to reach a number: a retro with no items is normal. Up to `loop.retro_max_items` (default 3) is the usual size. More is allowed when each extra item comes from a different finding, with one line under the table saying why this feature needed more (a long or failed feature often does). The limit is soft because every approved item costs a commit, often a new rule, and one more row for the prune to audit. -- **Why raised:** every item fills its **Why raised** column: the finding it answers (`What to improve #N`, or the Answers line it comes from) and the evidence (a QA round, a human correction, a cost figure, a file). No finding, no item. The prune reads it to judge whether the rule the item added worked: if the problem it names came back after it landed, it didn't. +- **What went wrong:** from the plan, `progress.md`, `review.md` if any, `qa-result.md`, the agents' completion notices and the human's corrections in the conversation, list what went wrong, one line each with its cause and evidence. A feature where nothing did writes `None.`, no action items and no other look-back (learned: a clean feature's retro filled a 60-line template to say nothing went wrong). +- **Action items come from those lines:** as many as they call for and never padded; a feature with none is normal. Up to `loop.retro_max_items` (default 3) is the usual size; more is allowed when each extra item comes from a different line, with one line under the table saying why. Every item's **Why raised** names the line it answers and its evidence; the prune reads it to judge whether the rule the item added worked: if the problem came back after it landed, it didn't. - **Rules come from misses:** an item that adds or tightens a rule or checklist item names a miss in this feature that the rule would have caught: a failed QA round, a review finding that needed a fix, a human correction, rework, or a bug found after a pass. Specifics, examples or wording that restate what already went right are not items; a feature with no miss adds no rule. While `scripts/prune-counter.sh` prints `Prune is DUE` for a size budget, an approved rule item also removes or shortens existing text of at least its size, or waits for the prune (learned: over 12 clean features, 10 retros each added checklist "specifics" and the project's rule files grew by more than half with no miss behind any of them). - **Mechanism over memo:** an item that adds a sentence to a rule, checklist or agent body says, in its **Action**, why a script, hook, test or check can't do the job instead. When the problem a rule names came back after it landed, convert the rule into a mechanism; don't add another sub-bullet (learned: the rules that held changed how something works, while reminder rules saw their problem return). -- **Real cost data:** agents' **Started** and **Ended** are notes only. The Technical Lead records duration and tool calls from each subagent's completion notice in `retro.md` § Agent runs (the `subagent_type` column carries the model and effort), filling a row as each notice arrives. Every row is filled before the single retro commit; no separate follow-up cost commit. -- **Feature cost:** with `cost.track` on (the default), run `bash scripts/feature-cost.sh` in the session that ran the feature, before the retro commit, and fill § Agent runs → Est. $ and its Technical Lead and Total rows from its table. Its last line (`Cost ≈ $… (Technical Lead $…, fullstack-engineer $…, …); API list prices as of …`) goes on the learnings file's **Cost** line and at the end of the hand-off message, next to the prune counter. It reads Claude Code's own session transcripts, which record every API call's model and tokens by kind, and prices them from `harness/MODEL_PRICES.md`: the tokens are exact, the dollars an estimate at public API prices, useful to compare features even on a Pro or Max plan. Never fill it from the completion notices: their single token figure doesn't say which tokens were cache reads, which cost a small fraction of fresh input, so it can't be priced (learned: a notice said 70k tokens for a run whose transcript showed 211k input tokens, most of them cache reads). When the session did other work before this feature, pass `--since