Skip to content

feat(hooks): flip install --all to the dispatcher + migrate legacy (B, stage 2b)#607

Merged
Necmttn merged 1 commit into
mainfrom
feat/hook-dispatcher-flip
Jun 25, 2026
Merged

feat(hooks): flip install --all to the dispatcher + migrate legacy (B, stage 2b)#607
Necmttn merged 1 commit into
mainfrom
feat/hook-dispatcher-flip

Conversation

@Necmttn

@Necmttn Necmttn commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Context

Stage 2b of the hybrid dispatcher (B), on #605 (core) + #606 (scaffold/embed). Flips ax hooks install --all to register the single dispatcher instead of N per-guard hooks, and migrates off legacy per-guard entries. One spawn now multiplexes every guard; guards never double-fire.

What

dispatch-install.ts (pure planning + thin Effect):

  • planDispatcherInstall — fan the dispatcher's per-event plan (dispatchInstallPlan, per-event tool matchers UNION-ed) across providers; command = bun <dir>/dispatch.{ts,js}.
  • planLegacyRemoval — which rows to drop: only ax-owned entries (an # ax:<id> marker) whose marker-stripped command is bun <dir>/<guard>.{ts,js} for this dir + target provider/scope. A user's own hook is never touched, even on a colliding command.
  • resolveDispatcherPath + installDispatcher (idempotent add + migrate).

cli.ts--all resolves the dispatcher, calls installDispatcher, prints installed/skipped/migrated. Single-file install <file> unchanged.

Tested (no DB)

  • 9 pure unit tests — plan fan-out; legacy-removal safety (skips non-ax / different dir / wrong provider+scope / the dispatcher itself).
  • Integration test exercises the real config CRUD against a temp settings.json (project scope via repoRoot). readAllHooks(withEvidence:false) returns before it touches SurrealClient, so a never-invoked stub layer suffices — no DB needed. Asserts: dispatcher entries written, legacy ax guard migrated, user hook preserved, re-run idempotent.

bun test apps/axctl/src/hooks/206 pass, 0 fail. bun run typecheck → exit 0.

Migration safety

planLegacyRemoval removes a row only if all hold: has an # ax:<id> marker (ax-owned), command matches a legacy guard path for the exact workspace dir, and provider+scope match. Hand-written hooks and the dispatcher entry itself are left alone — covered by dedicated tests.

Roadmap

  1. feat(hooks): dispatcher core — multiplex all guards in one process (B, stage 1) #605 dispatch core ✅
  2. feat(hooks): scaffold + embed the single dispatcher (B, stage 2) #606 scaffold + embed ✅
  3. This PR--all installs the dispatcher + migrates legacy.
  4. Stage 3 — daemon POST /hooks/eval + effect-free shim (daemon-first, lazy-import bundle on fallback). The latency win.

🤖 Generated with Claude Code

…(B, stage 2b)

`ax hooks install --all` now registers the SINGLE dispatcher instead of N
per-guard hooks, and migrates off any legacy per-guard entries - so one spawn
multiplexes every guard (the #605/#606 dispatcher) and guards never double-fire.

- dispatch-install.ts:
  - planDispatcherInstall - fan the dispatcher's per-event plan
    (`dispatchInstallPlan`, per-event tool matchers) across providers; command
    = `bun <dir>/dispatch.{ts,js}`.
  - planLegacyRemoval - which existing rows to drop: ONLY ax-owned entries
    (an `# ax:<id>` marker) whose stripped command is `bun <dir>/<guard>.{ts,js}`
    for THIS dir + target provider/scope. A user's own hook is never touched.
  - resolveDispatcherPath + installDispatcher (idempotent add + migrate).
- cli.ts: --all resolves the dispatcher, calls installDispatcher, prints
  installed/skipped/migrated; single-file install unchanged. (export stripAxMarker)

Tested without a DB:
- 9 pure unit tests (plan fan-out, legacy-removal safety: skips non-ax / wrong
  dir / wrong provider+scope / the dispatcher itself).
- integration test exercises the REAL config CRUD against a temp settings.json
  (project scope via repoRoot; readAllHooks(withEvidence:false) returns before
  touching SurrealClient, so a stub layer suffices): dispatcher entries written,
  legacy ax guard migrated, user hook preserved, re-run idempotent.

`bun test apps/axctl/src/hooks/` -> 206 pass. typecheck -> exit 0.

Stage 2b of B. Remaining: stage 3 daemon /hooks/eval fast-path (the latency win).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying ax with  Cloudflare Pages  Cloudflare Pages

Latest commit: f8e8128
Status: ✅  Deploy successful!
Preview URL: https://20075c69.ax-62d.pages.dev
Branch Preview URL: https://feat-hook-dispatcher-flip.ax-62d.pages.dev

View logs

@Necmttn Necmttn merged commit a0f98af into main Jun 25, 2026
3 checks passed
@Necmttn Necmttn deleted the feat/hook-dispatcher-flip branch June 25, 2026 02:30
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.

1 participant