Skip to content

refactor(core): move the command descriptor registry into its own workspace package - #2348

Merged
thymikee merged 3 commits into
mainfrom
claude/2336-command-registry-package
Sep 6, 2026
Merged

refactor(core): move the command descriptor registry into its own workspace package#2348
thymikee merged 3 commits into
mainfrom
claude/2336-command-registry-package

Conversation

@thymikee

@thymikee thymikee commented Sep 6, 2026

Copy link
Copy Markdown
Member

Summary

src/core/command-descriptor/, src/command-catalog.ts, src/core/wait-positionals.ts and src/core/parse-timeout.ts imported nothing from root src/, yet were the daemon's largest root coupling. They move as git renames into a new private package @agent-device/command-registry (deps: contracts, selectors), one subpath per module pointing straight at the moved file; no index.ts, no re-export at the old path. Every consumer — daemon, commands, mcp, cli, cli-schema, core, root, client, ai-sdk, scripts, tests — switches to the owning specifier; contracts is untouched. Six descriptor tests that import root src/ stay there, which a package may not do; the rest move unchanged. The __OWNER_FILES__ declaration moves with its only consumer.

Rebased onto dcd8b65, so it carries #2347's code-signature walker and #2346's daemon-request.ts / session-state.ts split. One test pins that the daemon code graph reaches this package, and two doc comments are corrected off their pre-#2347 figures.

Rename-only proof — git diff -M90% --stat origin/main...HEAD: 42 renames, all 12 moved sources at 92–100%.

Closes #2336.

Validation

pnpm check:affected --run on this tree: format, lint, typecheck, layering, di-seams, fallow, mcp-metadata, build, package, integration-node, macos-coverage green. vitest-related: 9746 passed, 3 failed — the chmod 0500 tests that fail identically at origin/main here, since uid 0 ignores the mode.

Daemon → root src/ value edges 148 → 112 (−36, ceiling 118), from pnpm depgraph. CLI help byte-identical to its base: 82 sections, one sha256.

Signature walk, measured against pre-#2347 main: 620 → 1,461 modules, 12 of them this package; cold walk ~90ms; and the CLI hot path — the cached per-invocation signature — 2.9ms → 7.5ms.

🤖 Generated with Claude Code

https://claude.ai/code/session_016DkNZCMWtm1KUbseKANADi

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-06 10:55 UTC

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.50 MB 4.50 MB -28 B
Package (unpacked) 4.50 MB 4.49 MB -28 B
Package (download) 1.33 MB 1.33 MB +10 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 28.4 ms 28.1 ms -0.3 ms
CLI --help 83.0 ms 82.2 ms -0.8 ms

thymikee commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

Android / Smoke Tests failed on e18aa8f (run 34023633671), and I do not believe it belongs to this PR.

The failing step is the live emulator orientation check in smoke-android-emulator:

step: wait for landscape
command: agent-device wait text landscape 10000 --platform android --serial emulator-5554 ...
error.details: { reason: "wait_capture_stalled", captureStalled: true, readableCaptures: 0, timeoutMs: 10000, waitedMs: 10332 }

readableCaptures: 0 means the wait dispatched and its budget resolved correctly — no readable snapshot capture completed on the emulator inside the window. Nothing in this diff touches the capture path: it is a module move, all twelve sources rename at 92–100% with only import specifiers edited, the descriptor tables are unchanged, and the whole versioned help surface is byte-identical to origin/main (82 sections, same sha256). The wait positionals and timeout policy moved unmodified and their unit coverage passes.

This lane is also independently red on recent main pushes — runs 7795, 7788, 7761 — though it passed on this PR's merge base 27a97ee. No fix for it exists that I could port here.

I have re-run the failed job once (my one allowed re-run). Every other completed check on this head is green: Lint & Format, Typecheck & Package, Integration Tests, Compatibility & Provenance, Bundle Size, Ownership and context budgets, command-docs-gate, CodeQL, and the other Smoke Tests lane. If the re-run fails the same way, that is evidence about the emulator lane, not this diff, and I will say so rather than change the PR to chase it.


Generated by Claude Code

@thymikee

thymikee commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

The package extraction and gates are otherwise clean at e18aa8f, and CI is green. One source-checkout regression blocks readiness: the daemon signature walker follows only relative imports. The registry and derive modules were previously in that graph; their new workspace imports are ignored. Editing the moved command policy can therefore reuse a daemon running the old implementation.

This shares the signature-resolution fix needed by #2347. Please make that a shared prerequisite rather than adding a second resolver, and prove a package-owned registry edit changes the source daemon signature. Bundled installations are not affected.

@thymikee thymikee left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial review from the #2335 coordinator, at e18aa8f. I verified the claims against the tree rather than reading the body — the substantive ones hold up. Three things to record before this merges; none block.

