Skip to content

feat: CodeGraph layer, Headroom hygiene, and a one-PR plan-run loop - #15

Merged
protonspy merged 6 commits into
mainfrom
feat/codegraph-layer-and-launch-integrations
Aug 4, 2026
Merged

feat: CodeGraph layer, Headroom hygiene, and a one-PR plan-run loop#15
protonspy merged 6 commits into
mainfrom
feat/codegraph-layer-and-launch-integrations

Conversation

@protonspy

Copy link
Copy Markdown
Owner

Five related changes, one per commit. Everything here came out of one question — Headroom was registering MCP servers nobody asked for — and each answer exposed the next.

internal/codegraph and scc graph

A third integration package beside rtk and headroom, on the same terms: composes argument vectors, finds the binary, installs it, and reads nothing inside .codegraph/.

scc graph build | sync | status | query | explore adds the two things scc knows and CodeGraph does not — the workspace root, so graph build from specs/ indexes the repo rather than a subtree, and whether the binary exists. status --check answers "does this checkout have a graph" with exit 2 and runs no subprocess, so a CI runner without CodeGraph installed still has a correct answer.

npm is the only installer scc will run. CodeGraph's headline install pipes a remote script into a shell — fine for a person to type, not something scc executes on their behalf. InstallHint names it.

scc launch declines what wrap would register

headroom wrap writes to disk, which this repo's docs denied. It registers MCP servers into the agent's config and they outlive the session — which is why Headroom ships unwrap. Two defaults change:

  • --headroom-mcp retrieve — keeps Headroom's retrieve tool (the proxy needs it to make compression markers actionable), drops the code-memory server. all and none are the ends.
  • --headroom-context-tool off — Headroom's context-tool setup appends RTK guidance to the same entry file scc rtk splices, behind its own markers. Neither marker is a substring of the other, so both tools' idempotency checks pass and both append.

The opt-out flags are discovered from wrap <agent> --help, not hardcoded. Headroom renamed this control once already, and the profiles disagree today — verified against 0.33.0:

scc launch claude    → wrap claude --code-memory none --no-context-tool
scc launch opencode  → wrap opencode --no-serena

A hardcoded table would have got one right and broken the other. A build advertising no opt-out is reported, not overridden.

Launch also indexes before starting — init when .codegraph/ is absent, sync when present — degrading like the Headroom path, because a graph is an enhancement. --no-graph opts out; plan-only runs report without indexing.

scc rtk prefers its own block

Measured against rtk 0.42.4: rtk init writes 139 lines / 5140 bytes, scc ships 18 / ~900. Both v2, both saying the same thing, and the entry file is preloaded into every request. End to end:

depois de rtk init:  7477 bytes
✓ CLAUDE.md — RTK block replaced (5139 → 765 bytes)
segunda passagem:    already there v2, byte-identical

--force becomes --keep. Where this costs something is a block claiming a newer version — a real downgrade — so the run names what it displaced and points at --keep.

The markers stay RTK's own rather than scc:rtk-instructions, with a test saying why: rtk init writes that exact pair, so sharing it is what makes the two tools converge on one copy.

plan-run learns a second loop shape

From a real run: implemented group by group, but the review subagents ran once at the end and one PR carried the whole plan — about an hour end to end. The skill only described the other shape, where subagents run N times and CI settles N times.

  • pr: per-group | per-plan, validated like the rest of the loop answers. The trade-off is stated: per-plan is cheaper and faster, and gives up granularity. Suite, lint, and scc validate stay per group even there — cheap, and what makes a later failure attributable.
  • The skill now takes what the invocation already decided and asks only for the rest. Re-asking what somebody just typed is the friction that stops people using it.
  • It says what delivered means: CI green on the PR, not the agent's assessment. Resuming had to follow — under per-plan nothing reaches main until the end, so a session reading its position from main would conclude no group was done and be wrong.

Template-set version bumped to 10 so scc update offers this to existing workspaces.

