Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,21 @@ These landed after phase 10, and all are documented in `design/orchestration.md`
What scc adds over typing `codegraph` directly is the two things it already knows: the workspace root, so `scc graph build` from `specs/` indexes the repo rather than a subtree, and whether the binary is there at all. The graph itself is *not* an scc artifact — not in the manifest, never touched by `scc update`, and `.codegraph/` stays CodeGraph's directory on CodeGraph's schedule. Unlike the launch path, a missing binary in `scc graph` is a hard error: the whole command is the binary.

npm is the only installer scc will run. CodeGraph's headline install pipes a remote script into a shell (`curl … | sh`, `irm … | iex`), which is a fine thing for a person to type and not a thing scc executes on their behalf — `InstallHint` names it and leaves the decision where it belongs.
- **`scc map` and `scc patch`.** The artifacts are structured documents that happen to be Markdown, and the cost of treating them as prose is paid on every request rather than once: measured on a real workspace, one plan is 56KB and its 31 specs bring the corpus to ~90k tokens, so an agent answering "what is the next open task?" by reading the file carries the whole plan for the rest of the session. `map` turns a file into addressable pieces — `index | outline | tasks | show | blocks | find | trace` — and `patch` changes one of them — `check | uncheck | task | add | rm | append | prepend | replace | fm`.
- **The plan as a contract (`design/plan-format-v2.md`).** A plan stopped being a document and became a header plus a checklist. Six sections and no others — `Why`, `Paths`, `References`, `Out of scope`, `Tasks`, `Done when` — because a closed set is the *only* thing that ever capped a plan's size: the 56KB plan measured below got there through `## Notes`, which nothing forbade, growing to half the file. There is no line limit anywhere in the contract except on the description, since a limit that fires on a legitimate plan is worse than the growth it prevents; what there is instead is nowhere for prose to go.

**`## Decomposition` became `## References`, and that cost almost nothing** — `parseLeaves` recognizes a leaf by the `specs/<feature>/` citation *anywhere in the file*, not by the heading above it, so `Leaf`, the `specs/foo/` address and `map trace` all kept working untouched. What closes the door on leaves appearing elsewhere is `plan.unknown-section` itself.

**A task gains four flags and no more** — `_Depends_`, `_Priority_`, `_Status removed_`, `_Reason_` — and the vocabulary is closed because an italic one-liner is a shape prose also uses: a parser that absorbed any of them would silently eat a sentence and hand the task a region that is not the task. An unknown one is `task.unknown-flag`, reported and left where it sits — since the box is the state, a flag that could restate it is the `item-has-two-records` defect arriving by another door, and there is no `_Blocked_` because that is derived from `_Depends_`. `(Unit)`/`(TDD)` is reused as the test strategy rather than a new `_Test_` flag — zero migration, and the concept already had a name. `_Status_` never takes `open` or `completed`.

Three consequences had to land together or the result is worse than before: `Task.End` covers the flags (so `map show` returns them and `patch rm` removes them), `Detail` excludes them (so the searcher does not index `_Priority 2_` as prose), and **`renderTask` re-emits them plus the continuation** — without that, `patch task --method TDD` was a data-loss command that deleted a sixty-line description and every dependency the task declared.

**The reading surface is what gives "never read the plan" its authority.** `map brief` is the header, `map tasks` is the checklist, and no command returns both — so a session pays `brief` once and `--next` per task instead of ~14k tokens per reread. Forbidding the read without offering the equivalent query produces an agent that disobeys the rule, correctly — so the surface shipped in the phase before the rule did. `--next` is now determined (eligible → priority ascending, absent last → number compared *numerically*, which is also the fix for `1.10` sorting before `1.9`), and `--ready`/`--blocked`/`--deps` share that one implementation, because two notions of eligibility would be two answers to "what do I work on".
- **`scc plan approve|reseal|migrate`, and the seal.** `approve` validates, then writes `status: approved` and a `checksum:` over the file minus its own checksum line, LF-normalized. It is **tamper-evidence, not prevention** — `reseal --force` is one command away and sha256 is public — and it is recorded that way here so nobody builds a guarantee on it later. The check runs before an edit is applied, which is the whole value: a harness that edited by hand and then ran `patch check` would otherwise have its edit resealed by the command that should have reported it. A plan with no `status:` is never checked, which is what makes every pre-existing plan keep working.

