perf(cli): route command aliases through the help fast path - #1641
Conversation
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
Reviewed exact head |
9418f11 to
842469b
Compare
|
842469b to
7dc6f0f
Compare
|
Re-reviewed exact head
The current PR body is also stale relative to the six-file structural-guard diff. Current head is mergeable and completed checks are green, with required lanes still pending; no |
bin.ts's `--help` fast path resolved aliases through a hand-written two-entry table that had drifted out of sync with the real CLI_COMMAND_ALIASES registry (five entries). `tap`, `launch`, and `relaunch` missed the table and silently fell through to a full runCli() bootstrap just to print static help text (~150-165ms vs ~45-50ms for aliases already in the table). Delegate to the shared normalizeCliCommandAlias registry instead of the stale local table, so every alias the registry knows about gets the fast path automatically.
The unit test added for the alias fast-path fix (cli-help-alias-fast-path.test.ts) calls normalizeCliCommandAlias directly, so it stays green even if bin.ts itself reverts to a hand-rolled table — it pins the registry composition, not bin.ts's own wiring, and bin.ts cannot be safely unit-imported (it runs unguarded top-level dispatch on import and is deliberately excluded from coverage). Add an AST-based structural guard instead, in the style already established by scripts/layering/session-state.ts, facade-exports.ts, and zero-dep-jobs.ts (oxc-parser's module/program records, not a line scan, so a fixture's string literal can't produce a false hit). R12 asserts two facts about src/bin.ts: it holds a value import of normalizeCliCommandAlias from commands/cli-command-aliases.ts, and it contains none of the registry's own alias tokens as string literals. The token list is read out of the registry's own source (CLI_COMMAND_ALIASES's `alias:` property values), not hard-coded, so a future sixth alias is covered automatically. Both facts were false on the pre-fix bin.ts, verified by reverting locally and capturing the failure before restoring the fix. Wired into the existing check:layering chain (already part of check:tooling), next to R7's session-state ownership rule, which pins the same "delegate to your single owner" shape.
…2 P2) Maintainer review of R12 (PR #1641): import-presence and literal-absence alone let bin.ts regress to buildCommandUsageText(helpTarget) while the normalizeCliCommandAlias import stays in place, used harmlessly elsewhere (or not at all) — the real-tree gate stayed green through that exact regression. Add a third fact: bin.ts's call to buildCommandUsageText must receive, as its argument, a call to the LOCAL binding the resolver was imported as (aliasResolverLocalName + usageTextCallsResolver, both AST-based). Binding by local name rather than the literal export name means a renamed import (`as resolveAlias`) still verifies, and an unrelated same-named local cannot be mistaken for it. Verified by reverting locally to exactly the missed regression — import left in place, call reverted to buildCommandUsageText(helpTarget) — and confirming R12 now fails where the two-fact version passed; restored after. Two negative fixtures pin the scenario going forward: import present but unused, and import present but used only unrelated to the call.
7dc6f0f to
d2c3b1b
Compare
|
Fixed at R12 now asserts a third fact via AST: Red run for exactly the regression you named — import left in place, call site reverted to raw: Both negative fixtures you asked for are committed: present-but-unused import, and an import used only unrelatedly ( One honest caveat, since this PR is partly about not overclaiming test strength: the run above proves the three-fact guard fails on that fixture. That the two-fact version would have stayed silent on it was established by reading both predicates against the fixture, not by re-running the older guard — an argument, not a measurement. Noted in the body too. Also worth recording: the first push attempt was blocked by its own gate on
|
|
Re-reviewed exact head P2 — the AST guard existentially accepts any unrelated wrapped usage. import { normalizeCliCommandAlias } from "./commands/cli-command-aliases.ts";
void buildCommandUsageText(normalizeCliCommandAlias("press"));
const commandHelp = buildCommandUsageText(helpTarget);Add this negative fixture, then require the relevant/all usage-text call(s) to receive the imported local binding invoked specifically with |
The previous fact 3 asked whether *any* `buildCommandUsageText(resolver(...))`
existed in bin.ts. That quantifier is satisfied by a decoy call while the line
that actually ships resolves nothing:
void buildCommandUsageText(normalizeCliCommandAlias('open'));
const commandHelp = buildCommandUsageText(helpTarget);
Fact 3 now requires EVERY `buildCommandUsageText` call to receive the imported
resolver applied to the fast path's own help-target binding, which rejects both
lines above independently. The help-target name is read from bin.ts (the
variable initialized by `resolveSimpleHelpTarget`), so renaming it re-points
the guard instead of disarming it.
Because fact 3 claims binding identity by name, it also now rejects a local
shadow of the resolver and an ambiguous second help-target declaration — a
same-named local would otherwise let the composition read as delegation while
calling something that resolves nothing.
The predicate returns the reason rather than a boolean, so the gate names which
of the several distinct failures happened.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Rva4YGtSCAKJqH5PbpcCU
|
Re-reviewed exact head f469d6c. The production alias fast path is correct, and the latest R12 guard closes the previous existential and decoy bypass: every buildCommandUsageText call must receive the imported resolver applied to the binding produced by resolveSimpleHelpTarget, with negative fixtures for raw, decoy, wrong-argument, and shadow cases. No actionable code findings. This is host-side static help routing, so device evidence does not apply. Code review is clean; remaining CI is still in progress. |
|
Fixed at Fact 3 is now universal and value-bound. Every Red run on your exact fixture, planted in the real tree: I used And the old predicate, measured against the same planted tree (checked out from So the gap was real and is now closed, and that sentence is a run rather than a reading. Same-named shadow covered too, as you asked. Since fact 3 claims binding identity by name, it now also rejects a local declaration of the resolver's name ( The predicate returns the reason instead of a boolean, so the gate names which of the several distinct failures happened rather than sending you back to re-derive it. Guard tests 23, all green; Generated by Claude Code |
Summary
agent-device <command> --helphas a fast path insrc/bin.tsthat prints static help without booting the full CLI. It resolved aliases through a hand-written two-entry table while the real registry (src/commands/cli-command-aliases.ts) has five, sotap,launch, andrelaunchsilently missed it and fell through to a fullrunCli()bootstrap just to print static text.It now delegates to
normalizeCliCommandAlias, so every alias the registry knows about gets the fast path automatically.Measured on the built CLI, 3 runs each, warmed:
--helppress(control)long-press(control, was in the old table)taplaunchrelaunchlong-pressvstapis the controlled comparison: both are aliases printing identical help, differing only by whether the stale table knew about them. Output is byte-identical to each alias's canonical command (diffclean on all four pairs).rotatedeliberately still misses the fast path, so its rename migration error keeps rendering through the slow path. No carve-out was added tobin.tsfor it — it simply isn't in the alias registry.Regression coverage: a structural guard, not just a unit test
bin.tsruns unguarded top-level dispatch on import and is excluded from coverage by design, so it cannot be imported in a unit test — the committedcli-help-alias-fast-path.test.tspins the registry compositionbin.tscalls (a durable guard against a future sixth alias lacking help text), but revertingbin.tsalone does not fail it. That gap was closed with a dedicated structural gate instead of stretching the unit test past what it can honestly prove.R12 (
scripts/layering/bin-alias-fast-path.ts+.test.ts, wired intoscripts/layering/check.tsandcheck:layering) readssrc/bin.ts's source withoxc-parser— the same AST-based approach assession-state.ts,facade-exports.ts, andzero-dep-jobs.tsin the same directory, not a line scan (a line scan would mistake a fixture's string literal or a comment for the real thing). It asserts three facts:bin.tsholds a value import ofnormalizeCliCommandAliasfrom the registry (not type-only, which would be erased at compile time).bin.ts's call tobuildCommandUsageTextreceives, as its argument, an actual call to the local binding that import resolved to — not merely both names appearing somewhere in the file. Binds by local name, so a renamed import (as resolveAlias) still verifies and an unrelated same-named local does not.bin.tscontains none of the registry's own alias tokens as string literals (no local hand-rolled table sitting beside the delegation).Fact 2 was added after a maintainer review of the first pass: import-presence and literal-absence alone still pass a
bin.tsthat imports the resolver and never calls it (or calls it on something unrelated) whilebuildCommandUsageText(helpTarget)runs raw — exactly the regression the fast path actually had. Verified by reverting locally to precisely that shape (import left in place, call reverted to the raw form) and confirming R12 now reports a violation; restored after.Precision about that proof, since this PR is partly about not overclaiming test strength: the run demonstrated the three-fact guard failing on that fixture. That the two-fact version would have stayed silent on it was established by reading both predicates against the fixture (import present satisfies fact 1; no alias literals satisfies fact 3), not by re-running the older guard. The reasoning is checkable from the predicates themselves, but it is an argument rather than a measurement.
The alias-token list (fact 3) is read out of the registry's own
CLI_COMMAND_ALIASESarray-literal declaration rather than hard-coded, so a future sixth alias is covered automatically without touching this file.Validation
pnpm check:tooling— green (format, lint, typecheck,check:layeringwith R12, depgraph, production-exports, tmpdir-leaks, mcp-metadata, build, bundle-owner-files, check:package).pnpm check:affected --run— green.check:layering: 76 tests pass (15 in the newbin-alias-fast-path.test.ts), guard reports OK including R12.bin.ts(stale table, no import) — 2 violations; (2) the composition fact against abin.tswith the import left in place but the call reverted to rawbuildCommandUsageText(helpTarget)— 1 violation, exactly the case the two-fact version missed.CLI --version29.4 → 28.4 ms, confirming the one static import added tobin.tscosts nothing measurable — tsdown inlines the alias table intobin.jsrather than emitting a chunk.nodesubprocesses inunit-corewould violate the suite's "unit tests must not wait real time" budget and has no precedent in the repo):Scope
6 files, +523/−9:
src/bin.tsand its unit test (the fix), plusscripts/layering/bin-alias-fast-path.ts, its test,scripts/layering/check.ts, andpackage.json(the R12 structural guard, added across two review rounds). Not device-facing, so no simulator/emulator evidence applies.Split out of #1639 per review. Found by a read-only codebase audit.