Verification

gofmt -l, go vet ./..., go test ./... green. -race and golangci-lint were not run locally (no gcc, no linter on this machine) — left to CI, which is the gate this PR is asking to pass.

A scaffolded workspace validates clean, including the edited skill against its own validator:

✓ validate: no findings
✓ skill: no findings

🤖 Generated with Claude Code

https://claude.ai/code/session_01J54qbk8RpC2tZH8LBz6T2b

protonspy and others added 6 commits August 4, 2026 02:36
A third integration package beside rtk and headroom, on the same terms: it
composes argument vectors, finds the binary, and installs it, and it reads
nothing inside `.codegraph/` — the database is CodeGraph's schema on
CodeGraph's schedule.

`scc graph build | sync | status | query | explore` wraps the CLI with the two
things scc already knows and CodeGraph does not: the workspace root, so
`graph build` from `specs/` indexes the repository rather than a subtree, and
whether the binary is there at all. `build` initializes once and refreshes
after, with `--force` for the full re-index; `status --check` answers "does
this checkout have a graph" with the findings code and runs no subprocess, so
a CI runner without CodeGraph installed still has a correct answer.

npm is the only installer scc will run. CodeGraph's headline install pipes a
remote script into a shell, which is a fine thing for a person to type and not
a thing scc executes on their behalf — no version pinned and nothing to
inspect. InstallHint names it and leaves the decision where it belongs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J54qbk8RpC2tZH8LBz6T2b
…dex first

`headroom wrap` writes to disk, which this repo's own documentation denied. It
registers MCP servers into the agent's config, and those registrations outlive
the session that made them — which is why Headroom ships `unwrap` at all. So
`scc launch` now declines two of them by default:

  --headroom-mcp retrieve   keeps Headroom's own retrieve tool, which its proxy
                            needs to make compression markers actionable, and
                            drops the code-memory server; `all` and `none` are
                            the two ends.
  --headroom-context-tool   off, because Headroom's context-tool setup appends
                            RTK guidance to the same entry file `scc rtk`
                            splices, behind its own marker pair. Neither marker
                            is a substring of the other, so both tools'
                            idempotency checks pass and both append.

The opt-out flags are read off `headroom wrap <agent> --help` rather than
compiled in. Headroom has renamed this control once already (`--no-serena` →
`--code-memory none`) and the profiles disagree today: 0.33.0 wants
`--code-memory none` for claude and `--no-serena` for opencode. A flag name
hardcoded here turns that kind of release into a launch that dies on "no such
option", which is strictly worse than one unwanted MCP server. A build that
advertises no opt-out is reported, not overridden.

WrapArgs now takes scc's options and the pass-through separately, and puts
scc's first: `wrap` parses every flag it recognizes out of the tail, so a
colliding pass-through argument — `--verbose`, which both Claude Code and
`wrap` define — has to land last to win.

Launch also brings the symbol graph up to date before starting: `init` when
`.codegraph/` is absent, `sync` when it is there. It degrades the way the
Headroom path does, because a graph is an enhancement — a missing binary, a
declined install, or a failed index all still start the agent. `--no-graph`
opts out and a plan-only run reports without indexing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J54qbk8RpC2tZH8LBz6T2b
…g one

Measured against rtk 0.42.4: `rtk init` writes 139 lines / 5140 bytes into the
project entry file, scc ships 18 lines / ~900, both stamped v2, both giving the
agent the same instruction. The entry file is preloaded into every request of
the session, so that difference is paid continuously rather than once. Leaving
the larger block in place because it got there first was not deference, it was
a standing cost.

So the default inverts: scc's block wins, and `--force` becomes `--keep` for
anyone who curated their own. `Present` now means the block is already scc's
byte for byte, which keeps re-running free. Where preferring scc's block does
cost something is a block claiming a newer version — a real downgrade — so the
run names the version it displaced and points at `--keep`, and the report
carries `was`, `bytes`, and `was_bytes`.

