From bb643b86352abfe42059eb06d395f70f400f9acf Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Wed, 13 May 2026 18:17:47 +0200 Subject: [PATCH] fix(work): dispatch CLI handoff broke after import-side-effect guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cli.mjs's `dispatch` branch called `import("./dispatch.mjs")` and relied on dispatch.mjs's top-level CLI block running as a side effect of the import. The earlier guard added to dispatch.mjs (to stop the CLI firing when sibling work scripts import `resolveClaudeAuth`) also stopped this legit handoff — so `pnpm work dispatch` silently exited with no output. Fix: explicit CLI entry function, called by name. Same pattern already in use for prd-ship + decompose. dispatch.mjs: - Wraps the args parsing + print/execute branch in `export async function runCli(args)` - The invokedDirectly guard now wraps `runCli(process.argv.slice(2))` so direct-invocation (`node scripts/work/dispatch.mjs ...`) still works cli.mjs: - Imports runCli as runDispatch - The `cmd === "dispatch"` branch calls runDispatch(args) directly with a .catch attached (instead of import("./dispatch.mjs")) Verified: `pnpm work dispatch` now correctly prints the dispatch plan for the first ready task (`binder-wrap-helper / 01-wire-use-case-helper`'s first bullet); decompose tests stay 9/9. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/work/cli.mjs | 5 +++-- scripts/work/dispatch.mjs | 21 ++++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/scripts/work/cli.mjs b/scripts/work/cli.mjs index 62e26ab..cd3ff66 100644 --- a/scripts/work/cli.mjs +++ b/scripts/work/cli.mjs @@ -13,6 +13,7 @@ import { fileURLToPath } from "node:url"; import { buildState } from "./state-builder.mjs"; import { runCli as runPrdShip } from "./prd-ship.mjs"; import { runCli as runDecompose } from "./decompose.mjs"; +import { runCli as runDispatch } from "./dispatch.mjs"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = path.resolve(__dirname, "..", ".."); @@ -138,8 +139,8 @@ else if (cmd === "next") printNext(); else if (cmd === "ready") printReady(); else if (cmd === "blocked") printBlocked(); else if (cmd === "dispatch") { - // Re-dispatch to the dispatch script so it can handle its own --execute flag - import("./dispatch.mjs").catch((e) => { + // dispatch.mjs handles its own --execute flag + runDispatch(process.argv.slice(3)).catch((e) => { console.error(e); process.exit(1); }); diff --git a/scripts/work/dispatch.mjs b/scripts/work/dispatch.mjs index 99da3c7..0c07889 100644 --- a/scripts/work/dispatch.mjs +++ b/scripts/work/dispatch.mjs @@ -295,15 +295,22 @@ async function executeDispatch() { console.log("(Automatic state mutation by the orchestrator is v2.)"); } -// Only run the CLI when this module is invoked directly. Without this guard, -// importing any export (e.g. `resolveClaudeAuth` from sibling work scripts) -// triggers the CLI as a side effect. -const invokedDirectly = import.meta.url === `file://${process.argv[1]}`; -if (invokedDirectly) { - const args = process.argv.slice(2); +/** + * Explicit CLI entry. Exported so cli.mjs can dispatch into this module + * without relying on a top-level side effect (which would also fire when + * sibling work scripts import `resolveClaudeAuth`, etc.). + */ +export async function runCli(args) { if (args.includes("--execute")) { - executeDispatch(); + await executeDispatch(); } else { printPlan(); } } + +// When invoked directly (`node scripts/work/dispatch.mjs ...`), run the CLI. +// When imported by cli.mjs or any sibling, do nothing — the caller decides. +const invokedDirectly = import.meta.url === `file://${process.argv[1]}`; +if (invokedDirectly) { + runCli(process.argv.slice(2)); +}