#!/usr/bin/env node /** * pnpm work — CLI for the local work-system. * * Subcommands: * rebuild-state Rewrites docs/work/_state.json from the current markdown * status Prints a tree of all epics + their stories * next Prints the first non-done story in the first non-done epic */ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { buildState } from "./state-builder.mjs"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = path.resolve(__dirname, "..", ".."); const WORK_ROOT = path.join(REPO_ROOT, "docs", "work"); 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).`); } function printStatus() { const state = buildState(WORK_ROOT); const epicIds = Object.keys(state.epics).sort(); if (epicIds.length === 0) { console.log("No epics found under docs/work/."); return; } for (const epicId of epicIds) { const epic = state.epics[epicId]; const storyIds = Object.keys(epic.stories).sort(); const storyTotals = storyIds.reduce( (acc, sid) => ({ total: acc.total + epic.stories[sid].ac_total, done: acc.done + epic.stories[sid].ac_completed, }), { total: 0, done: 0 }, ); const epicMark = mark(epic.status); 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}`); } } } 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; } } } console.log("All epics + stories are done. ✓"); } function mark(status) { if (status === "done") return "✓"; if (status === "in-progress") return "→"; if (status === "blocked") return "✗"; return "○"; } function usage() { console.log("Usage: pnpm work "); process.exit(2); } const cmd = process.argv[2]; if (cmd === "rebuild-state") rebuildState(); else if (cmd === "status") printStatus(); else if (cmd === "next") printNext(); else usage();