Skip to content

feat: aidlc single-command CLI - dark-launch dispatcher, grammar, and build (2.3.8-2.3.9) - #560

Merged
apackeer merged 15 commits into
v2from
feature/single-cli-dispatcher
Jul 17, 2026
Merged

feat: aidlc single-command CLI - dark-launch dispatcher, grammar, and build (2.3.8-2.3.9)#560
apackeer merged 15 commits into
v2from
feature/single-cli-dispatcher

Conversation

@apackeer

@apackeer apackeer commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What this is

The aidlc single-command CLI, shipped dark: a noun-verb dispatcher (aidlc <noun> <verb>) that replaces path-typed invocations (bun .claude/tools/aidlc-X.ts ...), built as the user-facing half of the binary distribution plan. The grammar exists at core/tools/aidlc.ts, fully tested under bun and compiled/smoke-gated as a native binary - but no shipped string references it yet. The cutover (WP7) waits for a signed binary on PATH.

Rebased 2026-07-14 onto v2 after the four-PR merge train (#550 2.3.5, #565, #562 2.3.6, #563 2.3.7): the base auto-retargeted to v2, this branch's versions shifted to 2.3.8/2.3.9, and two test slots moved (t230-handler-additions -> t231, t231-build-binaries -> t238; t232-t237 are claimed by merged and in-flight work). Previously rebased 2026-07-12 onto #550's review-fix head.

The commits (landing order = the dark-launch safety order)

Commit What
003ad5f Detectors first: the stop hook and runtime-compile hook recognize BOTH command grammars (legacy regexes kept verbatim; new-shape branches inert until the grammar exists). 77-case pinned corpus (t226).
6060be3 Entrypoint exports: 19 tools export main(argv), 11 hooks + 3 adapters export run(input), zero side effects on import; spawned behavior byte-identical (t227/t228).
d0c6a49 Workspace verbs (2.3.8, user-visible): one shared two-token parser at both call sites (space create teamB now creates instead of switching to a space named "create"); verb names reserved at creation; doctor advisory; Kiro quoted-name fix (t229).
056477c The dispatcher: 29-route translating router, dev-spawn + compiled in-process modes, help / help --all with plumbing banner + alias table; 29 byte-parity cases old-vs-new shape (t230).
eb63cf9 Handlers (2.3.9, user-visible): config get/list, plugin list/sync, the init transition error (loud, one release, replaces the silent intent-birth alias), upgrade stub; emitted plugin hook probes aidlc-then-bun (t231).
2e00daa Build script: scripts/build-binaries.ts with mandatory smoke gates (run version+help from neutral cwd, empty-PATH version, bundle grep-gate); bytecode permanently banned with the reproduced failure documented (t238).
7a59be1 Re-slot after the 2026-07-14 merge train: versions 2.3.6/2.3.7 -> 2.3.8/2.3.9, handler test -> t231, build-binaries test -> t238, coverage registry regenerated.

User-visible changes (the complete list)

Everything else is byte-identical - the parity suite pins it. Two CHANGELOG'd releases ride along:

  • 2.3.8: workspace nouns gain list/switch/create/birth verbs (bare-name sugar preserved, legacy space-create accepted); archive/rename/show reserved; verb-shaped names refused at record creation; doctor flags pre-existing collisions; Kiro preserves quoted multi-word names.
  • 2.3.9: config get/list, plugin list/sync; init errors loudly for one transition release instead of silently creating an intent (its future meaning is data-tree scaffolding per the install-model decision); upgrade reserves its name.

Evidence

  • Post-rebase deterministic tiers at tip 7a59be1: smoke+unit 165 files / 0 failures (stamp 2026-07-14T00-27-12Z); integration 102 files with 2 reds, both accounted for: t72 (green-alone rerun, latency flake) and t92 (pre-existing v2 baseline eslint/tsc environment issue, fails identically on untouched v2).
  • 7 test files: t226-t231 + t238; package.ts --check clean; typecheck clean; coverage registry fresh.
  • Old command shape stays a valid input forever (dev mode + third-party plugin manifests); the router translates new to old, so existing callers never break.
  • The init meaning change was validated by its own blast radius: ~25 test files used the deprecated alias as their fixture driver and broke exactly as a stale caller would - all repointed to intent-birth in the same commit.

Out of scope (later, per the plan)

WP7 cutover (rewrites the 67 authored + ~22-26 generated invocation strings, hook wiring, allowlists - only after a signed binary is on PATH) and WP8 docs. The sliced live pre-merge gate runs against the cutover PR; this PR ran the default deterministic tier per WP plus at tip.

@apackeer
apackeer force-pushed the feature/plugin-selection branch from 6c3fd8a to 91a7108 Compare July 12, 2026 20:06
@apackeer
apackeer force-pushed the feature/single-cli-dispatcher branch from 047d8f8 to 61bd7ef Compare July 12, 2026 20:59
@leandrodamascena
leandrodamascena marked this pull request as ready for review July 13, 2026 06:53
Base automatically changed from feature/plugin-selection to v2 July 13, 2026 23:16
@apackeer
apackeer force-pushed the feature/single-cli-dispatcher branch from 61bd7ef to 7a59be1 Compare July 14, 2026 01:05
@apackeer apackeer changed the title feat: aidlc single-command CLI - dark-launch dispatcher, grammar, and build (2.3.5-2.3.6) feat: aidlc single-command CLI - dark-launch dispatcher, grammar, and build (2.3.8-2.3.9) Jul 14, 2026

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

CHANGES REQUESTED

I built the binary, ran the live engine, diffed the classifiers against base, and set up real workspaces. The dispatcher and new nouns are genuinely dark — nothing shipped invokes them, dev-spawn is byte-for-byte the legacy tool. But two things change live behavior, and one breaks a remedy this PR advertises.

One framing note: "no current behavior changes" isn't literally true. 2.3.8 is a deliberate, disclosed live workspace-grammar change (bare intent list now runs LIST instead of switching to a record named "list"). That's fine — the problem is the escape hatch it points to doesn't work.

Required fixes (confirmed, live impact):

1. The switch <name> escape hatch is broken for verb-named records — aidlc-lib.ts:571. workspaceCommandUtilityArgv returns [noun, name] for a switch, dropping the switch token, so the utility re-reads the name as a verb. This runs on the live orchestrate path and the Kiro adapter, not just the dark dispatcher. I reproduced it: orchestrate next intent switch list emits intent list (record unreachable), and intent switch birth emits intent birth, which births a new intent — a switch mutating state. On v2 you could create records named list/birth/create; after upgrade the advertised remedy fails for exactly those, while the doctor advisory and CHANGELOG promise it works. archive/rename/show survive (not utility verbs). t229 hides it — the "coverage" test calls the 3-token argv directly, which the engine never emits. Fix: emit [noun, "switch", name] (handlers already accept it) and test the engine path.

2. The runtime-compile reject leg isn't inert — aidlc-lib.ts:832. The PR adds /\baidlc\s+runtime\b/ as a reject disjunct feeding the live PostToolUse hook. A legacy transition whose --user-input contains the words "aidlc runtime" flips base "fire" → PR "reject", suppressing the runtime-graph recompile — reachable today, no cutover. It self-heals (next clean transition catches up), so it's below #1, but it's a live matcher change contradicting "inert until cutover". Fix: keep the reject leg path-anchored, not a bare two-word substring.

Majors (no current impact, but block the future binary cutover):

3. The compiled binary is broken for every delegate — aidlc.ts:712. I ran bun build --compile on the shipped dispatcher: version/help work, but doctor/state get/plugin sync all throw Cannot find module '/$bunfs/root/aidlc-utility.ts'. The 15 tools are dynamically imported via a computed path --compile can't discover, so they never embed. No binary ships today — but this is the artifact WP7 would ship.

4. The build gate is blind to #3build-binaries.ts:383. build-binaries.ts --target native printed native ok, exit 0, while the artifact fails every delegate — the gates (version/help/grep/pathless) never delegate. Exactly the BYTECODE-1 "compiles but doesn't run" case it claims to guard. Add a gate that runs one real delegate against the compiled artifact.

5. Plugin-compose hook couples to an aidlc on PATH — package.ts:914. The emitted hook probes command -v aidlc and runs aidlc plugin sync; exit $? if found. Today it falls through to bun compose.ts (no-op, confirmed). But once a binary named aidlc is on PATH it runs plugin sync (exits 1 per #3) and the fallback is unreachable — plugin compose silently stops. Dormant.

Non-blocking: router drops --project-dir on workspace routes (post-cutover only; live channel is the env var). Stale init emitter left in audit-format.md (the PR fixed the parallel 12-state-machine.md). Version 2.3.9 is consistent and correct, but open #572/#573 bump from a 2.3.7 base — coordinate merge order.

Didn't fully verify: whether the release names the binary aidlc on PATH (no .github/workflows in the repo) — that decides if #5 is dormant or live. Didn't run the Kiro/Codex adapters end-to-end (diffed the bodies; mechanical wrapper refactor).

The dark-launch discipline is careful and most holds. But #1 breaks an advertised remedy on the live path and #2 changes a live matcher, so land those first (plus the binary/gate fixes before any cutover).

apackeer added a commit that referenced this pull request Jul 14, 2026
…that runs its delegates

Fixes leandrodamascena's PR #560 review findings:

- workspaceCommandUtilityArgv now emits [noun, "switch", name] for an
  explicit switch instead of dropping the switch token, so a pre-existing
  intent or space whose slug shadows a verb (list, birth, create) stays
  reachable through the engine and dispatcher instead of being misread as
  that verb.
- classifyRuntimeCompileCommand anchors its new aidlc-runtime reject leg to
  the start of a shell command segment instead of matching the bare
  two-word substring anywhere in the command string, so a legacy transition
  whose --user-input prose happens to contain "aidlc runtime" no longer
  suppresses the post-transition runtime-graph recompile.
- The compiled native binary now statically imports each of the 15
  dispatcher delegate modules through a literal switch instead of a
  runtime-computed import path, so Bun's --compile bundler can discover and
  embed them; previously every delegated subcommand failed with a missing
  module error.
- build-binaries.ts gates the native artifact with a real plugin sync
  delegate call in addition to the existing version/help/grep checks, so a
  future regression that breaks delegate embedding fails the build instead
  of shipping a broken binary.
- The plugin-compose hook's PATH-preferred aidlc invocation now only exits
  early on success; a nonzero plugin sync falls through to the bun compose
  path instead of leaving compose unreachable.
- Mirrors the intent-birth-for-init emitter correction in
  core/knowledge/aidlc-shared/audit-format.md that the parallel state
  machine doc already had.

Extends t226 (path-anchored reject leg, prose-in-flag-value negative case),
t229 (engine and dispatcher switch to a verb-named record without
birthing), t230 (dispatcher parity for the corrected switch argv), t231
(the exit-vs-fallthrough plugin hook shape), and t238 (a real plugin sync
delegate call against the rebuilt native artifact, plus a fake-entry
negative case for the new gate).
@apackeer
apackeer force-pushed the feature/single-cli-dispatcher branch from 7a59be1 to f369c76 Compare July 14, 2026 23:58
@apackeer

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review. The requested fixes landed in f1f0b5f and were followed by runtime-data and dispatch hardening through the current head:

  • Explicit switch <name> now preserves the switch token, while bare-name switch sugar retains its original two-token shape.
  • The runtime-compile reject leg is anchored to a shell-command segment and no longer matches prose inside flag values.
  • Compiled delegates use literal static imports so Bun embeds them.
  • The binary build gate executes a real plugin sync delegate.
  • Plugin compose falls back to Bun when the PATH binary returns nonzero.
  • The stale audit-format init emitter was corrected.
  • Compiled commands now resolve staged runtime data beside the executable, with doctor/runtime-data gates.

Regression coverage was extended across the detector, dispatcher, workspace, plugin-hook, binary-build, and doctor tests. Re-requesting review.

@leandrodamascena

Copy link
Copy Markdown
Contributor

Follow-up on my earlier CHANGES REQUESTED. I re-reviewed the reworked branch and my three original findings (workspace switch token, runtime-compile reject disjunct, dynamic-import bundling) are resolved. But I compiled the binary at a6d8c3bc and ran the delegated paths myself, and five independent regressions reproduce. Keeping the change-request open for these.

P1 — the compiled binary is non-functional beyond version/help/doctor

  1. Sensors are invisible in the binary. aidlc sensor list is empty and sensor describe linter reports "Known ids: (none)", while the same commands under bun list all four sensors. Root cause: sensorsDir() at core/tools/aidlc-graph.ts:337 resolves join(__FILE_DIR, "..", "sensors") — module-relative with no executable fallback, unlike its sibling scopesDir() (aidlc-lib.ts), which was given resolveModuleOrExecutablePath(...). In a compiled binary __FILE_DIR is a $bunfs path and the sensor manifests aren't embedded (only .ts modules are), so loadSensors() returns an empty map. The exe-relative fallback that fixed scopes needs to reach sensorsDir too.

  2. Graph/asset resolution still falls to cwd-relative. aidlc graph compile fails on a missing data/asset file. Even with the data dir co-located next to the binary, agent/scope resolution then fails on a cwd-relative path (/private/tmp/agents). resolveDataDir has the exe fallback, but not every asset resolver shares it, so the binary can't compile a graph outside a dir that happens to mirror the layout.

  3. Bolt/Swarm delegation re-invokes $bunfs paths. spawnSibling (core/tools/aidlc-bolt.ts:117) spawns bun run <fileURLToPath(new URL("./aidlc-state.ts", import.meta.url))>. In the binary import.meta.url is $bunfs/..., so the worktree fork/merge steps shell out to bun run /$bunfs/root/aidlc-state.ts — which needs bun on PATH and points inside the binary's own FS. Compiled bolt/swarm can't delegate.

P2 — grammar/parser gaps

  1. Interleaved workspace flags misroute (live, not binary-specific). space --project-dir /tmp create teamB lists spaces instead of creating teamB — the two-token workspace parser doesn't skip --flag value pairs before reading the verb. This one will bite at WP7 when the grammar goes live.

  2. space-create absent from the dispatcher. aidlc space-create teamB → "unknown command or noun 'space-create'". The PR body says "legacy space-create accepted" — that's true on the old bun .../aidlc-utility.ts space-create path, but the new dispatcher grammar rejects it, so the claim is overstated for the CLI surface.

Why t238 doesn't catch these: the build gate's realPluginSyncGate copies a full .claude runtime tree into the project dir and sets CLAUDE_PROJECT_DIR, so the data files exist on disk right where a project-relative lookup finds them — which masks the exe-relative resolution bug. And the gate never runs sensor list, a graph compile outside that seeded layout, or a bolt/swarm delegation in the compiled artifact. So a binary that's broken for sensors/graph/bolt/swarm passes the gate green.

None of this is a production regression today — the binary is dark (not on PATH, WP7 out of scope). But shipping the build script + gate now, with the gate certifying a binary that's non-functional for its real delegated work, is the concern: the gate should exercise sensor list, an unseeded graph compile, and a bolt delegation in the compiled artifact before it can vouch for the binary. Happy to treat P1 as "accept dark, fix before WP7 cutover" if you'd rather scope it that way — but then the gate claims and the CHANGELOG's "routed commands reach their handlers" line should say so, and P2c (the interleaved-flag misroute) is a real parser bug worth fixing regardless.

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My previous findings are resolved. I rebuilt and tested the current binary, but I found two remaining blockers:

  1. Mutable commands can write into the packaged runtime. Running plugin select or graph compile against a project without an installed harness modifies files beside the executable, such as harness.json and stage-graph.json. Project commands should never modify the shared packaged runtime.

  2. --project-dir is dropped for hook, adapter, and statusline routes. I confirmed that a hook from the current working directory runs even when --project-dir points to another project.

I also found some non-blocking issues:

  • Relative --project-dir values break Bolt/Swarm re-entry.
  • The runtime matcher still rejects quoted user input containing a separator followed by aidlc runtime.
  • __sensor-script-file can execute any matching TypeScript file. This matters if the aidlc binary will be allowlisted as a trusted command.

The targeted tests, native binary tests, and package drift check pass. These issues are dormant while the binary remains dark, but the first two conflict with the claim that the packaged binary is safe and self-contained. I recommend fixing them before merge, or clearly scoping the binary as experimental until WP7.

apackeer added a commit that referenced this pull request Jul 17, 2026
…that runs its delegates

Fixes leandrodamascena's PR #560 review findings:

- workspaceCommandUtilityArgv now emits [noun, "switch", name] for an
  explicit switch instead of dropping the switch token, so a pre-existing
  intent or space whose slug shadows a verb (list, birth, create) stays
  reachable through the engine and dispatcher instead of being misread as
  that verb.
- classifyRuntimeCompileCommand anchors its new aidlc-runtime reject leg to
  the start of a shell command segment instead of matching the bare
  two-word substring anywhere in the command string, so a legacy transition
  whose --user-input prose happens to contain "aidlc runtime" no longer
  suppresses the post-transition runtime-graph recompile.
- The compiled native binary now statically imports each of the 15
  dispatcher delegate modules through a literal switch instead of a
  runtime-computed import path, so Bun's --compile bundler can discover and
  embed them; previously every delegated subcommand failed with a missing
  module error.
- build-binaries.ts gates the native artifact with a real plugin sync
  delegate call in addition to the existing version/help/grep checks, so a
  future regression that breaks delegate embedding fails the build instead
  of shipping a broken binary.
- The plugin-compose hook's PATH-preferred aidlc invocation now only exits
  early on success; a nonzero plugin sync falls through to the bun compose
  path instead of leaving compose unreachable.
- Mirrors the intent-birth-for-init emitter correction in
  core/knowledge/aidlc-shared/audit-format.md that the parallel state
  machine doc already had.

Extends t226 (path-anchored reject leg, prose-in-flag-value negative case),
t229 (engine and dispatcher switch to a verb-named record without
birthing), t230 (dispatcher parity for the corrected switch argv), t231
(the exit-vs-fallthrough plugin hook shape), and t238 (a real plugin sync
delegate call against the rebuilt native artifact, plus a fake-entry
negative case for the new gate).
@apackeer
apackeer force-pushed the feature/single-cli-dispatcher branch from 97c9971 to 4531b71 Compare July 17, 2026 08:32
@apackeer

Copy link
Copy Markdown
Contributor Author

@leandrodamascena I addressed the latest review findings and rebased the branch onto the current v2 head in 4531b71:

  • Packaged runtimes are read-only fallbacks; mutable plugin/graph commands require an installed project harness.
  • Hook, statusline, and adapter routes honor global --project-dir.
  • Relative project paths survive Bolt/Swarm self-reentry.
  • Runtime command matching is quote-aware.
  • Sensor workers resolve registered IDs instead of caller-supplied TypeScript paths.
  • Native binary gates cover these paths and packaged-runtime immutability.

The branch is now versioned 2.4.4. Package drift, TypeScript, lint, focused dispatcher/plugin tests, and the native binary gate pass. Could you please re-review PR #560?

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved. All five previous blockers have been addressed, and the implementation checks passed. The remaining t144 failure is a non-blocking test portability issue caused by /tmp resolving to /private/tmp on macOS.

apackeer added 4 commits July 17, 2026 22:22
…ooks

Extract the stop hook's engine-engagement classifier and the runtime-compile
hook's command filter into shared pure functions in aidlc-lib.ts, and extend
both to recognize the upcoming aidlc <noun> <verb> grammar beside the legacy
bun <path>/aidlc-X.ts shape. The legacy regexes are retained verbatim (plugin
manifests and dev mode keep that shape as a permanent input); the new-shape
branches are inert until the new grammar ships anywhere, so this lands dark -
detectors first, so the grammar never runs unguarded.

Deliberate delta: new-shape 'aidlc park' counts as engine engagement (legacy
orchestrate park did not - the old segment logic recognized only next/report).

Internal change only: no version bump per the changelog policy (no user-visible
behavior change; both hooks classify every existing command exactly as before,
pinned by the corpus).

Tests: t225-detector-corpus pins the 77-case both-shape corpus (labels, negatives,
documented false-positive classes, four named regression guards); t131 gains
new-shape twins for the runtime-compile seam (state approve, report-after-gate,
runtime recursion guard). Coverage registry regenerated for the new file.
Every argv-dispatching tool (19) now exports main(argv) and keeps the guarded
dev invocation; the five tools that ran main() unconditionally on import
(sensor, swarm, validate, sensor-required-sections, sensor-upstream-coverage)
gain the import.meta.main guard. All 11 hooks and the 3 harness adapters move
every module-scope statement into an exported run(input) (adapters:
run(target, input)) with process.exit sites converted to return codes; the
spawned process contract (stdout/stderr bytes, exit codes, files written) is
byte-identical, pinned by the existing hook suites. mint-presence no longer
audit-appends at import time.

This lets a future single dispatcher statically import every module without
side effects; until that dispatcher exists nothing user-visible changes, so
no version bump (internal refactor per the changelog policy).

utility.ts die() now takes its error-context argv from main's parameter
instead of re-reading process.argv (same output, dispatcher-safe).

Tests: t226 pins main(argv) exports + no-side-effect-on-import for all 19
tools plus dev-path envelopes; t227 pins run() exports + quiet import for all
11 hooks and 3 adapters plus spawned-contract smokes; the coverage-registry
none-to-cli pin gains both files.
…visory (2.3.8)

The workspace nouns gain real verbs through one shared parser in aidlc-lib
(parseWorkspaceCommand), driving both existing call sites: the terminal
classifier (classifyTerminalCommand) and the engine's next parser/handler.
space list/switch/create and intent list/switch/birth now mean what they say;
bare-name switch sugar and the legacy space-create spelling keep working;
archive/rename/show error as reserved future verbs. Missing names are usage
errors instead of switches to a verb-named record.

Precedence unified deliberately: a leading workspace noun owns the whole
command, so a later --status is that command's token, not a mode switch
(previously the two sites disagreed; the classifier's early return wins).

Reservation: RESERVED_RECORD_NAMES grows from {help} to the verb union at
both creation chokepoints (intent birth slugify, space create). No
grandfather machinery - explicit switch <name> reaches pre-existing
verb-named records by construction, and doctor gains an advisory row naming
any such records.

Kiro CLI adapter: the off-band tokenizer now preserves double-quoted names
(splitDoubleQuotedArgs) and forwards the full semantic argv tail, so
space create "My Space" survives the seam.

Version 2.3.5 + CHANGELOG (user-visible migration delta listed there);
docs updated to the new spellings with legacy notes.
core/tools/aidlc.ts is the single-command CLI entry the binary distribution
plan compiles: a routes table (29 entries: 14 passthrough, 10 translation,
stubs for future handlers, routing-only hook/statusline/adapter nodes) with
per-row argv synthesis, two help renderers off the same table (short human
help; help --all with the plumbing banner and the slash-flag alias table),
and a one-line unknown-command error naming the nearest help node.

Two delegation modes, discriminated by the compiled-binary marker in
import.meta.url: dev spawns bun on the sibling tool file (byte-identical
stdio passthrough); compiled calls the tool's exported main(argv)
in-process. hook <name>, statusline, and adapter <target> dispatch through
the exported run() entries. version prints the static AIDLC_VERSION without
touching any data (binary plan requires a data-free version path).

Workspace nouns route through the shared parseWorkspaceCommand, so the
dispatcher and the engine can never disagree on workspace semantics.

DARK: no shipped string references the dispatcher; nothing user-visible
changes, so no version bump (internal per the changelog policy).

Tests: t229 pins the parity suite (29 translated/passthrough cases
byte-identical old-vs-new shape), dev-vs-in-process mode parity,
routes-table completeness (every tool target on disk, every exported tool
reachable, help --all one-entry-per-verb), help shapes and error UX, and
hook/statusline routing through run().
apackeer and others added 11 commits July 17, 2026 22:23
New deterministic handlers per the Utility Handler Checklist:
- config get <key> / config list [--json] read the active workflow's Depth
  and Test Strategy beside the existing write path; the dispatcher's
  config set translates to config-change, which stays accepted.
- plugin list [--json] renders known plugins with enabled state from the
  harness.json selection (absent selection = all enabled).
- plugin sync fronts the plugin fold: runs each installed plugin root's
  hooks/compose.ts with the emitted hook's env contract; idempotent, exits
  0 with a note when no plugin trees are installed.

init changes meaning for one transition release: instead of silently
routing to intent-birth (the deprecated alias), it errors loudly telling
the user to describe what to build - the future init lays down the project
data tree from the installed harness dist. upgrade reserves its name with
a clear not-available error until the packaged distribution lands.

The emitted plugin SessionStart hook now probes for an installed aidlc
binary FIRST (running aidlc plugin sync) and falls back to the bun
compose.ts path, still skipping cleanly when neither exists.

Test-suite migration for the init meaning change: ~25 files used the
deprecated init alias as their fixture driver; all repointed to
intent-birth (same handler, same flags) - the exact stale-caller class
the transition error exists to surface.

Version 2.3.6 + CHANGELOG; docs updated (cli-commands table, plugin
authoring guide, stale init references swept).
scripts/build-binaries.ts compiles the generated Claude dispatcher
(dist/claude/.claude/tools/aidlc.ts) per target with bun build --compile,
SEPARATE from package.ts (which stays the deterministic drift-guarded
projection; this script refuses to build on a drifted dist). Default =
native only; --target / --all-targets cover the release matrix.

Gates, because the build exit code is a liar (BYTECODE-1: a bytecode build
exits 0 while emitting an artifact that crashes at launch - bytecode is
never enabled here and a comment says why):
- native: run version (must equal the stamped AIDLC_VERSION) and help from
  a neutral cwd; re-run version with an EMPTY PATH proving the compiled
  version path needs no PATH bun.
- bundle inspection: a text bundle of the entry must contain no literal
  bun spawn outside the marked dev-mode line (the dispatcher's dev spawn
  carries an inline marker).
- cross targets: file(1) needle (ELF/PE32+/Mach-O) + >10MB size; Windows
  outfile expects bun's appended .exe.

Artifacts land in gitignored build/binaries/ with build-results.json.
Internal build tooling: no version bump.

Tests: t231 builds the real native artifact, re-runs it from /tmp against
the stamped version, and proves the version gate can FAIL via an injected
fake entry (AIDLC_BUILD_ENTRY test seam).
v2 shipped 2.3.10 (linter eslint pin) and reserved 2.3.11-2.3.14, so this
branch's versions shift again: 2.3.8 (workspace verbs) -> 2.3.15 and 2.3.9
(config/plugin verbs) -> 2.3.16. CHANGELOG headings renumbered to match
with the 2.3.10 entry preserved above 2.3.7; core + dist version files and
the README badge bump to 2.3.16. Test slots t226-t231 + t238 do not collide
with v2's t221-t225/t232/t233/t237 and are unchanged. dist regenerated.
…that runs its delegates

Fixes leandrodamascena's PR #560 review findings:

- workspaceCommandUtilityArgv now emits [noun, "switch", name] for an
  explicit switch instead of dropping the switch token, so a pre-existing
  intent or space whose slug shadows a verb (list, birth, create) stays
  reachable through the engine and dispatcher instead of being misread as
  that verb.
- classifyRuntimeCompileCommand anchors its new aidlc-runtime reject leg to
  the start of a shell command segment instead of matching the bare
  two-word substring anywhere in the command string, so a legacy transition
  whose --user-input prose happens to contain "aidlc runtime" no longer
  suppresses the post-transition runtime-graph recompile.
- The compiled native binary now statically imports each of the 15
  dispatcher delegate modules through a literal switch instead of a
  runtime-computed import path, so Bun's --compile bundler can discover and
  embed them; previously every delegated subcommand failed with a missing
  module error.
- build-binaries.ts gates the native artifact with a real plugin sync
  delegate call in addition to the existing version/help/grep checks, so a
  future regression that breaks delegate embedding fails the build instead
  of shipping a broken binary.
- The plugin-compose hook's PATH-preferred aidlc invocation now only exits
  early on success; a nonzero plugin sync falls through to the bun compose
  path instead of leaving compose unreachable.
- Mirrors the intent-birth-for-init emitter correction in
  core/knowledge/aidlc-shared/audit-format.md that the parallel state
  machine doc already had.

Extends t226 (path-anchored reject leg, prose-in-flag-value negative case),
t229 (engine and dispatcher switch to a verb-named record without
birthing), t230 (dispatcher parity for the corrected switch argv), t231
(the exit-vs-fallthrough plugin hook shape), and t238 (a real plugin sync
delegate call against the rebuilt native artifact, plus a fake-entry
negative case for the new gate).
…hes on a bad stage graph

Finishes the delegate-embedding fix from the previous commit: the compiled
binary's delegates now run, but doctor and next still crashed because
stage-graph.json, scope-grid.json, harness.json, scopes/, and agents/ are
resolved relative to import.meta.url, which points at a synthetic /$bunfs/
path inside a compiled binary with no real filesystem presence there.

Bun's static-asset embedding (import x from "./data/foo.json" with { type:
"file" }) is not viable for these paths: stage-graph.json and friends are
compiled, per-harness artifacts that live only under dist/<harness>/, never
under core/tools/data/ where the harness-neutral aidlc-lib.ts and
aidlc-graph.ts source lives, so a static import of that path fails
typecheck and dev-mode bun run for every caller that loads these modules
straight out of core/ (six unit tests, plus dev-mode delegation).

Instead, each data-path resolver now tries, in order: the existing env-var
override (unchanged), the module-relative path if it exists on disk
(unchanged behavior for every source-tree and dist/<harness>/ install), and
only then a new fallback relative to the running executable's own location
(dirname(process.execPath)), which resolves correctly even through a PATH
lookup or a symlink. build-binaries.ts now stages data/, scopes/, agents/,
and aidlc-common/stages/ beside the compiled artifact so that fallback has
something to find.

Also fixes a related pre-existing crash in handleDoctor: an unguarded
loadGraph() call threw straight out of the command instead of degrading to
a failing advisory row, unlike every other doctor check in the same
function. Reproduces on any real install with a missing or corrupt
stage-graph.json, not just the compiled binary.

New build-binaries.ts gates: runtime-assets (proves the sibling trees
staged) and delegate-doctor-data (runs doctor from a scratch tmpdir against
the compiled artifact and fails on any Cannot find module / $bunfs / ENOENT
signature). Extends t238 with both, and t37 with the handleDoctor crash
reproduction.
…ase, not bare-name sugar

The finding-1 fix in the previous commit (workspaceCommandUtilityArgv
emitting the literal "switch" token) was applied unconditionally to every
switch-kind WorkspaceCommand. That over-corrected: bare-name switch sugar
(space teamB, explicit: false) got 3-tokenized into space switch teamB too,
even though the reviewer's finding and the shipped CHANGELOG both describe
bare-name sugar as unaffected, still-desired behavior.

workspaceCommandUtilityArgv now branches on the command's existing explicit
flag: explicit switch <name> still emits [noun, "switch", name] (the actual
fix), bare-name sugar keeps the original [noun, name] shape.

t229's own migration-parity table and precedence-pin test were edited in
the prior commit to match the regressed behavior instead of catching it;
reverted those four rows and the precedence-pin assertion back to the
2-token shape they pinned before. t114 (cases 20 and 22), t178, and
t198 needed no test changes - they already pinned the correct sugar
behavior and were red against the code bug, not stale.

Regenerated tests/.coverage-registry.json + .coverage-ratchet.json (the
prior two commits changed covered units without refreshing them).
Close the five gaps found by compiling the dispatcher and running its
delegated paths as a real binary:

- Add aidlc-runtime-paths.ts, a shared resolution ladder (env seam ->
  project install -> module-relative -> executable-packaged runtime)
  and route sensorsDir, data/scopes/agents/stages/skills/conductor
  lookups through it, so `aidlc sensor list`, graph compiles, and
  validation work outside a seeded project layout.
- Re-enter the compiled executable by public noun in bolt/swarm
  spawnSibling and utility runBunTool instead of `bun run
  <import.meta.url path>`, so compiled fork/merge and plugin-selection
  regeneration need no bun on PATH. Bundle the four sensor worker
  scripts and dispatch them via internal __sensor-script verbs.
- Extract --project-dir as a global flag before noun/verb parsing, so
  interleaved forms (`space --project-dir X create teamB`) route
  correctly; thread it to delegates via AIDLC_PROJECT_DIR.
- Accept legacy top-level `space-create` in the dispatcher grammar.
- Stage complete per-harness distributions under
  build/binaries/<target>/runtime/<harness>/ and grow the native gate
  set: sensor list/fire, unseeded graph compile, validate outputs,
  generated-surface checks, per-harness runtime resolution, plugin
  select, conductor persona, workspace flag forms, and bolt/swarm
  self-reentry, all with an empty PATH; doctor must report a non-zero
  complete schema count.

The compiled binary detects the project's harness dir when
AIDLC_HARNESS_DIR is unset - main() pins runtimeHarnessDir()'s probe
(.claude/.kiro/.codex by tools/data/harness.json) instead of assuming
.claude, and resolveHookPath shares the same ladder; a kiro-only
install now resolves .kiro data with no env, gated by
harness-probe-kiro.

rulesDir keeps a module-relative rung ahead of the packaged-runtime
fallback so dev checkouts and installed trees resolve rules without
env pinning; fixture copy-lists that cherry-pick aidlc-lib.ts gain its
new aidlc-runtime-paths.ts sibling.
@apackeer
apackeer force-pushed the feature/single-cli-dispatcher branch from 50db1f3 to d5b235f Compare July 17, 2026 22:33
@apackeer
apackeer merged commit 124572d into v2 Jul 17, 2026
@apackeer
apackeer deleted the feature/single-cli-dispatcher branch July 17, 2026 22:36
osataken pushed a commit to osataken/aidlc-workflows that referenced this pull request Jul 22, 2026
…ards

- Renumber test to t244 (t238 taken by awslabs#560 on v2)
- Test 4: compare row order directly (no sort) so scrambled tables fail
- Test 1: derive INIT_ROW_LABEL from graph numbers so renumbered init
  stages trip the guard
- Conflict resolution: keep v2's 14-agent wording, add matrix link only
osataken pushed a commit to osataken/aidlc-workflows that referenced this pull request Jul 23, 2026
…ards

- Renumber test to t244 (t238 taken by awslabs#560 on v2)
- Test 4: compare row order directly (no sort) so scrambled tables fail
- Test 1: derive INIT_ROW_LABEL from graph numbers so renumbered init
  stages trip the guard
- Conflict resolution: keep v2's 14-agent wording, add matrix link only
osataken pushed a commit to osataken/aidlc-workflows that referenced this pull request Jul 24, 2026
…ards

- Renumber test to t244 (t238 taken by awslabs#560 on v2)
- Test 4: compare row order directly (no sort) so scrambled tables fail
- Test 1: derive INIT_ROW_LABEL from graph numbers so renumbered init
  stages trip the guard
- Conflict resolution: keep v2's 14-agent wording, add matrix link only
osataken pushed a commit to osataken/aidlc-workflows that referenced this pull request Jul 27, 2026
…ards

- Renumber test to t244 (t238 taken by awslabs#560 on v2)
- Test 4: compare row order directly (no sort) so scrambled tables fail
- Test 1: derive INIT_ROW_LABEL from graph numbers so renumbered init
  stages trip the guard
- Conflict resolution: keep v2's 14-agent wording, add matrix link only
leandrodamascena pushed a commit that referenced this pull request Jul 27, 2026
…#596)

* docs: add stage-by-scope matrix to the scopes guide, drift-guarded by t238

The guide stated per-scope stage counts ("22 of 32") and skip prose, but
the full stage x scope EXECUTE/SKIP view existed only as compiled JSON
(scope-grid.json) or the runtime scope-table command - a reader could
not see which stages a scope walks through without joining 32 stage
frontmatter blocks by hand.

- docs/guide/05-scopes-and-depth.md: new Stage-by-Scope Matrix section
  after the Scope Routing Table - all 9 stock scopes x 32 stages,
  generated from the compiled scope-grid.json + stage-graph.json,
  wrapped in BEGIN/END scope-stage-matrix markers. Footnote clarifies
  membership vs runtime-conditional execution and that composed scopes
  live in scope-grid.json only.
- tests/unit/t238-scope-matrix-doc-sync.test.ts: drift guard in the
  t132 two-direction discipline - columns, row identity, every cell,
  the collapsed initialization row, and totals, all cross-checked
  against the compiled grid (itself pinned to stage frontmatter by
  t124), forward and reverse. Mechanism: none (pure in-process reads).
- docs/guide/04-phases-and-stages.md: cross-link the matrix from the
  stage-colors note and Next Steps.
- docs/reference/11-contributing.md: "Adding a Scope" step 8 names the
  matrix and its guard so scope authors update it in the same PR.

Doc + test only - no version/CHANGELOG bump per the changelog policy.

* fix: address PR review — rebase on v2, renumber t238→t244, tighten guards

- Renumber test to t244 (t238 taken by #560 on v2)
- Test 4: compare row order directly (no sort) so scrambled tables fail
- Test 1: derive INIT_ROW_LABEL from graph numbers so renumbered init
  stages trip the guard
- Conflict resolution: keep v2's 14-agent wording, add matrix link only

* fix: correct ALWAYS legend wording, guard duplicate special rows

- docs/guide/04-phases-and-stages.md: ALWAYS now reads 'runs whenever the
  selected scope includes it' — matches the orchestrator reference and the
  matrix (1.4 Scope Definition is ALWAYS yet skipped by 6 scopes)
- t244: parseDocTable throws on duplicate init row, totals row, or stage
  row — a duplicated special row no longer silently overwrites
- t244: template literal for init-label assertion (Biome preference)

* fix: validate separator row and tighten special-row label/numeric checks

- parseDocTable now validates lines[1] is a well-formed Markdown table
  separator (each cell matches :?-{3,}:?) with correct column count
- Initialization row requires exact label 'Initialization (all 3 stages)'
- Footer row requires exact label '**Total stages**' (not substring match)
- Total cells must be strictly numeric (/^\d+$/ after stripping bold)

* fix: harden parser — validate header labels, cell content, body row column count, derive init label

- Validate header[0] is '#' and header[1] is 'Stage' at parse time
- Constrain scope cells to only ✓ or empty (catches typos/wrong Unicode)
- Validate every body row has the same column count as the header
- Derive INIT_STAGE_LABEL from initStages.length instead of hard-coding '3'

* fix: correct runtime semantics, validate contiguous table and full marker

- docs/guide/05-scopes-and-depth.md: rewrite matrix footnote — ✓ marks
  static scope membership; CONDITIONAL stages self-skip; pending stages
  reshaped via composer proposal (not 'skip at approval gate')
- Same file: fix identical claim in 'When in doubt, start with feature'
- t244: validate BEGIN marker line ends with --> (broken marker no longer
  hides the matrix inside an HTML comment)
- t244: require pipe-lines form one contiguous block (blank line mid-table
  breaks Markdown rendering and now throws)
- docs/reference/11-contributing.md: 'Adding a Stage' step 5 now mentions
  the matrix and t244

---------

Co-authored-by: osataken <osataken@amazon.co.th>
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.

2 participants