After approval the work is fixed and only discovery moves: `add` allocates the number (high-water mark including removed tasks, so nothing is stored anywhere) and demands `--reason`; `rm` strikes the task out where it stands rather than deleting it; rewriting a task or the prose is refused. What discovery can never touch is guaranteed structurally rather than by instruction — `Why`, `Out of scope`, `Done when` and the title are reachable only through `append`/`prepend`/`replace`, and those are exactly the three refused.

`migrate` moves a v1 plan across: it renames `Decomposition`, moves every other heading to `plans/archive/<name>-notes.md` (safe because the plan scanner uses `ReadDir` and skips directories), creates the missing required sections **empty and lets the findings appear** — a placeholder that satisfied the validator would be a plan that lies — and writes `status: draft`, never `approved`.
- **`scc map` and `scc patch`.** The artifacts are structured documents that happen to be Markdown, and the cost of treating them as prose is paid on every request rather than once: measured on a real workspace, one plan is 56KB and its 31 specs bring the corpus to ~90k tokens, so an agent answering "what is the next open task?" by reading the file carries the whole plan for the rest of the session. `map` turns a file into addressable pieces — `index | outline | brief | tasks | show | blocks | find | trace` — and `patch` changes one of them — `check | uncheck | task | add | rm | append | prepend | replace | fm`.

**The addresses are the design.** A task is `1.2`, a requirement `R1.2`, a section `#notes`, a leaf `specs/<feature>/`, a paragraph `notes:7`; `L120-160` is the escape hatch and the only form that is a line number. That is what lets `patch` write into a file nobody read: a line number stops being true the moment anything above it moves, so an editor addressing by line has to read first — which is the cost the package exists to remove. The guard that reading-first was providing is replaced by three that are stronger for a structured file: an address that does not resolve is an error and never an insert at a guess, the file is re-validated afterwards and **rolled back if the edit introduced a finding** (exit 2), and the displaced and written lines are printed back — elided past a few lines, because a confirmation that echoed 400 lines would put the file in context by the back door.

Expand All @@ -49,6 +63,8 @@ These landed after phase 10, and all are documented in `design/orchestration.md`
**`internal/artifact` owns the grammars, and `internal/validate` consumes them.** The task grammar used to live in the validator; a reader that disagreed with the validator about what a task is would be worse than no reader. The parser now states facts about a line (`Methodologies`, `Loose`, `HasCitation`) and turning a fact into a finding stays in `validate` — which is also what lets `map` read a malformed artifact instead of refusing exactly the file a user most needs to inspect.

**No search engine.** The obvious reach for `find` is an inverted index; at 352KB and 94 artifacts a linear pass ranks the whole workspace in 55ms, and Tantivy or its kin would cost a CGO surface or a second binary against a stdlib-only `go.mod` and a six-platform cross-compile. What precision needed here was not a better index but a better *unit*: BM25 over addressable regions rather than lines, so a hit comes back as something `show` accepts. The seam is `artifact.Search` — it takes artifacts and returns hits, and nothing outside that file knows how it found them.

**`map find` is now undocumented rather than removed.** Its stated reason was the whole corpus (94 artifacts, 352KB), not the plan — and with the plan small, searching *inside* one stopped making sense, while searching `design.md` and the knowledge base is still the only alternative to reading a file. So `runMapFind` and `search.go` stay and the line comes out of `rules/artifacts.md`, `entry.md` and `mapUsage()`: deleting the code saves nothing, and deleting the line saves tokens in every request of every session.
- **`caveman.md`, the register the agent answers in.** The output budget belongs to the code: prose about the work is written once and then carried in every later request of the session, so narration is the part of a long run that can be cut without losing a fact. It ships as a *rule* rather than a skill because it is on by default, and a default the model has to decide to load is not one — the cost is what every rule costs, since the harnesses that read `rules/` preload it. One level (ultra) rather than a dial, because three descriptions of the register are three things to keep true instead of one, and nobody turns the dial.

