Public: anyone can read this; the crew can change it.

Hollow work does not pass the gate: an action no surface calls, or a screen no page links to, is named and the check goes red #681

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

Closes #615

shipped production is serving it for issue #615 — its issue is already done deploy e2a191c

opened this

Closes #615

Work in progress: the audit (scripts/hollow.ts) is here; the gate test, the allow-list entries and the fixes for what it finds on main follow in this pull.

🤖 Generated with Claude Code

commented

What a person sees. A pull that adds an action nobody can call, or a screen nobody can get to, turns the gate red, and the failing line names it and says what to do.

What is in it

  • scripts/hollow.ts: the audit, and a command. bun scripts/hollow.ts prints one line per problem and exits 1.
  • scripts/hollow-allow.ts: what main carries today, each entry under the issue that owns it.
  • test/hollow.test.ts, in GATE: 25 tests. The audit over this tree, the command, and each rule on small trees; the spawnForge shape and the fleet-graph shape are both there.
  • src/actions/origins.ts: mirrorRepo is removed. The audit named it, and nothing called it, tests included.
  • CLAUDE.md and README.md: the rule, a paragraph each.

How it decides

  • An action. Every exported function under src/actions/. Reach starts at every declaration under src/http, src/web, src/crew, src/cli and src/tui and at each module's top-level statements, and follows the names a reached declaration mentions, declaration by declaration, through imports and re-exports. An action called by another action or through src/fleet/ is reached; one that only its tests call is not.
  • A screen. A GET route with no parameter whose handler can draw the page (page in web/layout.ts). On main that is 26 routes, and no API route, fragment, redirect or file is among them. It is linked when its path stands in a string of a module that a reachable page uses, starting from / and from every page whose path takes a parameter. A page that links only to itself, or two that link only to each other, are still orphans.

Where it departs from the issue's wording, and why

  • "the diff against main". The forge runs the check in a directory with no .git, so there is nothing to diff. The allow-list is the baseline instead: whatever is hollow and not listed came with the pull. An entry that is no longer hollow fails the check, so the list only shrinks.
  • "nothing under src/http ... imports". Read as a direct import from those five directories, that rule named 22 functions on main (at 596540a), and 20 of them are called: the six require* helpers in actions/access.ts and originPush only ever by other actions, setIssueLabels, requeueRepoRun and spawnForge through src/fleet/ and src/spawn/, and ten are helpers that a called function of the same file uses. The audit follows the calls instead.
  • Screens are an addition, asked for by the steward: the fleet graph was the other hollow thing on main.
  • Where it runs. As a test in GATE, the way scripts/tree.ts and scripts/gate.ts do, so forge.json is unchanged and bun test catches it too.

What it found on main

orphanwhat it ishere
src/actions/origins.ts#mirrorRepodead: no caller in src/ or test/removed
src/actions/origins.ts#resetConnectLimita test seam, called by 17 test filesallowed under #685 (new)
GET /fleet/grapha page no page links toallowed under #448

spawnForge is no longer hollow: src/spawn/door.ts calls it, and the API and the MCP call the door. Cutting those two imports in memory makes the audit name spawnForge and childDirOf again.

Not touched. The release workflow and the chart. Wiring this into the release workflow is the owner's choice; the command is bun scripts/hollow.ts.

Limits

  • Names are followed by spelling, not by scope, and a namespace or dynamic import reaches the whole module: where it cannot tell, an action counts as called.
  • Everything under the five surface directories counts as a caller, routed or not.
  • A route with a parameter is not judged: its links are computed from rows.
  • A constant holding a wrapped function (export const x = wrap(() => ...)) is not judged.
  • It imports typescript, the dev dependency the gate already runs as tsc, to read the sources.

Verified

  • bun x tsc --noEmit clean.
  • bun run check locally: 1379 tests in 98 files, 2 failed, both also failing on a pristine main at the time (the #230 studio test, since fixed on main by #696, and a crew test that fails only on macOS). After merging main again: test/hollow.test.ts, test/check-list.test.ts, test/tree.test.ts and the #230 test pass.
  • On the real tree, in memory: linking /fleet/graph from the board clears it; cutting the spawn door's imports names spawnForge.

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs