From 4cf979aaa56f43a5ff3c3d8c94b2b14a8f8f7e83 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Wed, 13 May 2026 08:05:19 +0200 Subject: [PATCH] feat(scripts): pnpm work ready + blocked subcommands, DAG-aware next --- scripts/work/cli.mjs | 72 ++++++++++++++++++++++++++++++--------- scripts/work/cli.test.mjs | 12 +++++++ 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/scripts/work/cli.mjs b/scripts/work/cli.mjs index 6440349..504088a 100644 --- a/scripts/work/cli.mjs +++ b/scripts/work/cli.mjs @@ -20,7 +20,9 @@ const STATE_FILE = path.join(WORK_ROOT, "_state.json"); function rebuildState() { const state = buildState(WORK_ROOT); fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2) + "\n"); - console.log(`Rebuilt ${path.relative(REPO_ROOT, STATE_FILE)} with ${Object.keys(state.epics).length} epic(s).`); + console.log( + `Rebuilt ${path.relative(REPO_ROOT, STATE_FILE)} with ${Object.keys(state.epics).length} epic(s).`, + ); } function printStatus() { @@ -41,31 +43,67 @@ function printStatus() { { total: 0, done: 0 }, ); const epicMark = mark(epic.status); - console.log(`${epicMark} ${epicId} — ${epic.title} (${storyTotals.done}/${storyTotals.total} tasks done)`); + console.log( + `${epicMark} ${epicId} — ${epic.title} (${storyTotals.done}/${storyTotals.total} tasks done)`, + ); for (const sid of storyIds) { const s = epic.stories[sid]; - console.log(` ${mark(s.status)} ${sid} (${s.ac_completed}/${s.ac_total}) — ${s.title}`); + console.log( + ` ${mark(s.status)} ${sid} (${s.ac_completed}/${s.ac_total}) — ${s.title}`, + ); } } } function printNext() { const state = buildState(WORK_ROOT); - const epicIds = Object.keys(state.epics).sort(); - for (const epicId of epicIds) { - const epic = state.epics[epicId]; - if (epic.status === "done") continue; - const storyIds = Object.keys(epic.stories).sort(); - for (const sid of storyIds) { - const s = epic.stories[sid]; - if (s.status !== "done") { - console.log(`${epicId} / ${sid} — ${s.title}`); - console.log(` status: ${s.status}, tasks: ${s.ac_completed}/${s.ac_total}`); - return; + if (state.ready.length === 0) { + if (state.blocked.length > 0) { + console.log("No ready stories. Blocked:"); + for (const b of state.blocked) { + console.log( + ` ${b.epic} / ${b.story} — waiting on: ${b.waiting_on.join(", ")}`, + ); } + } else { + console.log("All epics + stories are done. ✓"); } + return; + } + const r = state.ready[0]; + console.log(`${r.epic} / ${r.story} — ${r.title}`); + console.log(` (use \`pnpm work ready\` to see all ready stories)`); +} + +function printReady() { + const state = buildState(WORK_ROOT); + if (state.ready.length === 0) { + console.log( + "No ready stories. Run `pnpm work blocked` to see what's waiting on what.", + ); + return; + } + console.log( + `${state.ready.length} ready stor${state.ready.length === 1 ? "y" : "ies"}:`, + ); + for (const r of state.ready) { + console.log(` ${r.epic} / ${r.story} — ${r.title}`); + } +} + +function printBlocked() { + const state = buildState(WORK_ROOT); + if (state.blocked.length === 0) { + console.log("No blocked stories."); + return; + } + console.log( + `${state.blocked.length} blocked stor${state.blocked.length === 1 ? "y" : "ies"}:`, + ); + for (const b of state.blocked) { + console.log(` ${b.epic} / ${b.story} — ${b.title}`); + console.log(` waiting on: ${b.waiting_on.join(", ")}`); } - console.log("All epics + stories are done. ✓"); } function mark(status) { @@ -76,7 +114,7 @@ function mark(status) { } function usage() { - console.log("Usage: pnpm work "); + console.log("Usage: pnpm work "); process.exit(2); } @@ -84,4 +122,6 @@ const cmd = process.argv[2]; if (cmd === "rebuild-state") rebuildState(); else if (cmd === "status") printStatus(); else if (cmd === "next") printNext(); +else if (cmd === "ready") printReady(); +else if (cmd === "blocked") printBlocked(); else usage(); diff --git a/scripts/work/cli.test.mjs b/scripts/work/cli.test.mjs index 901289f..e41a037 100644 --- a/scripts/work/cli.test.mjs +++ b/scripts/work/cli.test.mjs @@ -21,6 +21,8 @@ describe("pnpm work cli", () => { expect(out).toContain("rebuild-state"); expect(out).toContain("status"); expect(out).toContain("next"); + expect(out).toContain("ready"); + expect(out).toContain("blocked"); }); it("rebuild-state writes _state.json", () => { @@ -39,4 +41,14 @@ describe("pnpm work cli", () => { const out = run("next"); expect(out.length).toBeGreaterThan(0); }); + + it("ready prints something", () => { + const out = run("ready"); + expect(out.length).toBeGreaterThan(0); + }); + + it("blocked prints something", () => { + const out = run("blocked"); + expect(out.length).toBeGreaterThan(0); + }); });