feat: read and edit a plan or spec by address, not by the whole file - #17
Merged
Conversation
An agent answering "what is the next open task?" reads the whole file to find out, and a plan that decomposes into thirty specs is tens of kilobytes of prose wrapped around a dozen checkboxes. Measured on a real workspace, one plan is 56KB and its 31 specs bring the corpus to ~90k tokens. internal/artifact is the navigable model of one artifact, layered on mdscan so fenced blocks and HTML comments are excluded here exactly as they are for the validators. It turns a file into addressable pieces — sections, tasks, requirements, decomposition leaves, paragraph blocks — each with a stable name and a line range. None of those addresses is a line number, which is the point: a line number stops being true the moment anything above it moves, so an editor that addresses by line has to read the file first. Editor applies splices resolved against the original and applied bottom-up, so a batch cannot invalidate its own line numbers halfway through, and two edits to one region are refused rather than merged. Two things measurement decided rather than taste. Blocks exist because section addressing bottoms out: that plan's `## Notes` is 411 lines with no headings inside it, but every paragraph opens with a bolded thesis, so the leads alone are an index a twentieth of the size. And Search indexes addressable regions rather than lines — at 352KB a linear BM25 pass ranks the whole workspace in 55ms, and what precision needed was a better unit, not an inverted index that would cost a CGO surface against a stdlib-only go.mod. The task grammar moves here and validate consumes it. 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 — how many methodology annotations it carried, whether it carried the citation separator — and turning a fact into a finding stays in validate, which is also what lets the reader open a malformed artifact instead of refusing exactly the file a user most needs to inspect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YLNd5uRpQDQ8ffvEvLJ3q
…ed by address `scc map` is the read half — index | outline | tasks | show | blocks | find | trace. Measured on a 56KB plan: the outline is 878 bytes, the paragraph index of a 411-line Notes section is 5KB, and showing one addressed note is 514. `scc patch` is the write half — check | uncheck | task | add | rm | append | prepend | replace | fm. It exists for a policy rather than a cost: a harness makes a tool read a file before it may edit it, because a string-match edit against a file nobody looked at is a footgun. That guard is right for a text editor and wrong for a structured artifact, so it is replaced by three that are stronger here. An address that does not resolve is an error and never an insert at a guess. The file is re-validated after the write and the change is rolled back if it introduced a finding the file did not already have — exit 2, file untouched. And the displaced and written lines are printed back, which is the confirmation the read was standing in for. Two details in that verification are load-bearing. The comparison is on rule plus message and deliberately not on line number: 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. A change that would delete more than a screenful stops and asks for --force. An address is a name, not a span, so `replace #notes` reads as one small edit and resolves to four hundred lines — and the whole point of the command is that nobody looked at the file first. The refusal elides what it declined to delete, since a confirmation that echoed four hundred displaced lines would put the file in context by the back door while reporting that it refused to touch it. A requirement id is scoped to its own spec, so `map trace R2.5` unscoped answers with the list of specs that define it and stops rather than concatenating nine traces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YLNd5uRpQDQ8ffvEvLJ3q
… the layout block A capability the agent never learns about is a capability nobody uses, so the knowledge lands in three places on three different terms. artifacts.md is a new rule, 55 lines, inside the preload budget: the address forms, the read verbs, and the write verbs, plus why patch is the way to tick a box. It exists as its own rule rather than as a section of code-search.md because that rule is about the symbol graph and was already over its line cap — and because the concern is not only search. code-search.md now points at it, closing the gap it already named: the graph indexes code, not this repository's Markdown. The entry file carries the reflex rather than the rule — which tool for which corpus, in nine lines. That earns its place for Codex and opencode, where rules/ is scc's own directory and nothing loads it, so the entry file is the only thing that will ever mention map or patch. On Claude Code, which loads rules/*.md at launch with the same priority as CLAUDE.md, it is a summary of a rule already in context. The explanatory bullets for code-search and artifacts became one-line pointers to pay for it. plan-run stops reading the plan to find its place. Mapping it is how the loop reads the shape, `patch check` is how a group's boxes get ticked, `patch fm` is how the kickoff answers get recorded, and re-mapping at each group boundary replaces re-reading — the read this loop performs most often. `## Notes` gets its own instruction, because that section is the longest in any real plan and most of it decides nothing about order: the paragraph index answers the ordering question for about a sixth of the tokens. The layout block is now one column across all three harnesses. The padding is computed from the profile rather than written into the template, because a run of spaces that lines up for `.codex/` is ragged for `.opencode/`, and half that column is hand-written while half is derived — so nothing but TestEntryLayoutBlockIsAligned keeps the two halves agreeing, and the failure is invisible in review. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YLNd5uRpQDQ8ffvEvLJ3q
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YLNd5uRpQDQ8ffvEvLJ3q
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An agent answering "what is the next open task?" reads the whole file to find out. Measured on a real workspace: one plan is 56KB / ~14k tokens, and its 31 specs bring the corpus to ~90k tokens. That cost is paid on every request rather than once, because a token read is paid
1×on entry and0.1×on every subsequent request of the session.This adds a read surface and a write surface that address an artifact by name.
scc map— readindex | outline | tasks | show | blocks | find | tracescc map <plan>scc map blocks <plan> notesscc map show <plan> notes:10scc patch— writecheck | uncheck | task | add | rm | append | prepend | replace | fmIt exists for a policy rather than a cost: a harness makes a tool read a file before it may edit it. That guard is right for a text editor and wrong for a structured artifact, so it is replaced by three that are stronger here — an address that does not resolve is an error and never an insert at a guess; the file is re-validated and the change rolled back if it introduced a finding (exit
2, file untouched); and the changed lines are printed back.A change deleting more than a screenful stops and asks for
--force, with the displaced region elided — a confirmation that echoed 400 lines would put the file in context by the back door while reporting that it refused to touch it.Addresses
None is a line number, which is what lets a write survive an edit above it:
Two things measurement decided
blocksexists because section addressing bottoms out. That plan's## Notesis 411 lines — half the file — with no headings inside it, but every paragraph opens with a bolded thesis. The leads alone are an index a twentieth of the size.No search engine. At 352KB / 94 artifacts a linear BM25 pass ranks the whole workspace in 55ms. Tantivy or its kin would cost a CGO surface or a second binary against a stdlib-only
go.modand a six-platform cross-compile. What precision needed was a better unit — addressable regions rather than lines, so a hit comes back as somethingshowaccepts. The seam isartifact.Search.One grammar, two consumers
The task grammar moves into
internal/artifactandinternal/validateconsumes it. A reader that disagreed with the validator about what a task is would be worse than no reader. The parser states facts about a line; turning a fact into a finding stays invalidate— which is also what letsmapopen a malformed artifact instead of refusing exactly the file a user most needs to inspect.Also
artifacts.md, a new rule inside the 55-line preload budget;code-search.mdpoints at it, closing the gap it already named.plan-runstops re-reading the plan to find its place — it maps it, and ticks boxes withpatch check..codex/is ragged for.opencode/.TestEntryLayoutBlockIsAlignedis what keeps the hand-written and derived halves agreeing.assets.Version→12.Verification
gofmt·go vet· full suite green. The race detector could not run locally (no gcc on this Windows box) — CI covers it on Linux and macOS.Behaviour proven against the real 94-artifact corpus:
check/uncheckround-trips the 56KB plan byte-identically, an edit that introducesplan.item-has-two-recordsis rolled back with the file intact, and 28 pre-existing findings do not block an unrelated edit.🤖 Generated with Claude Code
https://claude.ai/code/session_019YLNd5uRpQDQ8ffvEvLJ3q