Verified independently: old paths are gone with no re-export shim (src/command-catalog.ts and src/core/command-descriptor/ absent from the tree); zero suppressions added anywhere in the diff (no fallow-ignore, APPROVED_OVER_CEILING, ts-expect-error, disable comments, skipped tests); enforcement edits are confined to e18aa8f and none leaked into the implementation commit; all 12 subpaths point directly at moved source files with no index.ts.

1. The HUB_ENTRY_FILES deletion deserves an explicit note — it is not a pure relocation.
Dropping registry.ts and platform-execution-entry.ts from that list is the one edit here whose failure mode is silent, so I checked it rather than taking the commit message's word: discoverFacadeEntryFiles delegates to R11's manifest-first facadeEntryFiles, and the new manifest publishes both ./registry and ./platform-execution-entry straight at the moved sources, so both are rediscovered. That part is correct.

What the commit message does not say is that the polarity of denyPlatformImplementations flips with the move: hub rows set it false, package entry surfaces set it true. These two files are now held to the stricter rule. That is fine — and green — but a future reader comparing the lists will read a deletion as lost coverage. One sentence in the commit body stating the flip, and that it was intentional, converts a silent semantic change into a recorded one.

2. Baseline discrepancy against the issue — worth settling now, not at the end.
#2336 states 154 daemon → root src/ value edges; you measured 150 → 114 with scripts/depgraph/build.ts. The delta is −36, exactly the issue's predicted reduction, so the change is right and the ceiling is met either way. But the umbrella (#2335) promises a final re-measurement of this number, and it was originally taken over a "production-only import graph (1,629 files, 8,805 edges)". Please name the filter difference (production-only vs. what you counted) in the PR body so the closing measurement uses one definition rather than silently comparing two.

3. Name the six stranded tests' root dependency.
src/__tests__/command-descriptor-*.test.ts stay behind because a package may not import root src/. That is the correct call, but it leaves AGENTS.md's "tests mirror source topology one-to-one" broken for six files, and the reason is now invisible. List which root-src/ symbol each of the six actually needs. That turns an acknowledged exception into an inventory a later wave can act on — and it is the same kind of residual-seam list #2342 is required to produce.

For the record, the three chmod 0500 unit failures are not yours: the container runs as uid 0, which ignores the mode. #2347 hit the identical three independently, and CI is green here across all 21 checks.


Generated by Claude Code

@thymikee

thymikee commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

This head now conflicts with main. Resolve that alongside the shared source-signature prerequisite from #2347, then rerun the affected gates.

thymikee commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

Fixed in 009887e. You were right, and it was wider than the registry: the walk followed relative specifiers only, so every workspace package — kernel, contracts, capture-kit, selectors — has been outside the source-checkout signature since #1490. This PR moved the registry across that boundary, which is what made it observable.

One resolver, in the file #2347 moves. The fix is inside walkDaemonCodeGraph itself, so whichever of us lands first, the other carries it: if #2347 goes first I rebase onto the walker in @agent-device/host-kit; if this does, the move takes the fix with it. No second resolver anywhere.

Three properties worth review:

  • Rooted at the walked checkout, not at the walker. import.meta.resolve would have resolved against this module's own location, so a walk of a sibling checkout would fingerprint this one's packages. Resolution goes through <root>/node_modules/@agent-device/<name>, the link the loader follows, and the symlink is resolved so stamps carry repository-relative labels the cache can re-stat.
  • The manifest is in the graph. Its exports map is the resolution, and the cache replays a walk's whole input from statSync — so a manifest that chose a module is stamped beside it, and repointing a subpath moves the edge. Manifests are stamped but never scanned: JSON names no imports, and scanning one would drag in each package's whole published surface instead of what the daemon loads.
  • Bundled installs unaffected, as you said: dist/src/internal/daemon.js contains zero @agent-device/ strings, so that arm walks exactly as before.

Proof. src/daemon/__tests__/code-signature.test.ts (new, 5 tests): a fixture checkout with a linked workspace package proves the module edit changes the signature while an unexported sibling does not, that repointing the exports map moves the edge, and that an uninstalled package is recorded in absentPaths. The last test is your case on this repository — the real src/daemon.ts walk stamps packages/command-registry/src/registry.ts, catalog.ts and the manifest. Planted red: all 5 fail against the previous walker, all 5 pass with it.

Cost, measured here: 608 → 1,460 modules; cold walk 35ms → ~80ms; the per-invocation cached path 3.1ms → 7.5ms. The two doc comments quoting "~800 modules / ~30ms" are updated rather than left stale.

pnpm check:affected --run at 009887e: all 12 selected checks green except vitest-related, which fails only on the three chmod 0500 tests that also fail at origin/main in this sandbox (uid 0 ignores the mode). A third commit splits the per-file edge resolution out of the walk — Fallow's complexity gate caught it at 9 cyclomatic / 16 cognitive after the fix, so that is a real gate finding, not a cleanup.


Generated by Claude Code

thymikee commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

Merged main at b4ebd77 (merge commit, no rebase — #2339 is stacked here). Head is fc40090.

Signature coverage, verified after the merge. #2347 landed the same fix in packages/host-kit/src/code-signature.ts, so this branch's copy is gone: one resolver, the one on main. The walk from src/daemon.ts now stamps 1,461 modules, 12 of them this package — the eleven imported sources plus packages/command-registry/package.json, the manifest whose exports map chose them. Touching packages/command-registry/src/registry.ts changes the signature (graph:1461:80027f71…graph:1461:90daac5d…). packages/host-kit/src/code-signature.test.ts gains one test pinning that membership, beside the existing test that pins the walker's own.

Conflict: only src/cli/commands/router-types.ts#2349's CommandProgressState import beside this branch's catalog specifier; both kept. One fixture string in the relocated CLI-route test also went back to the specifier it illustrates, which the move had rewritten as if it were a real import.

The three review points are recorded in the merge commit body. In short: dropping the two entries from HUB_ENTRY_FILES flips denyPlatformImplementations false → true, since the manifest now publishes them as domain facades — both carry the ADR-0019 no-platform-implementation assertion and pass it, and that strengthening is intended. The 150-vs-154 gap is a filter difference: pnpm depgraph collapses to one edge per file pair and I counted value edges only; all three kinds on the same graph give 174 into 67 root files, the shape the issue's own breakdown uses, and both readings drop by exactly 36. The six root-kept tests each need named root symbols (parity: STRUCTURED_BATCH_COMMAND_NAMES, DAEMON_COMMAND_DESCRIPTORS, canRunReplayScopedAction, DaemonRequest; and so on) — full list in the commit.

Re-measured on the new base: daemon → root src/ value edges 148 → 112, still −36. Versioned CLI help byte-identical to b4ebd77 across 82 sections. pnpm check:affected --run at fc40090: all 12 checks green except the three chmod 0500 tests that also fail on origin/main as uid 0.


Generated by Claude Code

thymikee commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

Signature-walk cost, re-measured on this head against pre-#2347 main (835af32), since #2347 shipped without these numbers:

pre-#2347 main head fc40090
modules stamped from src/daemon.ts 620 1,461
cold walk ~35 ms ~90 ms
cached signature, per CLI invocation 2.9 ms 7.5 ms

The last row is the one on the hot path: resolveLocalDaemonCodeSignature runs it on every command in a source checkout, and it is now ~4.6 ms dearer because the cache re-stats 1,461 stamps instead of 620. That is the price of the fingerprint being honest — the alternative was the stale-daemon bug — but it is worth knowing, and worth revisiting if source-checkout startup gets attention. Corroborates #2347's own independent 619 → 1,459.

Of this package: 11 imported sources plus packages/command-registry/package.json. owner-files.ts is deliberately absent — nothing in the production graph reaches it, which is the property check:bundle-owner-files exists to keep.

Two doc comments in packages/host-kit/src/code-signature-cache.ts and src/daemon/client/daemon-launch-spec.ts still described the pre-#2347 graph ("~800 modules", "~30ms", "returns in ~1.5ms"). This branch corrects them rather than leaving figures that the workspace-following walk had already invalidated.


Generated by Claude Code

@thymikee

thymikee commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

Independent review at fc400904b9decc6eadaebb42389c0809b4f5904b: clean. The registry move keeps direct package subpaths and source-signature coverage of the workspace package; no legacy root path or behavioral finding remains. All current CI, including iOS and Android smoke, is green. Ready to merge.

@thymikee thymikee added ready-for-human Valid work that needs human implementation, judgment, or maintainer merge and removed ready-for-human Valid work that needs human implementation, judgment, or maintainer merge labels Sep 6, 2026
@thymikee

thymikee commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

The code review is still clean, but this head now conflicts with main after #2346 merged. I removed ready-for-human; resolve the conflict and revalidate before landing.

…kage

`src/core/command-descriptor/`, `src/command-catalog.ts`, `src/core/wait-positionals.ts`
and `src/core/parse-timeout.ts` move as git renames into a new private package
`@agent-device/command-registry` (deps: contracts, selectors). One subpath per module
points straight at the moved file; no `index.ts`, no re-export at the old path. Every
consumer switches to the owning specifier.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jqfa11D8QsCMuL17SsLvDz
…de graph

The daemon reaches the registry and its catalog only by workspace specifier. A walk
that stopped at the package boundary would report an unchanged signature after a
descriptor edit, and the client would keep reusing a daemon running the superseded
policy. The manifest is asserted beside the sources because its `exports` map is what
chose them. The cache doc comment quoting the old ~800-module graph is corrected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jqfa11D8QsCMuL17SsLvDz
R66's `COMMAND_DESCRIPTOR_MODULE`, R16's record-runtime join subject and the Fallow
`AssertTrue` totality-guard key follow the registry to its package. The two descriptor
hubs leave `HUB_ENTRY_FILES` because the package manifest now publishes them, so the
eager-closure gate discovers them as facades and one entry gets one rule; this also
flips `denyPlatformImplementations` from false (hub) to true (package entry) for both,
which is intentional and stricter. `command-registry` joins the ranked spine at rank 1.

No `APPROVED_OVER_CEILING` row: rename detection carries every moved entry's merge-base
baseline, so all twelve fall under the no-growth rule rather than a ceiling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jqfa11D8QsCMuL17SsLvDz
@thymikee
thymikee force-pushed the claude/2336-command-registry-package branch from fc40090 to f887766 Compare September 6, 2026 10:45

thymikee commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

Conflict resolved. Rebased onto main (dcd8b65) — new head f887766, force-pushed. #2357 was rebased onto this new head in the same operation, so the stack is intact; neither branch conflicts with main or with each other.

History is now three commits instead of five, and two commits reviewers saw earlier are deliberately gone:

  • 0aecbcb / 009887e — the workspace-package fingerprint fix. They edited src/daemon/code-signature.ts, which no longer exists: refactor(daemon): move four pure leaves to their kits #2347 moved it to packages/host-kit/ and fixed the same defect there, and that is what main carries. They were unreplayable onto the new base, and their content was already superseded by refactor(daemon): move four pure leaves to their kits #2347's walker in the merge this rebase replaces. Nothing was lost by dropping them.
  • What did survive is now its own commit, 2b2dc90 test(host-kit): pin the command-registry package inside the daemon code graph — the regression test asserting packages/command-registry/{registry,catalog}.ts and its manifest are inside the walk from src/daemon.ts, plus the corrected cache doc comment (it still claimed the old ~800-module graph).

The three conflicts against #2346 were import-line only: package specifiers alongside the split daemon-request.ts / session-state.ts type modules, with the two descriptor tests keeping their src/__tests__/ paths at the corrected relative depth.

I did not hand-wave the equivalence — I produced the correct merge tree first, rebuilt the branch linearly on top of main, and confirmed git diff between the two is empty. The rebased tree is byte-identical to a correct merge of the old head with main.

Also folded in from review: the chore(gates) commit body now records that dropping the two descriptor hubs from HUB_ENTRY_FILES flips denyPlatformImplementations from false to true for them, and that this is intentional and stricter.

CI is re-running on f887766; the authoring session is revalidating pnpm check:affected --run on that exact head and will post the result.


Generated by Claude Code

@thymikee
thymikee merged commit 2ec4e91 into main Sep 6, 2026
21 checks passed
@thymikee
thymikee deleted the claude/2336-command-registry-package branch September 6, 2026 10:55

thymikee commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

Validated on the rebased head f887766 after pnpm install --frozen-lockfile && pnpm build.

pnpm check:affected --run selected 12 checks. Green: format, lint, typecheck, layering, di-seams, fallow, mcp-metadata, build, package, integration-node, macos-coverage. vitest-related: 9,746 passed, 3 failedapp-log-session-resource, durable-capture-resource, and capture-kit adoption, the same three that fail at origin/main in this sandbox because each asserts a chmod 0500 directory is unwritable and uid 0 ignores the mode. Not this branch's.

The rebase preserved the tree exactly: f887766 and the merge I had resolved locally share tree d7322042, so nothing was dropped — including the packages/host-kit/src/code-signature.test.ts regression test and the two corrected doc comments. The three A↔C import conflicts are resolved as specified: package specifiers plus daemon-request.ts / session-state.ts, with both descriptor tests at their src/__tests__/ paths.

CI on this head is green across everything that has reported — Coverage, Integration Tests, Typecheck & Package, Lint & Format, Repo Guards, Compatibility & Provenance, Bundle Size, Ownership and context budgets, command-docs-gate, CodeQL, and two of the four Smoke lanes; the remaining two are still running. The Android emulator lane that went red on an earlier head has since passed on both fc40090 and here.


Generated by Claude Code

thymikee added a commit that referenced this pull request Sep 6, 2026
Main moved the command descriptor registry into @agent-device/command-registry (#2348) and split
src/daemon/types.ts (#2346): the planned-operations module and its test follow the registry into
the package (exported as ./planned-operations), the executionPlan carrier moves to
daemon-request.ts, and the batch step context lands on the new BatchInvoke signature.
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.

refactor(core): move the command descriptor registry into its own workspace package

2 participants