What it must never compress is the line that keeps it honest: artifacts under `specs/`, `plans/`, `docs/`, anything a validator parses or a shell runs, quoted output, commit and PR bodies, and questions asked of the user. A denser EARS line is a finding, not a saving.
Expand Down Expand Up @@ -115,7 +131,7 @@ Three packages sit off to the side of that tree — `rtk`, `headroom`, `codegrap
| `internal/assets` | The embedded template set — rules, review agents, skills, slash commands, artifact templates. **Workspace templates are data-free except for the harness profile** (a `(version, harness)` pair still renders byte-identically everywhere, and the manifest records both, so the future three-way merge can still reconstruct the old side); **artifact templates take data** (`spec new` renders them and the user owns the result); **seeds are the `docs/` anchors** — data-free like a workspace file, untracked like an artifact. `Render(h, file)` is the only way to get a workspace file's bytes: it expands paths and synthesizes the per-harness header for agents and commands. `Version` is the template-set version and must be bumped whenever a workspace template changes. |
| `internal/scaffold` | Applies the template set to a root (`Apply`) and brings an existing one current (`PlanUpdate`/`ApplyUpdate`). Idempotent, never overwrites without being told to, manifest written last. |
| `internal/mdscan` | The only Markdown parser: fence- and HTML-comment-aware headings, checkboxes, links, wikilinks, slugs, plus a small frontmatter reader. `Body` is the comment/fence-stripped text every validator applies its grammar to. |
| `internal/artifact` | The navigable model of one artifact, layered on `mdscan`: sections (two ends — the subtree, and the body before the first child), tasks with their continuation, requirements, decomposition leaves, paragraph blocks. Owns **every grammar** (task, requirement, spec reference), `Find` for address resolution, `Editor` for line splices resolved against the original and applied bottom-up, and `Search`. Knows nothing about findings or exit codes. |
| `internal/artifact` | The navigable model of one artifact, layered on `mdscan`: sections (two ends — the subtree, and the body before the first child), tasks with their continuation *and their flags*, requirements, spec-reference leaves, paragraph blocks. Owns **every grammar** (task, requirement, spec reference, flag), `Find` for address resolution, `Editor` for line splices resolved against the original and applied bottom-up, `Search`, the schedule (`Ready`/`BlockedTasks`/`Next`/`Cycles`, one implementation shared by `--next`, `--ready` and `--blocked`), and the seal. Knows nothing about findings or exit codes. |
| `internal/ears` | EARS requirement parsing, all five patterns plus complex. |
| `internal/validate` | The eight validators, one file each, sharing `mdscan` and `finding`. The exception is `stack_manifests.go`: the seven dependency-file readers age on their own schedule, so they sit beside the rule rather than inside it. |
| `internal/rtk` | RTK's marker pair and the idempotent splice of its block into the entry file, plus finding or `cargo install`ing the binary. |
Expand All @@ -141,6 +157,8 @@ Three packages sit off to the side of that tree — `rtk`, `headroom`, `codegrap

**Writes are atomic.** Use `workspace.AtomicWrite` for anything a concurrent reader might see.

**The rules are a standing budget, not a place to explain things.** Every file under `<harness>/rules/` is preloaded into every request on a harness that reads them, so a line added there is paid continuously rather than once. `TestRulesStayShortEnoughToBePreloaded` caps a rule at 55 lines (three predate the budget and are capped where they stand; they may shrink, never grow), and `TestScaffoldedEntryFileStaysShort` caps the entry file at 60. When a feature does not fit, the answer is to move the detail into a `--help` string — read only when consulted — and keep the question→command table in the rule. The plan-format change was measured in and out on this basis: it landed at +2 lines and +709 bytes across `entry.md` + `artifacts.md` + `tasks.md`, against ~14k tokens saved per plan reread.

**A command that edits an artifact verifies it afterwards.** `scc patch` snapshots the file, writes, re-runs the validator that owns it, and restores the snapshot if the edit introduced a finding the file did not already have. Two details are load-bearing: the comparison is on `rule + message` and deliberately **not on line number**, because an insertion moves every finding below it and comparing on line would blame this edit for the whole tail of a pre-existing problem; and an artifact scc has no validator for is written and *reported as unverified* rather than silently claimed clean.

**The marker is the file `<harness>/scc-manifest.json`, never the harness directory.** Two reasons, and both are load-bearing: every harness has a global twin in the user's home (`~/.claude`, `~/.codex`, `~/.config/opencode`) that exists on any machine running that tool, so an upward walk accepting the *directory* would resolve the root to `$HOME` for any command run outside a workspace — every command would then read and write the user's global configuration. And those directories exist in every repo that merely *uses* the tool, where scc was never initialized. `workspace.Find` therefore stats a regular file, for each harness in turn.
Expand Down
Loading
Loading