`scc rtk` also reports Headroom's block when it finds one. Headroom namespaces
its markers (`headroom:rtk-instructions`), so scc cannot address that block and
must not silently ignore it either: a file carrying both tells the agent the
same thing twice. Detected, named, `headroom unwrap <agent>` offered as the
fix, and left exactly where it is.

The markers stay RTK's own rather than becoming `scc:rtk-instructions`, and
there is now a test saying why: `rtk init` writes that exact pair, so sharing
it is what makes the two tools converge on one copy. Namespacing would make
each blind to the other and leave the file carrying both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J54qbk8RpC2tZH8LBz6T2b
…f delivered

From a real run: the plan was implemented group by group, but the review
subagents ran once at the end and one PR carried the whole thing. The skill
only described the other shape — a PR per group — where the subagents run N
times, CI settles N times, and there are N merges.

So `pr: per-group | per-plan` becomes an answer the loop takes, validated like
the rest. `per-plan` is cheaper and measurably faster; what it costs is
granularity, and the skill says so: a large diff to review, a red pipeline that
does not say which group broke it, and nothing on `main` if the run stops half
way. The suite, the lint, and `scc validate` stay per group even there, because
they are cheap and they are what makes a later failure attributable.

Two more things that run exposed:

The skill now takes whatever the invocation already decided and asks only for
the rest. "Implement the whole plan, one PR at the end, delivered when CI
passes" answers three of the four questions in one sentence, and re-asking what
somebody just typed is the friction that stops people using the skill at all.

And it states what delivered means, which it never did in one place: CI green
on the pull request, not the agent's own assessment. An assessment is a claim;
a green pipeline is a fact about the repository that a person can check without
the agent. Resuming had to follow — under `per-plan` nothing reaches `main`
until the end, so a session that read its position from `main` would conclude
no group was done and be wrong.

Template-set version bumped so `scc update` offers this to existing workspaces.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J54qbk8RpC2tZH8LBz6T2b
…eadroom

Both claims were load-bearing and both were wrong:

"Headroom wraps one process for one session and changes nothing on disk" was
the stated reason Headroom is the default where RTK is opt-in. It does write to
disk — MCP registrations in the agent's own config, which is why `unwrap`
exists. The default survives on other grounds; the reasoning did not.

"Everything after `--` is passed through untouched" is true of scc and not of
the command line it produces: `headroom wrap` takes every flag it recognizes
out of the tail before forwarding the rest, so a colliding argument is eaten
there. The escape is a second terminator.

Also documents `scc graph` and the launch-time index, the discovered-not-
hardcoded opt-out flags, and why `internal/rtk` shares RTK's markers rather
than namespacing its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J54qbk8RpC2tZH8LBz6T2b
The graph was being built on every `scc launch` and nothing instructed anyone to
query it. A rule that produces an index no reader is pointed at is the whole
investment wasted, silently.

`code-search.md` routes the structural question — where does this live, what
calls what, what breaks if I change it — to `codegraph_explore` or `scc graph
explore`, and leaves reading for the question about this exact text. It names
both surfaces because they answer identically and a subagent or a harness with
no MCP still needs the graph.

The load-bearing half is what it says the graph does *not* know: `docs/` is
Markdown and no part of it is indexed — not the glossary, not the wiki, not an
ADR, not a design.md. That matters more in this project than it would elsewhere,
because the methodology deliberately keeps the *why* out of the code. "Where is
this implemented" and "why is it like this" are different questions, and a rule
that sent both to the graph would route the agent away from the knowledge base
that exists to answer the second — which is the opposite of what scc is for.

A missing or stale graph is explicitly never a reason to stop, matching how
`scc launch` already degrades.

TestEntryFileNamesEveryRule caught the orphan on the first run, which is what
that test is for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J54qbk8RpC2tZH8LBz6T2b
@protonspy
protonspy merged commit c28bdbe into main Aug 4, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant