callstack / callstack/agent-device
refactor(cli-schema): orient the schema layer below commands
- Dominant language
- TypeScript
- Stars
- 4.6k
- Forks
- 299
- Avg merge
- 10h 17m
- Merged PRs (30d)
- 515
Description
## Purpose
`src/commands` (140 files, 21k LOC) and `src/cli-schema` (9 files, 2k LOC) point at each other. Forward: `commands → cli-schema` 24 file pairs. Back: `cli-schema → commands` 12 pairs, and the kind split says the direction is nearly settled already:
- **value (5):** `cli-help.ts → commands/cli-command-aliases.ts`, `cli-help.ts → commands/command-text.ts`, `command-overrides.ts → commands/family/registry.ts`, `command-overrides.ts → commands/cli-grammar/flag-groups.ts`, `command-schema.ts → commands/command-metadata.ts`, `command-schema.ts → commands/cli-grammar/flag-registry.ts`, `command-schema.ts → commands/cli-grammar/flag-groups.ts`
- **type-only (7):** `cli-config.ts`, `command-overrides.ts`, `command-schema.ts`, `types.ts` reaching `cli-grammar/flag-types.ts`, `command-metadata.ts`, `command-text.ts`
`command-schema.ts` is also one of the 51 daemon → root pairs, imported by 2 daemon files, and it is the daemon's **second** path into the commands zone: `command-schema.ts → command-metadata.ts → family/registry.ts → every command family`. Measured at `e6f288012b`: cutting only the `src/runtime.ts → commands/index.ts` edge (#2540) leaves 75 `src/commands/**` files in the daemon value closure; also cutting the daemon's two `command-schema.ts` imports takes it to 0. This issue is therefore on the umbrella's critical path, not only a layering nicety.
Nothing can package `src/commands` — the largest packaging decision left in the repo — until the schema layer's position is stated.
Umbrella: #2545.
## What the back edges actually carry
Two kinds, and they go opposite ways:
1. **Vocabulary commands owns but nothing in commands' runtime needs:** `cli-grammar/flag-types.ts`, `flag-definitions-*.ts`, `flag-registry.ts`, `flag-groups.ts`, `command-text.ts`, `cli-command-aliases.ts`. Their closures are package-only (`@agent-device/contracts/command`, `@agent-device/command-registry`). These sit **below** both layers.
2. **Facet-authored schema:** each command file declares its own `cliSchema` and `defineCommandFacet` completes it; `family/registry.ts` (imports all twelve families) aggregates them and `command-overrides.ts` / `command-schema.ts` read the aggregate to resolve a command's schema. That is not vocabulary; it is the sum of what commands authored. `family/registry.ts` cannot move into a package. The aggregation sits **above** commands.
So "one direction" for the whole of `src/cli-schema` is false. The layer splits.
> **Resolution (step 3, #2543):** the back edge is not a defect to invert — it is the presentation
> layer reading what the facets author. The only real inversion was the forward `types.ts` import
> (22 files), which now sits in `@agent-device/command-registry` beside the flag grammar. After the
> move, `commands → cli-schema` is 0 of any kind, and `cli-schema → commands` is the declared,
> R2-guarded direction.
## Required behavior
1. **Move kind-1 vocabulary down** to `packages/command-registry` (the flag grammar: types, definitions, registry, groups; `command-text`; `cli-command-aliases`). #2421 already made a command option declared once in the descriptor; this continues that boundary instead of adding another table. No compatibility re-export at the old path.
2. **Orient the schema layer above commands, not below.** The original body assumed the aggregation had to move *up and out* of `src/cli-schema`. That premise is wrong: both zones are rank 3, and R2 does not govern the pair, so the direction was never settled by the gate — only by what gets packaged. The measured data says the honest direction is `commands` below `cli-schema`: the 24 `commands → cli-schema` edges are 22 type-only imports of one file (`types.ts`) plus one value importer (`command-explain.ts`); the `cli-schema → commands` pair is 2 value edges reading the facet-authored schema aggregate. So do not evacuate `cli-schema` — that just relocates the same edges. Instead: move `src/cli-schema/types.ts` (`CommandSchema`, `CommandSchemaOverride`) down to `packages/command-registry` beside the flag types it already imports, retire the 22 forward type edges, and keep `command-schema.ts` / `command-overrides.ts` / `cli-help.ts` / `option-schema.ts` / `usage.ts` / `cli-config.ts` in place — their read of `family/registry.ts` and `command-metadata.ts` becomes the declared direction, not a violation. `cli-config.ts` takes `projectConfigFlagKeys` straight from the package instead of through `command-schema.ts`.
3. **The daemon stops importing `src/cli-schema/command-schema.ts`.** (Landed in #2562.) `session-action-recorder.ts` takes `recordedFlagKeys` from the moved flag registry; `request-execution-scope.ts` takes `applyCommandDefaults` from `@agent-device/command-registry/registry`, the one module already in both callers' eager closure. R10 now rejects any daemon import of `src/cli-schema/`.
4. `command-explain.ts` is the only `commands` file with value edges upward into `usage.ts` and `command-schema.ts`, and it already types against `daemon-command-registry.ts` — it is a cross-zone explainer, not a facet. Move it up out of `commands` (to `cli/`, above `daemon-server`) so `commands → cli-schema` reaches 0 without moving `cli-help.ts`. This also retires the last `commands → daemon-server` type inversion.
5. Preserve the `--json`/help/MCP conformance surfaces; the help-conformance scripts are the existing check.
## Completion conditions
- `src/commands → src/cli-schema` holds **0 edges of any kind** (value, type-only, or dynamic). `src/cli-schema → src/commands` — reading the facet aggregate — is the declared direction, guarded by `R2 commands-floor` in `scripts/layering/zone-policy.ts`, whose hint states the orientation.
- `src/daemon` imports nothing under `src/cli-schema/` (R10), and the daemon value closure contains 0 `src/commands/**` files once #2540 has also landed (probe as described in #2545, reported in the PR).
- No flag is declared in two places; no help-conformance or MCP-metadata check regresses.
- R4 stays 0, R5 stays 0, R6 unchanged or lower; eager-closure budgets for the daemon entry do not rise.
## Out of scope
- Packaging `src/commands`. This issue settles the direction; the packaging decision follows with its own numbers.
- Flag semantics changes. Versioned CLI help stays the source of truth; see `docs/agents/cli-flags.md`.
## Dependencies
- Shares `src/commands/index.ts` with #2540 (Wave A). Land after #2540 merges, or agree with that branch on who owns the two daemon → `command-schema.ts` edges. Wave B.
Contributor guide
Assessment
This issue has not been assessed yet.