From b5a48ea213c13dc87206e07ff256ddff48e87808 Mon Sep 17 00:00:00 2001 From: prode Date: Tue, 4 Aug 2026 02:54:11 -0300 Subject: [PATCH] refactor(assets): put the rules on a line budget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every rule is preloaded into every request of the session on a harness that reads `rules/`, so a line here is not paid once — it is paid continuously. The set was 651 lines / 28.6 KB and is now 554 / 25.6 KB, with no rule and no reason removed: what went was repetition, ceremony, and prose that restated the table above it. Seven of ten files now sit at or under 55 lines. Three do not, and the test grandfathers them at their current size rather than pretending otherwise: specs.md and knowledge-base.md are roughly half table and fenced example — 45 and 46 lines of structure — so cutting them to 55 would leave under ten lines of prose across six sections. That is enough to state a rule and not enough to say why, and a rule whose reason was deleted is one an agent quietly deviates from. delivery.md is at 62 with dense prose and no structure to blame. The test enforces the budget in both directions: a new rule over 55 fails, and a grandfathered file that shrinks below its cap fails too, so the gain gets locked in instead of drifting back. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01J54qbk8RpC2tZH8LBz6T2b --- internal/assets/assets_test.go | 45 ++++++++ internal/assets/templates/rules/delivery.md | 100 +++++++---------- .../assets/templates/rules/knowledge-base.md | 79 +++++--------- .../assets/templates/rules/methodology.md | 83 ++++++-------- internal/assets/templates/rules/specs.md | 102 +++++++----------- internal/assets/templates/rules/tasks.md | 47 ++++---- 6 files changed, 202 insertions(+), 254 deletions(-) diff --git a/internal/assets/assets_test.go b/internal/assets/assets_test.go index 6988901..a640ff2 100644 --- a/internal/assets/assets_test.go +++ b/internal/assets/assets_test.go @@ -267,6 +267,51 @@ func TestScaffoldedEntryFileStaysShort(t *testing.T) { // file addresses the set by pattern (`/.md`) instead of listing // nine links. What it still guarantees is the thing that actually breaks: a rule // added to the template set without being announced fails here. +// Every rule is preloaded into every request of the session on a harness that reads +// `rules/` — so a line added here is not paid once, it is paid continuously, and the +// whole set has to stay something an agent can hold rather than skim. +// +// 55 lines is the budget for a new rule. Three predate it and are capped where they +// stand instead, because each is roughly half table and fenced example: cutting them +// to 55 would leave under ten lines of prose across six sections, which is enough to +// state a rule and not enough to say why — and a rule whose reason was deleted is one +// an agent quietly deviates from. They may shrink; they may not grow. +func TestRulesStayShortEnoughToBePreloaded(t *testing.T) { + const budget = 55 + // Capped at what they measured when the budget landed. Lower a number here when a + // rewrite earns it; never raise one. + grandfathered := map[string]int{ + "specs.md": 82, + "knowledge-base.md": 81, + "delivery.md": 62, + } + + for _, f := range Workspace(paths.Claude) { + rules := paths.Claude.Dir + "/" + paths.Claude.RulesSeg + if !strings.HasPrefix(f.Rel, rules+"/") { + continue + } + raw, err := Render(paths.Claude, f) + if err != nil { + t.Fatalf("%s: %v", f.Name, err) + } + name := path.Base(f.Rel) + lines := strings.Count(strings.TrimRight(raw, "\n"), "\n") + 1 + + limit, capped := grandfathered[name] + if !capped { + limit = budget + } + if lines > limit { + t.Errorf("%s is %d lines, over its %d-line limit", name, lines, limit) + } + if capped && lines < limit { + t.Errorf("%s is down to %d lines — lower its cap from %d to lock the gain in", + name, lines, limit) + } + } +} + func TestEntryFileNamesEveryRule(t *testing.T) { for _, h := range paths.Harnesses() { raw, err := Render(h, entryFile(t, h)) diff --git a/internal/assets/templates/rules/delivery.md b/internal/assets/templates/rules/delivery.md index 525552a..037089e 100644 --- a/internal/assets/templates/rules/delivery.md +++ b/internal/assets/templates/rules/delivery.md @@ -1,88 +1,62 @@ # Delivery — branch, worktree, PR -Work does not happen on `main` and does not end with a green test run. It ends with -a pull request. - -## One branch per unit of work, in its own worktree - -Each unit of work — a spec, or a plan's leaf — gets its own branch, developed in its -own **git worktree**: +Work does not happen on `main` and does not end with a green test run. It ends with a +pull request. Each unit of work gets its own branch in its own worktree: ``` git worktree add ../- -b / ``` -The reason is that the user may run several agent sessions at once, one per -feature, and merge them into `main` as they land. A worktree is what makes that -possible: each session gets its own directory, none disturbs the others, and none -touches the checkout the user is sitting in. A shared tree with `git switch` cannot -do this — two sessions would fight over one working directory. +The user may run several sessions at once, one per feature, merging them as they land. +The worktree is what makes that possible: each session gets its own directory and none +touches the checkout the user is in. A shared tree with `git switch` cannot — two +sessions would fight over one working directory. ## Implementation is sequential — you write the code -**There is no implementation subagent and no parallel task dispatch.** This was -designed the other way first and rejected: +**There is no implementation subagent and no parallel task dispatch.** Designed the +other way first, and rejected: - Delegating implementation puts the cheaper model on the hardest work while the - orchestrator keeps the part that needs the least capability. That is backwards. -- Every fresh agent re-pays for discovery. Within one spec your accumulated context - is the asset: you use the right parser in task 1.2 because you wrote task 1.1. + orchestrator keeps the part needing the least capability. That is backwards. +- Every fresh agent re-pays for discovery. Within one spec your accumulated context is + the asset: you use the right parser in 1.2 because you wrote 1.1. - **File-disjointness is not independence, and a clean merge hides the difference.** - Two tasks touching no common file both need a `Money` type that does not exist - yet. Each creates its own, with different semantics. The merge is clean and - nothing signals that anything went wrong. Sequential execution cannot produce - this, because the later task sees the earlier task's code. - -Feature-level parallelism does not have that problem and is supported: the *human* -picks the split, each session has a full context, and two features a person -deliberately separated are unlikely to collide. - -## What running several sessions still costs + Two tasks touching no common file both need a `Money` type that does not exist yet. + Each invents its own, with different semantics, and the merge is clean. Sequential + execution cannot produce this: the later task sees the earlier task's code. -Worktrees isolate files, not the world outside them. Say these out loud rather than -discovering them: - -- **Shared external resources.** Two suites running at once fight over a fixed port, - one test database, a shared temp path. Either the suite namespaces them per - worktree, or the runs are serialized. -- **Cross-feature breakage.** Two features green on their own branches can be broken - together. Only CI on `main` after the merge sees that. +Feature-level parallelism has none of that and is supported — a *human* picks the split +and each session has full context. Worktrees isolate files, not the world: suites +fighting over a fixed port or one test database must be namespaced or serialized, and +two features green separately can still break together, which only CI on `main` sees. ## The delivery sequence Once the last task is done: -1. **Full suite + lint** on the integrated branch. The per-task scoped runs cannot - see breakage between tasks. -2. **`scc validate`** — the artifacts have to be in shape too, and exit `2` is not - done. -3. **`code-review` and `security-review`** subagents on the diff, dispatched - together. Each returns a verdict, a table of what it actually checked, and - findings by severity — you fix from that report, you do not re-review. `blocked` - or any `blocker`/`critical` finding means the PR does not open yet; `major`/`high` - is fixed before merge; `minor`/`low` is your call, and saying "not doing this, and - why" in the PR body is a legitimate answer. A gate reported `not-run` is not a - pass: run it yourself or say in the PR that it was not run. - - The PR should arrive already reviewed: a PR is for the human, and spending their - attention on findings a subagent would have caught is pure waste. Fixing then - re-running the two agents is worth one round; a third round means the finding - needs a person, not another review. +1. **Full suite + lint** on the integrated branch. Per-task scoped runs cannot see + breakage between tasks. +2. **`scc validate`** — the artifacts have to be in shape too, and exit `2` is not done. +3. **`code-review` and `security-review`** subagents on the diff, dispatched together. + Each returns a verdict, what it checked, and findings by severity — you fix from + that report, you do not re-review. `blocked` or any `blocker`/`critical` means the PR + does not open yet; `major`/`high` is fixed before merge; `minor`/`low` is your call, + and "not doing this, and why" in the PR body is a legitimate answer. A gate reported + `not-run` is not a pass. The PR should arrive already reviewed — a human's attention + spent on what a subagent would have caught is waste. One round of fix-and-re-run is + worth it; a third means the finding needs a person. 4. **Commit and push.** Conventional Commits, written from the diff and the spec. 5. **Open the PR.** Body: what changed, which spec or plan, how it was verified. -## Then, CI — the answer you already have - -Use the `ci:` answer recorded at kickoff ([autonomy.md](autonomy.md)); do not ask now. - -- **`wait`** — watch the PR's checks until they settle. Red means fix, push, keep - watching. The work is not finished while CI is failing. -- **`no-wait`** — opening the PR is the finish line. +Then CI, using the `ci:` answer from kickoff ([autonomy.md](autonomy.md)) — do not ask +now. **`wait`** means watch the checks until they settle, fixing and pushing while they +are red: the work is not finished while CI is failing. **`no-wait`** means opening the +PR is the finish line. ## Degrading -- **No remote, or no `gh`** — commit on the branch and stop there, saying so. A - branch the user can push themselves is a real deliverable; silently skipping the - PR is not. -- **Worktrees accumulate.** Remove a worktree once its branch is merged; keep it if - it still holds unmerged work, and say which you did. +**No remote, or no `gh`** — commit on the branch and stop there, saying so. A branch +the user can push themselves is a real deliverable; silently skipping the PR is not. +**Worktrees accumulate** — remove one once its branch is merged, keep it if it holds +unmerged work, and say which you did. diff --git a/internal/assets/templates/rules/knowledge-base.md b/internal/assets/templates/rules/knowledge-base.md index 3220312..3b1c9c9 100644 --- a/internal/assets/templates/rules/knowledge-base.md +++ b/internal/assets/templates/rules/knowledge-base.md @@ -1,8 +1,8 @@ # The knowledge base — `docs/` A spec answers *what this feature does now*. `docs/` answers *why* — the durable -reasoning, the decisions, the material read from outside. Neither replaces the other, -and keeping them separate is what lets a spec stay anchored to one feature. +reasoning, the decisions, the material read from outside. Keeping them separate is +what lets a spec stay anchored to one feature. ``` docs/ @@ -21,67 +21,47 @@ synonyms used where a canonical term belongs, and dependencies missing from ## wiki/ -One page per concept, linked with `[[wikilinks]]`. Every page is reachable from -`index.md` — an unreachable page is an orphan, and an orphan is a page nobody will -ever find again. Record what changed in `changelog.md` when you change the wiki. +One page per concept, linked with `[[wikilinks]]`, every page reachable from +`index.md` — an orphan is a page nobody will ever find again. Record what changed in +`changelog.md`. -**`raw/` is a drop box, not storage.** Material collected from outside goes there to -be read, distilled into a wiki page, and then removed. A file still sitting in -`raw/` is a finding: it was collected and never processed. +**`raw/` is a drop box, not storage.** Outside material goes there to be read, +distilled into a wiki page, and removed. A file still sitting there is a finding: it +was collected and never processed. ## adr/ -One record per decision that is **hard to reverse**. Numbered contiguously from -`0001`, one file per record, cited from anywhere as -`adr:0007-use-sqlite-for-the-cache`: +One record per decision that is **hard to reverse**, numbered contiguously from +`0001`, cited from anywhere as `adr:0007-use-sqlite-for-the-cache`. Frontmatter +`status:` is `proposed | accepted | rejected | superseded`; the body is `## Context`, +`## Decision`, `## Consequences`. -```markdown ---- -status: accepted # proposed | accepted | rejected | superseded ---- - -# 0007 · Use SQLite for the cache - -## Context -## Decision -## Consequences -``` - -**A superseded record is marked, never edited.** The point of an ADR is that it -records what was believed at the time; rewriting one destroys exactly the thing it -exists to preserve. Add the new record, and in the old one set: +**A superseded record is marked, never edited** — an ADR records what was believed at +the time, and rewriting one destroys the thing it exists to preserve. Add the new +record; in the old one set `status: superseded` and `superseded-by: 0012-…`. -```yaml -status: superseded -superseded-by: 0012-move-the-cache-to-redis -``` - -Not every design decision is an ADR. If it is cheap to change, `design.md` is where -it belongs. +Not every design decision is an ADR. If it is cheap to change, it belongs in +`design.md`. ## codewiki/ -Prose that explains code, one page per area, with every section citing the exact -lines it is about: +Prose explaining code, one page per area, every section citing the exact lines it is +about: ```markdown ## How the dispatcher routes [internal/cli/cli.go:48-64]() - -One switch, no registration... ``` A citation that no longer resolves is a finding, so this is the one part of `docs/` -that goes stale loudly rather than quietly. **Every section cites something** — a -section that cites nothing is prose that has drifted free of the code it describes. - -Write it for the parts where reading the code does not tell you why it is like that. -Do not narrate what a reader can see. +that goes stale loudly rather than quietly. **Every section cites something** — one +that cites nothing has drifted free of the code it describes. Write it where reading +the code does not tell you why it is like that; do not narrate what a reader can see. ## glossary.md -One canonical term per concept, and the synonyms to avoid. One entry per line: +One canonical term per concept and the synonyms to avoid, one entry per line: ```markdown - **order total** — the amount charged, in minor units. Avoid: grand total, sum @@ -89,16 +69,13 @@ One canonical term per concept, and the synonyms to avoid. One entry per line: ``` Domain vocabulary drifts by default — three names for one thing appear within a week -of two people working in parallel. Pick one, list the others after `Avoid:`, and use -the canonical term everywhere: in code, in requirements, in the wiki. An avoided -synonym used as a whole word in `docs/` is a finding. +of two people working in parallel. Use the canonical term in code, in requirements, +in the wiki. An avoided synonym used as a whole word in `docs/` is a finding. ## stack.md Every adopted technology, with one line on why. **Technology not listed here is an -open decision, never something adopted silently** — and because a project's -dependency file is structured data rather than source, this is checkable: a direct -dependency declared there and absent from `stack.md` is a finding. - -So adding a dependency is a two-step act: add it, and say here why it earned its +open decision, never something adopted silently** — and because a dependency file is +structured data, this is checkable: a direct dependency declared there and absent +here is a finding. Adding a dependency is two acts: add it, and say why it earned its place. diff --git a/internal/assets/templates/rules/methodology.md b/internal/assets/templates/rules/methodology.md index e9b31b2..6ee75c2 100644 --- a/internal/assets/templates/rules/methodology.md +++ b/internal/assets/templates/rules/methodology.md @@ -1,73 +1,54 @@ # Methodology — per task, Unit by default -Every task is built one of two ways, and the annotation on the task line says -which. The choice is per task, not per project: a feature routinely has both. +Every task is built one of two ways, and the annotation on its line says which. The +choice is per task, not per project: a feature routinely has both. ## Before either cycle — find the tests that already cover this -**Identify the existing tests that exercise the paths this task will change, and -run them, before writing anything.** Name them, in one line, before you start. +**Identify the existing tests that exercise the paths this task will change, and run +them, before writing anything.** Name them, in one line, before you start. -This is not part of Unit or TDD. It precedes both, because it is about not breaking -what works rather than about proving what is new. - -It is here because of a measured result. Giving an agent a test-first *procedure* -while leaving it ignorant of which tests actually cover the code being modified -**increased regressions above doing nothing at all** — roughly 9.9% against ~6.1%. -Identifying the covering tests first brought it to ~1.8%. Procedure without context -was worse than no procedure. - -The failure it prevents is specific: a task adds a case to a function whose -existing tests nobody looked at, the new test passes, and the old behavior silently -changed. +This precedes both cycles, because it is about not breaking what works rather than +proving what is new. It is here because of a measured result: giving an agent a +test-first *procedure* while leaving it ignorant of which tests cover the code being +modified **increased regressions above doing nothing at all** — roughly 9.9% against +~6.1%, where identifying the covering tests first brought it to ~1.8%. The failure it +prevents is specific: a task adds a case to a function whose existing tests nobody +looked at, the new test passes, and the old behavior silently changed. ## Unit — the default -Write the code, then write a unit test for **each function** in it. That is the -whole cycle. **There is no RED/GREEN here** — there is no failing-test step to -observe, because the code already exists. - -Two conditions make this legitimate rather than a shortcut, and both are -load-bearing: +Write the code, then a unit test for **each function** in it. **There is no RED/GREEN +here** — no failing-test step to observe, because the code already exists. Two +conditions make this legitimate rather than a shortcut, and both are load-bearing: - **Immediately, per function — never at the end.** Finish a function, test that - function, move on. The evidence supports test-last only in this iterative form; - nothing supports saving the tests for the end of the feature. -- **The test comes from the requirement, not from the code.** This is where an - agent fails differently from a human: a human writing tests late writes too few, - an agent writes tests that assert *what the code does* — green tests that - faithfully encode the bug. Read the requirement the task cites and assert that. - Reading your own implementation to decide what to assert is the failure mode, not - the method. + function, move on. The evidence supports test-last only in this iterative form. +- **The test comes from the requirement, not from the code.** This is where an agent + fails differently from a human: a human writing tests late writes too few, an agent + writes tests asserting *what the code does* — green tests that faithfully encode the + bug. Read the requirement the task cites and assert that. The default because most code is plumbing: the shape is not in doubt, and tests written straight after are just as binding. Those two conditions are the entire risk -of writing code first, and you are accountable for them — `scc` cannot check either. +of writing code first, and `scc` can check neither — you are accountable for them. ## TDD — RED/GREEN required Write the failing test first, **watch it fail**, then make it pass, then refactor. Skipping RED is not TDD: a test that has never failed has not been shown to test -anything. Say, in the task's notes, that you observed the red. - -RED/GREEN belongs to TDD and only to TDD. Unit is not a lazier TDD and TDD is not a -stricter Unit — they are two different cycles. - -**Mandatory when the cost of being wrong is high:** - -- **money** — any calculation, rounding, split, or conversion involving currency -- **complex algorithms** — anything whose correctness is not obvious by reading it -- **hypothesis / thesis validation** — code written to prove something holds -- **anything else** where the complexity is real and the chance of being wrong is - high - -The trigger is risk, not size. A three-line rounding helper that touches money is -TDD; a two-hundred-line CRUD handler is Unit. +anything. Say, in the task's notes, that you observed the red. RED/GREEN belongs to +TDD and only to TDD — Unit is not a lazier TDD, and TDD is not a stricter Unit. -A task annotated `(TDD)` is also a task to surface before it lands, even in an -automatic run — see [autonomy.md](autonomy.md). +**Mandatory when the cost of being wrong is high:** money, in any calculation, +rounding, split, or conversion involving currency · complex algorithms, whose +correctness is not obvious by reading them · hypothesis validation, code written to +prove something holds · anything else where the complexity is real and the chance of +being wrong is high. -## After the code +The trigger is risk, not size. A three-line rounding helper that touches money is TDD; +a two-hundred-line CRUD handler is Unit. A `(TDD)` task is also one to surface before +it lands, even in an automatic run — see [autonomy.md](autonomy.md). -Neither cycle ends at a passing test. Run the scoped tests **and** the lint before -you call the task done: [verification.md](verification.md). +Neither cycle ends at a passing test. Run the scoped tests **and** the lint before you +call the task done: [verification.md](verification.md). diff --git a/internal/assets/templates/rules/specs.md b/internal/assets/templates/rules/specs.md index 6dc543e..1ca6e77 100644 --- a/internal/assets/templates/rules/specs.md +++ b/internal/assets/templates/rules/specs.md @@ -1,16 +1,15 @@ # Specs — the three artifacts -`specs//` holds exactly three files. `scc spec new ` creates them -from the templates; this is what goes in them. +`specs//` holds exactly three files. `scc spec new ` creates them; +this is what goes in them. ## requirements.md — EARS, all five patterns -Requirements are written in EARS and numbered `R.`, so the later phases -can cite them. This is not ceremony: prose has nothing to validate, an EARS clause -has named parts, and a missing part is a finding. +Numbered `R.` so later phases can cite them. Prose has nothing to +validate; an EARS clause has named parts, and a missing part is a finding. The ruleset: **zero or many preconditions · zero or one trigger · one system name · -one or many responses**, always in that order. +one or many responses**, in that order. | Pattern | Shape | |---|---| @@ -21,51 +20,39 @@ one or many responses**, always in that order. | Unwanted behavior | `If , then the shall ` | | Complex | more than one keyword, in clause order | -**All five are valid.** Do not force everything into `When …`: inventing a trigger -for a requirement that is simply always true makes it worse. `If … then …` — -unwanted behavior — is the pattern most often missing and the one that most often +**All five are valid.** Inventing a trigger for something simply always true makes it +worse. `If … then …` is the pattern most often missing and most often the one that matters. -**Omit, don't fill.** Structured requirements measurably improve generated code, but -the curve is not monotonic: over-specification constrains reasoning and introduces -requirements that conflict with each other, and correctness drops. Specify what the -feature actually decides. A requirements document longer than the decision it records -can produce worse code than a shorter one. +**Omit, don't fill.** Structure improves generated code, but the curve is not +monotonic: over-specification constrains reasoning and introduces requirements that +conflict, and correctness drops. A requirements document longer than the decision it +records can produce worse code than a shorter one. ## design.md — scaled by complexity -**The design must fit the decision being made.** The common failure is inventing -architecture for a change that had no architectural question in it — a component -diagram for a two-function addition, a data-model section for something that touches -no data. +**The design must fit the decision being made.** Inventing architecture for a change +that had no architectural question is worse than verbose — **invented architecture +constrains**: the next session reads it as a decision somebody made, and honors it. -That is worse than verbose. **Invented architecture constrains:** the next session -reads it as a decision somebody made, and honors it. Filler becomes binding. - -So the sections are conditional. **Omit, don't fill:** +So the sections are conditional. Omit, don't fill: | When the change… | design.md carries | |---|---| -| decides nothing structural | what changes, where, and why — a few paragraphs. No components, no diagram. | -| moves a boundary, a data shape, or an external contract | those sections only, for the parts that actually change | -| has real alternatives with trade-offs | the alternatives and why one won — plus an ADR if the decision is hard to reverse | - -A heading filled with "N/A", or with prose written to satisfy the heading, is worse -than an absent heading: it reads as a decision and nobody can tell it apart from -one. Delete the heading instead. - -`scc` checks that the design exists and traces to its requirements. It never checks -that a particular section is present — a required heading is a request for filler. +| decides nothing structural | what changes, where, and why. No components, no diagram. | +| moves a boundary, a data shape, or an external contract | those sections only, for the parts that change | +| has real alternatives with trade-offs | the alternatives and why one won — plus an ADR if it is hard to reverse | -## tasks.md — the grammar +A heading filled with "N/A", or with prose written to satisfy the heading, reads as a +decision nobody can tell apart from a real one. Delete the heading instead. `scc` +never checks that a section is present: a required heading is a request for filler. -See [tasks.md](tasks.md). Exactly one `(Unit)` or `(TDD)` per task, and every task -cites the requirements it satisfies. Traceability runs both ways: every requirement -reaches at least one task, every task cites a requirement that exists. +## tasks.md -## Changing a spec that already exists — deltas, not rewrites +See [tasks.md](tasks.md). Traceability runs both ways: every requirement reaches a +task, every task cites a requirement that exists. -Write the change as a **delta** against the spec, marking each affected requirement: +## Changing an existing spec — deltas, not rewrites ``` - **R2.3** (MODIFIED) When the cart is empty, the checkout shall … @@ -73,32 +60,23 @@ Write the change as a **delta** against the spec, marking each affected requirem - **R1.4** (REMOVED) ``` -Three reasons: +You specify the change, not the system, so adopting this on an existing codebase +never means writing the spec for everything that already works. Deltas scoped to +individual requirements also make concurrent edits safe — two sessions can change one +spec while touching different requirements, where whole-file rewrites collide on +contact — and a reviewer reads intent instead of reconstructing it from a diff. -1. **You specify the change, not the system.** Adopting this practice on an existing - codebase must not require writing the spec for everything that already works. - Specs grow one change at a time. -2. **Deltas scoped to individual requirements make concurrent edits safe.** Two - sessions can change one spec as long as they touch different requirements. - Whole-file rewrites collide on contact. -3. **A reviewer reads intent instead of reconstructing it** from a diff. - -The delta is how a change is proposed and reviewed. Once it lands, fold it into the -spec: the spec stays the current statement of the feature, never an append-only log. +Once the delta lands, fold it in: the spec is the current statement of the feature, +never an append-only log. ## The spec is anchored, not disposable -A spec does not stop being true when its feature merges. **Work that touches an area -a spec covers updates that spec as part of the delivery** — as a delta, in the same -branch, in the same PR as the code. - -Under autonomy the file is the only record of intent, and a record that stops being -maintained stops being a record. A stale requirement read as current is worse than an -absent one, because it is believed. - -Nothing detects this drift mechanically — verifying that a spec matches the code -means understanding the code, which `scc` deliberately does not do. Keeping the spec -current is your obligation, and the reviewer's to check. +**Work touching an area a spec covers updates that spec as part of the delivery** — +as a delta, same branch, same PR. Under autonomy the file is the only record of +intent, and a stale requirement read as current is worse than an absent one because +it is believed. -Anchored means *maintained while the code exists*, not *never removed*. A feature -genuinely deleted takes its spec with it. +Nothing detects this drift mechanically: verifying a spec against code means +understanding the code, which `scc` deliberately does not do. It is your obligation +and the reviewer's to check. Anchored means *maintained while the code exists* — a +feature genuinely deleted takes its spec with it. diff --git a/internal/assets/templates/rules/tasks.md b/internal/assets/templates/rules/tasks.md index 11796fc..5f0f968 100644 --- a/internal/assets/templates/rules/tasks.md +++ b/internal/assets/templates/rules/tasks.md @@ -1,32 +1,28 @@ # Task grammar -One grammar governs every task line, whether it sits in a spec's `tasks.md` or in a -plan's checklist. The methodology is a property of the task, not of the vehicle that -carried it. +One grammar governs every task line, in a spec's `tasks.md` or a plan's checklist. +The methodology is a property of the task, not of the vehicle that carried it. ``` - [ ] 1.1 (Unit) Parse the manifest file — R1.2, R1.4 - [ ] 1.2 (TDD) Calculate the pro-rata split across accounts — R2.1 -- [ ] 1.3 (Unit) Render the summary table — R3.1 ``` - `- [ ]` / `- [x]` — the checkbox is the state. - `1.1` — a unique number, `.`. - `(Unit)` or `(TDD)` — **required, exactly one.** A task with no methodology is a - task where nobody decided, which is the failure this whole practice exists to - prevent. `scc` exits `2` on a task that is missing it. + task where nobody decided, which is the failure this practice exists to prevent. + `scc` exits `2` on a task missing it. - The description, in the imperative. -- `— R1.2, R1.4` — the requirements this task satisfies, after an em dash. Required - in a spec's `tasks.md`; that citation is what makes traceability checkable. +- `— R1.2, R1.4` — the requirements this task satisfies, after an em dash. Required in + a spec's `tasks.md`; that citation is what makes traceability checkable. There is no parallel-dispatch marker. Implementation is sequential — see [delivery.md](delivery.md) for why, and for the parallelism that *is* supported. -## Requirement IDs - -Requirements are numbered `R.` in `requirements.md` and cited by that -ID. It greps cleanly, it never collides with a task's own number, and a reader who -has never seen this document can follow it. +Requirements are numbered `R.` and cited by that ID: it greps cleanly, it +never collides with a task's own number, and a reader who has never seen this document +can follow it. ## How big is a task @@ -36,21 +32,18 @@ has never seen this document can follow it. Granularity is not tidiness; it decides what a failure costs. Agents complete individual steps far more reliably than whole workflows, and structuring work so a -failure can be retried at the subtask level cut retry cost by ~73% against retrying -a whole plan. - -- **Too coarse** and a red result tells you only that a feature is broken. -- **Too fine** and the checklist becomes bookkeeping about work smaller than the act - of recording it. +failure can be retried at the subtask level cut retry cost by ~73% against retrying a +whole plan. Too coarse and a red result tells you only that a feature is broken; too +fine and the checklist becomes bookkeeping about work smaller than recording it. ## Two checklists, one truth -Your harness's own todo list is where you track the task you are on right now. The -file — `specs//tasks.md`, or the checklist in `plans/.md` — is the -durable record: it survives the session, it gets reviewed, it gets committed, and it -is what `scc` validates. +Your harness's todo list tracks the task you are on right now. The file — +`specs//tasks.md`, or the checklist in `plans/.md` — is the durable +record: it survives the session, it gets reviewed, it gets committed, and it is what +`scc` validates. -**Checking an item off in the session means checking the `- [ ]` box in the file -too.** A session that ends with its todo list complete and the file untouched has -lost everything except the code: the next session does not know which tasks were -done, and neither does the reviewer. +**Checking an item off in the session means checking the `- [ ]` box in the file too.** +A session ending with its todo list complete and the file untouched has lost +everything except the code: neither the next session nor the reviewer knows which +tasks were done.