@stcrft/statecraft 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,7 +15,7 @@ Then run `statecraft <command>`. Or use `npx @stcrft/statecraft init` (etc.) wit
15
15
 
16
16
  | Command | Description |
17
17
  |--------|-------------|
18
- | **`statecraft init`** | Interactive setup: create a board file and optionally generate rules for Cursor (`.cursor/rules/statecraft.mdc`), Claude Code (`.claude/rules/statecraft.md`), and/or Codex (`AGENTS.md`) so your AI assistant knows the board path, task lifecycle, and AI guidelines. Re-run to create a new board; edit generated files to tweak guidelines. |
18
+ | **`statecraft init`** | Interactive setup: create a board file and optionally generate rules for Cursor (`.cursor/rules/statecraft.mdc`), Claude Code (`.claude/rules/statecraft.md`), and/or Codex (`AGENTS.md`). Prompts for workflow options (defaults: enforce “create task before any work”, require each task to have a spec `.md` file, include task spec format guidelines). Re-run to create a new board; edit generated files to tweak guidelines. |
19
19
  | **`statecraft spec`** | Print the board format spec (for AI agents; no paths). |
20
20
  | **`statecraft validate [path]`** | Parse and validate a board file. Exit 0 if valid, 1 on errors. |
21
21
  | **`statecraft summarize [path]`** | Print a short text summary of the board (name, columns, tasks, WIP/blocked). |
package/dist/index.js CHANGED
@@ -12,6 +12,9 @@ var INIT_DEFAULT_BOARD_PATH = "board.yaml";
12
12
  var INIT_DEFAULT_TASKS_DIR = "tasks";
13
13
  var CANONICAL_COLUMNS = ["Backlog", "Ready", "In Progress", "Done"];
14
14
  var SPEC_FILENAME = "spec.md";
15
+ var INIT_STRICT_MODE_DEFAULT = true;
16
+ var INIT_REQUIRE_SPEC_FILE_DEFAULT = true;
17
+ var INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT = true;
15
18
 
16
19
  // src/executors/init.ts
17
20
  import fs from "fs";
@@ -20,9 +23,43 @@ import readline from "readline";
20
23
  import { stringify } from "yaml";
21
24
 
22
25
  // src/executors/rule-content.ts
23
- function buildStatecraftRuleBody(boardPath, tasksDir) {
24
- return `# Statecraft
26
+ var DEFAULT_RULE_OPTIONS = {
27
+ strictMode: INIT_STRICT_MODE_DEFAULT,
28
+ requireSpecFile: INIT_REQUIRE_SPEC_FILE_DEFAULT,
29
+ includeTaskSpecFormat: INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT
30
+ };
31
+ function buildStatecraftRuleBody(boardPath, tasksDir, options = {}) {
32
+ const opts = { ...DEFAULT_RULE_OPTIONS, ...options };
33
+ const strictBlock = opts.strictMode && `## \u26A0\uFE0F REQUIRED BEFORE ANY OTHER ACTION (100% enforced)
34
+
35
+ **Before** doing any substantive work (coding, fixing, refactoring, or answering the user's request with implementation), you **MUST**:
36
+
37
+ 1. **Ensure a task exists:** In \`${boardPath}\`, either **create** a new task under \`tasks:\` with \`status: Backlog\` (use a kebab-case id, e.g. \`fix-header-import\`), or use an existing task. New tasks start in **Backlog** (vague is OK).
38
+ 2. ${opts.requireSpecFile ? `**Spec file:** Create or update \`${tasksDir}/<task-id>.md\` (required for each task).` : `**Spec file:** Optionally create or update \`${tasksDir}/<task-id>.md\` with description and acceptance criteria.`}
39
+ 3. **Flow:** Do **not** put the task in In Progress until it has been refined. After discovery/planning, update the task's spec (description, acceptance criteria); when you know what to do and the spec is clear, set \`status\` to **Ready**. Only when you are **actually starting the work** (e.g. writing code), set \`status\` to **In Progress**. So: Backlog \u2192 Ready (after refinement) \u2192 In Progress (when working) \u2192 Done.
40
+
41
+ You **must not** start implementation (no code, no concrete changes) until the task is at least in **Ready** and you are about to work on it; then move to **In Progress**. If \`${boardPath}\` does not exist yet, create a minimal valid board file (board name, columns: Backlog, Ready, In Progress, Done, tasks: {}) and the task in Backlog before proceeding.
42
+
43
+ ---
44
+ `;
45
+ const taskSpecFormatSection = opts.includeTaskSpecFormat ? `
46
+ ## Task spec file format (\`${tasksDir}/<task-id>.md\`)
25
47
 
48
+ Follow this structure so task specs are consistent and machine-friendly:
49
+
50
+ - **Title:** First line or \`# <Task title>\` (match the board \`title\` or expand it).
51
+ - **Description:** Short context or problem statement (optional \`## Description\`).
52
+ - **Acceptance criteria / Definition of Done:** \`## Acceptance criteria\` or \`## Definition of Done\` with a checklist (e.g. \`- [ ] item\`) so completion is unambiguous. All items must be checked before moving the task to Done.
53
+ - **Notes / Dependencies:** Optional \`## Notes\`, \`## Dependencies\` (for human context; \`depends_on\` in the board is the source of truth for task ordering).
54
+
55
+ Keep each file focused on one task; use the task id in the filename (e.g. \`fix-auth-timeout.md\`).
56
+
57
+ ---
58
+ ` : "";
59
+ const requireSpecBullet = opts.requireSpecFile ? `- **Spec required:** Every task must have a \`spec\` field pointing to \`${tasksDir}/<task-id>.md\`. Create the .md file when creating the task.
60
+ ` : "";
61
+ return `# Statecraft
62
+ ${strictBlock || ""}
26
63
  This project uses Statecraft for the task board.
27
64
 
28
65
  - **Board file:** \`${boardPath}\`
@@ -39,15 +76,17 @@ Use \`statecraft\` if installed globally. If not in PATH, use \`npx statecraft\`
39
76
 
40
77
  ## Task lifecycle (edit board and task files directly)
41
78
 
42
- - **Prepare for work:** When the task has a clear definition and dependencies are satisfied, set \`status\` to **Ready**.
43
- - **Start work:** Set the task's \`status\` to **In Progress**. Optionally open/read the task's \`spec\` file.
44
- - **Finish work:** Set the task's \`status\` to **Done** only when the task's acceptance criteria (in its spec file) are satisfied.
45
- - **Create task:** Add an entry under \`tasks\` with \`status: Backlog\` (id, title, optional description, spec, owner, priority, depends_on). If needed, create \`${tasksDir}/<task-id>.md\` with description and DoD.
79
+ **Flow:** Backlog \u2192 Ready \u2192 In Progress \u2192 Done. Do not skip Ready.
46
80
 
81
+ - **Create task:** Add an entry under \`tasks\` with \`status: Backlog\` (id, title, optional description, spec, owner, priority, depends_on). ${opts.requireSpecFile ? `Create \`${tasksDir}/<task-id>.md\` for each task (required).` : `If needed, create \`${tasksDir}/<task-id>.md\` with description and DoD.`} Backlog = initial or vague; refinement comes next.
82
+ - **Refine and move to Ready:** After discovery or planning, update the task's spec file (description, acceptance criteria). When the definition is clear and you know how to solve it, set \`status\` to **Ready**. Ready = "ready to be worked on."
83
+ - **Start work:** Only when you are about to do the actual work (code, fix, etc.), set the task's \`status\` to **In Progress**. Read the task's \`spec\` file if needed.
84
+ - **Finish work:** Set the task's \`status\` to **Done** only when the task's acceptance criteria (in its spec file) are satisfied.
85
+ ${taskSpecFormatSection}
47
86
  ## AI guidelines for creating tickets
48
87
 
49
- - **Keep the board in sync:** When doing substantive work (e.g. a new feature, fix, or refactor), create a new task in Backlog or move an existing task to In Progress/Done as appropriate. Update the board file and optionally add or update a task spec in \`${tasksDir}/\`.
50
- - **Task naming:** kebab-case, verb or noun phrase (e.g. \`fix-auth-timeout\`).
88
+ - **Keep the board in sync:** When doing substantive work, create a new task in **Backlog** (or use an existing one). After discovery/refinement, update the task spec and move to **Ready**; only when actually working move to **In Progress**, then **Done** when criteria are met. Do not skip Ready.
89
+ ${requireSpecBullet}- **Task naming:** kebab-case, verb or noun phrase (e.g. \`fix-auth-timeout\`).
51
90
  - **Description:** One line summary; optional markdown for context.
52
91
  - **Definition of Done:** Acceptance criteria in task spec; all checked before moving to Done.
53
92
  - **Task fields (from spec):** \`title\` (required), \`status\` (required), optional \`description\`, \`spec\` (path to .md), \`owner\`, \`priority\`, \`depends_on\`.
@@ -95,6 +134,27 @@ async function collectInitAnswers(rl, answers, index) {
95
134
  "WIP limit for In Progress (optional, press Enter to skip)",
96
135
  ""
97
136
  );
137
+ const strictModeStr = await getAnswer(
138
+ rl,
139
+ answers,
140
+ index,
141
+ "Enforce Statecraft workflow (create task before any work)? (Y/n)",
142
+ INIT_STRICT_MODE_DEFAULT ? "Y" : "n"
143
+ );
144
+ const requireSpecStr = await getAnswer(
145
+ rl,
146
+ answers,
147
+ index,
148
+ "Require each task to have a spec .md file? (Y/n)",
149
+ INIT_REQUIRE_SPEC_FILE_DEFAULT ? "Y" : "n"
150
+ );
151
+ const includeFormatStr = await getAnswer(
152
+ rl,
153
+ answers,
154
+ index,
155
+ "Include task spec .md format guidelines in rules? (Y/n)",
156
+ INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT ? "Y" : "n"
157
+ );
98
158
  const genCursor = await getAnswer(rl, answers, index, "Generate Cursor rule? (Y/n)", "Y");
99
159
  const genClaude = await getAnswer(rl, answers, index, "Generate Claude Code rule? (y/n)", "n");
100
160
  const genCodex = await getAnswer(rl, answers, index, "Generate Codex instructions (AGENTS.md)? (y/n)", "n");
@@ -103,6 +163,9 @@ async function collectInitAnswers(rl, answers, index) {
103
163
  boardPath,
104
164
  tasksDir,
105
165
  wipStr,
166
+ strictMode: /^y(es)?$/i.test(strictModeStr.trim()),
167
+ requireSpecFile: /^y(es)?$/i.test(requireSpecStr.trim()),
168
+ includeTaskSpecFormat: /^y(es)?$/i.test(includeFormatStr.trim()),
106
169
  generateCursorRule: /^y(es)?$/i.test(genCursor.trim()),
107
170
  generateClaudeRule: /^y(es)?$/i.test(genClaude.trim()),
108
171
  generateCodexAgents: /^y(es)?$/i.test(genCodex.trim())
@@ -139,21 +202,21 @@ function writeRuleFile(filePath, content, log) {
139
202
  log(filePath);
140
203
  }
141
204
  var CODECX_MARKER = "## Statecraft (generated by statecraft init)";
142
- function writeCursorRule(cwd, boardPath, specDir, log) {
205
+ function writeCursorRule(cwd, boardPath, specDir, options, log) {
143
206
  const cursorRulesDir = path.resolve(cwd, ".cursor", "rules");
144
207
  const cursorRulePath = path.join(cursorRulesDir, "statecraft.mdc");
145
- const content = buildCursorRuleContent(boardPath, specDir);
208
+ const content = buildCursorRuleContent(boardPath, specDir, options);
146
209
  writeRuleFile(cursorRulePath, content, (p) => log(`Wrote Cursor rule to ${p}`));
147
210
  }
148
- function writeClaudeRule(cwd, boardPath, specDir, log) {
211
+ function writeClaudeRule(cwd, boardPath, specDir, options, log) {
149
212
  const claudeRulesDir = path.resolve(cwd, ".claude", "rules");
150
213
  const claudeRulePath = path.join(claudeRulesDir, "statecraft.md");
151
- const content = buildClaudeRuleContent(boardPath, specDir);
214
+ const content = buildClaudeRuleContent(boardPath, specDir, options);
152
215
  writeRuleFile(claudeRulePath, content, (p) => log(`Wrote Claude Code rule to ${p}`));
153
216
  }
154
- function writeCodexRule(cwd, boardPath, specDir, log) {
217
+ function writeCodexRule(cwd, boardPath, specDir, options, log) {
155
218
  const codexAgentsPath = path.resolve(cwd, "AGENTS.md");
156
- const codexContent = buildCodexAgentsContent(boardPath, specDir);
219
+ const codexContent = buildCodexAgentsContent(boardPath, specDir, options);
157
220
  if (fs.existsSync(codexAgentsPath)) {
158
221
  const existing = fs.readFileSync(codexAgentsPath, "utf-8");
159
222
  if (existing.includes(CODECX_MARKER)) {
@@ -167,20 +230,20 @@ function writeCodexRule(cwd, boardPath, specDir, log) {
167
230
  log(`Wrote Codex instructions to ${codexAgentsPath}`);
168
231
  }
169
232
  }
170
- function buildCursorRuleContent(boardPath, tasksDir) {
233
+ function buildCursorRuleContent(boardPath, tasksDir, options) {
171
234
  return `---
172
235
  description: Statecraft board and task workflow; when to update the board and how to create tasks
173
236
  alwaysApply: true
174
237
  ---
175
- ${buildStatecraftRuleBody(boardPath, tasksDir)}`;
238
+ ${buildStatecraftRuleBody(boardPath, tasksDir, options)}`;
176
239
  }
177
- function buildClaudeRuleContent(boardPath, tasksDir) {
178
- return buildStatecraftRuleBody(boardPath, tasksDir);
240
+ function buildClaudeRuleContent(boardPath, tasksDir, options) {
241
+ return buildStatecraftRuleBody(boardPath, tasksDir, options);
179
242
  }
180
- function buildCodexAgentsContent(boardPath, tasksDir) {
243
+ function buildCodexAgentsContent(boardPath, tasksDir, options) {
181
244
  return `## Statecraft (generated by statecraft init)
182
245
 
183
- ${buildStatecraftRuleBody(boardPath, tasksDir)}`;
246
+ ${buildStatecraftRuleBody(boardPath, tasksDir, options)}`;
184
247
  }
185
248
  async function runInit(options) {
186
249
  const answers = options?.answers;
@@ -208,9 +271,14 @@ async function runInit(options) {
208
271
  Created board at ${absolutePath}`);
209
272
  log(`Task spec files: ${path.join(path.dirname(collected.boardPath), collected.tasksDir)}/<task-id>.md`);
210
273
  const specDir = path.join(path.dirname(collected.boardPath), collected.tasksDir);
211
- if (collected.generateCursorRule) writeCursorRule(cwd, collected.boardPath, specDir, log);
212
- if (collected.generateClaudeRule) writeClaudeRule(cwd, collected.boardPath, specDir, log);
213
- if (collected.generateCodexAgents) writeCodexRule(cwd, collected.boardPath, specDir, log);
274
+ const ruleOptions = {
275
+ strictMode: collected.strictMode,
276
+ requireSpecFile: collected.requireSpecFile,
277
+ includeTaskSpecFormat: collected.includeTaskSpecFormat
278
+ };
279
+ if (collected.generateCursorRule) writeCursorRule(cwd, collected.boardPath, specDir, ruleOptions, log);
280
+ if (collected.generateClaudeRule) writeClaudeRule(cwd, collected.boardPath, specDir, ruleOptions, log);
281
+ if (collected.generateCodexAgents) writeCodexRule(cwd, collected.boardPath, specDir, ruleOptions, log);
214
282
  log("\nRun `statecraft validate " + collected.boardPath + "` to validate, or `statecraft render " + collected.boardPath + "` to view.");
215
283
  } catch (err) {
216
284
  const message = err instanceof Error ? err.message : String(err);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/executors/init.ts","../src/executors/rule-content.ts","../src/render-server.ts","../src/executors/render.ts","../src/executors/spec.ts","../src/utils.ts","../src/executors/summarize.ts","../src/executors/validate.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { DEFAULT_BOARD_PATH, DEFAULT_RENDER_PORT } from \"./constants.js\";\nimport { runInit, runRender, runSpec, runSummarize, runValidate } from \"./executors/index.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../package.json\") as { version: string };\n\nfunction rejectMultiplePaths(extra: string[]): void {\n if (extra.length > 0) {\n process.stderr.write(\"Only one board file per run. Multiple paths are not supported.\\n\");\n process.exitCode = 1;\n }\n}\n\nconst program = new Command();\n\nprogram\n .name(\"statecraft\")\n .description(\"Validate, summarize, and render Statecraft board files\")\n .version(version);\n\nprogram\n .command(\"init\")\n .description(\"Interactive setup: create board and configure Statecraft for your workflow\")\n .action(async () => {\n await runInit().catch((err) => {\n process.stderr.write(err instanceof Error ? err.message : String(err) + \"\\n\");\n process.exitCode = 1;\n });\n });\n\nprogram\n .command(\"spec\")\n .description(\"Print the board format spec (for AI agents)\")\n .action(() => {\n runSpec();\n });\n\nprogram\n .command(\"validate\")\n .description(\"Validate a board file (exit 0 if valid, 1 on errors)\")\n .argument(\"[path]\", \"path to board file\", DEFAULT_BOARD_PATH)\n .argument(\"[extra...]\", \"ignored (only one path allowed)\")\n .action((path: string, extra: string[]) => {\n rejectMultiplePaths(extra);\n if (process.exitCode !== 1) runValidate(path);\n });\n\nprogram\n .command(\"summarize\")\n .description(\"Print a short text summary of the board\")\n .argument(\"[path]\", \"path to board file\", DEFAULT_BOARD_PATH)\n .argument(\"[extra...]\", \"ignored (only one path allowed)\")\n .action((path: string, extra: string[]) => {\n rejectMultiplePaths(extra);\n if (process.exitCode !== 1) runSummarize(path);\n });\n\nprogram\n .command(\"render\")\n .description(\"Serve the board in the browser (read-only UI)\")\n .argument(\"[path]\", \"path to board file\", DEFAULT_BOARD_PATH)\n .option(\"-p, --port <number>\", \"port for the server\", String(DEFAULT_RENDER_PORT))\n .option(\"--open\", \"open browser after starting server\")\n .action((path: string, options: { port: string; open: boolean }) => {\n const port = parseInt(options.port, 10) || DEFAULT_RENDER_PORT;\n runRender(path, { port, open: options.open ?? false });\n });\n\nprogram.parseAsync();\n","/** Default path to the board file when none is given (validate, summarize, render). */\nexport const DEFAULT_BOARD_PATH = \"./board.yaml\";\n\n/** Default port for the render server. */\nexport const DEFAULT_RENDER_PORT = 3000;\n\n/** Debounce delay (ms) for file watcher before broadcasting board updates to WebSocket clients. */\nexport const RENDER_WATCH_DEBOUNCE_MS = 100;\n\n/** Default board file path offered by init (cwd). */\nexport const INIT_DEFAULT_BOARD_PATH = \"board.yaml\";\n\n/** Default directory for task .md files (relative to board), offered by init. */\nexport const INIT_DEFAULT_TASKS_DIR = \"tasks\";\n\n/** Canonical column set per spec; init creates boards with these columns. */\nexport const CANONICAL_COLUMNS = [\"Backlog\", \"Ready\", \"In Progress\", \"Done\"] as const;\n\n/** Filename of the board format spec shipped with the CLI package (statecraft spec). */\nexport const SPEC_FILENAME = \"spec.md\";\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport readline from \"node:readline\";\nimport { stringify } from \"yaml\";\nimport {\n CANONICAL_COLUMNS,\n INIT_DEFAULT_BOARD_PATH,\n INIT_DEFAULT_TASKS_DIR,\n} from \"../constants.js\";\nimport { buildStatecraftRuleBody } from \"./rule-content.js\";\n\nexport interface InitAnswers {\n boardName: string;\n columns: Array<{ name: string; limit?: number }>;\n boardPath: string;\n tasksDir: string;\n}\n\n/** Raw answers collected from prompts (before parsing WIP or booleans). */\ninterface CollectedInitAnswers {\n boardName: string;\n boardPath: string;\n tasksDir: string;\n wipStr: string;\n generateCursorRule: boolean;\n generateClaudeRule: boolean;\n generateCodexAgents: boolean;\n}\n\nfunction question(rl: readline.Interface, prompt: string, defaultValue?: string): Promise<string> {\n const suffix = defaultValue !== undefined ? ` (default: ${defaultValue})` : \"\";\n return new Promise((resolve) => {\n rl.question(`${prompt}${suffix}: `, (answer) => {\n const trimmed = answer.trim();\n resolve(trimmed !== \"\" ? trimmed : (defaultValue ?? \"\"));\n });\n });\n}\n\nasync function getAnswer(\n rl: readline.Interface | null,\n answers: string[] | undefined,\n index: { current: number },\n prompt: string,\n defaultValue?: string\n): Promise<string> {\n if (answers !== undefined) {\n const raw = answers[index.current++] ?? \"\";\n const trimmed = raw.trim();\n return trimmed !== \"\" ? trimmed : (defaultValue ?? \"\");\n }\n return question(rl!, prompt, defaultValue);\n}\n\nfunction parseWipLimit(wipStr: string): number | undefined {\n if (wipStr === \"\") return undefined;\n const n = parseInt(wipStr, 10);\n return Number.isInteger(n) && n >= 1 ? n : undefined;\n}\n\n/** Collect all init prompts into a structured object. */\nasync function collectInitAnswers(\n rl: readline.Interface | null,\n answers: string[] | undefined,\n index: { current: number }\n): Promise<CollectedInitAnswers> {\n const boardName = await getAnswer(rl, answers, index, \"Board name\");\n const boardPath = await getAnswer(rl, answers, index, \"Path for board file\", INIT_DEFAULT_BOARD_PATH);\n const tasksDir = await getAnswer(\n rl,\n answers,\n index,\n \"Directory for task .md files (relative to board)\",\n INIT_DEFAULT_TASKS_DIR\n );\n const wipStr = await getAnswer(\n rl,\n answers,\n index,\n \"WIP limit for In Progress (optional, press Enter to skip)\",\n \"\"\n );\n const genCursor = await getAnswer(rl, answers, index, \"Generate Cursor rule? (Y/n)\", \"Y\");\n const genClaude = await getAnswer(rl, answers, index, \"Generate Claude Code rule? (y/n)\", \"n\");\n const genCodex = await getAnswer(rl, answers, index, \"Generate Codex instructions (AGENTS.md)? (y/n)\", \"n\");\n\n return {\n boardName,\n boardPath,\n tasksDir,\n wipStr,\n generateCursorRule: /^y(es)?$/i.test(genCursor.trim()),\n generateClaudeRule: /^y(es)?$/i.test(genClaude.trim()),\n generateCodexAgents: /^y(es)?$/i.test(genCodex.trim()),\n };\n}\n\n/** Build the board object (name, columns, empty tasks) from collected answers. */\nfunction buildBoardFromAnswers(\n boardName: string,\n tasksDir: string,\n wipStr: string\n): { board: string; columns: Array<string | { name: string; limit: number }>; tasks: Record<string, never> } {\n const inProgressLimit = parseWipLimit(wipStr);\n const columns: Array<string | { name: string; limit: number }> = [\n CANONICAL_COLUMNS[0],\n CANONICAL_COLUMNS[1],\n inProgressLimit != null ? { name: CANONICAL_COLUMNS[2], limit: inProgressLimit } : CANONICAL_COLUMNS[2],\n CANONICAL_COLUMNS[3],\n ];\n return {\n board: boardName,\n columns,\n tasks: {},\n };\n}\n\n/** Ensure parent directory exists and write file. */\nfunction ensureDirAndWrite(filePath: string, content: string): void {\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, content, \"utf-8\");\n}\n\n/** Write board YAML to cwd-relative path. */\nfunction writeBoardFile(\n cwd: string,\n boardPath: string,\n board: { board: string; columns: unknown[]; tasks: object }\n): void {\n const absolutePath = path.resolve(cwd, boardPath);\n const yamlContent = stringify(board, { lineWidth: 0 });\n ensureDirAndWrite(absolutePath, yamlContent);\n}\n\n/** Write a rule file and optionally log. */\nfunction writeRuleFile(\n filePath: string,\n content: string,\n log: (msg: string) => void\n): void {\n ensureDirAndWrite(filePath, content);\n log(filePath);\n}\n\nconst CODECX_MARKER = \"## Statecraft (generated by statecraft init)\";\n\nfunction writeCursorRule(cwd: string, boardPath: string, specDir: string, log: (msg: string) => void): void {\n const cursorRulesDir = path.resolve(cwd, \".cursor\", \"rules\");\n const cursorRulePath = path.join(cursorRulesDir, \"statecraft.mdc\");\n const content = buildCursorRuleContent(boardPath, specDir);\n writeRuleFile(cursorRulePath, content, (p) => log(`Wrote Cursor rule to ${p}`));\n}\n\nfunction writeClaudeRule(cwd: string, boardPath: string, specDir: string, log: (msg: string) => void): void {\n const claudeRulesDir = path.resolve(cwd, \".claude\", \"rules\");\n const claudeRulePath = path.join(claudeRulesDir, \"statecraft.md\");\n const content = buildClaudeRuleContent(boardPath, specDir);\n writeRuleFile(claudeRulePath, content, (p) => log(`Wrote Claude Code rule to ${p}`));\n}\n\nfunction writeCodexRule(cwd: string, boardPath: string, specDir: string, log: (msg: string) => void): void {\n const codexAgentsPath = path.resolve(cwd, \"AGENTS.md\");\n const codexContent = buildCodexAgentsContent(boardPath, specDir);\n if (fs.existsSync(codexAgentsPath)) {\n const existing = fs.readFileSync(codexAgentsPath, \"utf-8\");\n if (existing.includes(CODECX_MARKER)) {\n log(\"AGENTS.md already contains Statecraft section; skipped.\");\n return;\n }\n fs.writeFileSync(codexAgentsPath, existing.trimEnd() + \"\\n\\n\" + codexContent + \"\\n\", \"utf-8\");\n log(`Appended Statecraft section to ${codexAgentsPath}`);\n } else {\n fs.writeFileSync(codexAgentsPath, codexContent + \"\\n\", \"utf-8\");\n log(`Wrote Codex instructions to ${codexAgentsPath}`);\n }\n}\n\n// --- Public rule content builders (used by init and by tests) ---\n\nexport function buildCursorRuleContent(boardPath: string, tasksDir: string): string {\n return `---\ndescription: Statecraft board and task workflow; when to update the board and how to create tasks\nalwaysApply: true\n---\n${buildStatecraftRuleBody(boardPath, tasksDir)}`;\n}\n\n/** Claude Code: modular rule in .claude/rules/ (markdown, no frontmatter). */\nexport function buildClaudeRuleContent(boardPath: string, tasksDir: string): string {\n return buildStatecraftRuleBody(boardPath, tasksDir);\n}\n\n/** Codex: section for AGENTS.md at project root. */\nexport function buildCodexAgentsContent(boardPath: string, tasksDir: string): string {\n return `## Statecraft (generated by statecraft init)\n\n${buildStatecraftRuleBody(boardPath, tasksDir)}`;\n}\n\n// --- runInit ---\n\nexport interface RunInitOptions {\n /** For tests: pre-filled answers (board name, board path, tasks dir, WIP, Cursor y/n, Claude y/n, Codex y/n). */\n answers?: string[];\n /** For tests: working directory for writing board and rule files (default: process.cwd()). */\n cwd?: string;\n}\n\nexport async function runInit(options?: RunInitOptions): Promise<void> {\n const answers = options?.answers;\n const cwd = options?.cwd ?? process.cwd();\n const answerIndex = { current: 0 };\n const rl = answers === undefined ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null;\n const log = (msg: string) => {\n if (rl) process.stdout.write(msg + \"\\n\");\n };\n\n try {\n if (rl) {\n process.stdout.write(\"\\nStatecraft init — create your board and connect it to your workflow.\\n\\n\");\n }\n\n const collected = await collectInitAnswers(rl, answers, answerIndex);\n if (!collected.boardName) {\n process.stderr.write(\"Board name is required.\\n\");\n process.exitCode = 1;\n return;\n }\n\n rl?.close();\n\n const board = buildBoardFromAnswers(collected.boardName, collected.tasksDir, collected.wipStr);\n writeBoardFile(cwd, collected.boardPath, board);\n\n const absolutePath = path.resolve(cwd, collected.boardPath);\n log(`\\nCreated board at ${absolutePath}`);\n log(`Task spec files: ${path.join(path.dirname(collected.boardPath), collected.tasksDir)}/<task-id>.md`);\n\n const specDir = path.join(path.dirname(collected.boardPath), collected.tasksDir);\n\n if (collected.generateCursorRule) writeCursorRule(cwd, collected.boardPath, specDir, log);\n if (collected.generateClaudeRule) writeClaudeRule(cwd, collected.boardPath, specDir, log);\n if (collected.generateCodexAgents) writeCodexRule(cwd, collected.boardPath, specDir, log);\n\n log(\"\\nRun `statecraft validate \" + collected.boardPath + \"` to validate, or `statecraft render \" + collected.boardPath + \"` to view.\");\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(message + \"\\n\");\n process.exitCode = 1;\n } finally {\n rl?.close();\n }\n}\n","/**\n * Shared content for generated AI rules (Cursor, Claude Code, Codex).\n * Single source for the Statecraft rule body so init only orchestrates.\n */\n\nexport function buildStatecraftRuleBody(boardPath: string, tasksDir: string): string {\n return `# Statecraft\n\nThis project uses Statecraft for the task board.\n\n- **Board file:** \\`${boardPath}\\`\n- **Task spec files:** \\`${tasksDir}/<task-id>.md\\` (relative to board directory)\n- **Columns (canonical):** Backlog → Ready → In Progress → Done.\n\n## Commands\n\nUse \\`statecraft\\` if installed globally. If not in PATH, use \\`npx statecraft\\` (npm), \\`pnpm dlx statecraft\\` (pnpm), or \\`yarn dlx statecraft\\` (yarn)—e.g. \\`npx statecraft validate ${boardPath}\\`.\n\n- Get board format spec: \\`statecraft spec\\`\n- Validate board: \\`statecraft validate ${boardPath}\\`\n- View board in browser: \\`statecraft render ${boardPath}\\`\n\n## Task lifecycle (edit board and task files directly)\n\n- **Prepare for work:** When the task has a clear definition and dependencies are satisfied, set \\`status\\` to **Ready**.\n- **Start work:** Set the task's \\`status\\` to **In Progress**. Optionally open/read the task's \\`spec\\` file.\n- **Finish work:** Set the task's \\`status\\` to **Done** only when the task's acceptance criteria (in its spec file) are satisfied.\n- **Create task:** Add an entry under \\`tasks\\` with \\`status: Backlog\\` (id, title, optional description, spec, owner, priority, depends_on). If needed, create \\`${tasksDir}/<task-id>.md\\` with description and DoD.\n\n## AI guidelines for creating tickets\n\n- **Keep the board in sync:** When doing substantive work (e.g. a new feature, fix, or refactor), create a new task in Backlog or move an existing task to In Progress/Done as appropriate. Update the board file and optionally add or update a task spec in \\`${tasksDir}/\\`.\n- **Task naming:** kebab-case, verb or noun phrase (e.g. \\`fix-auth-timeout\\`).\n- **Description:** One line summary; optional markdown for context.\n- **Definition of Done:** Acceptance criteria in task spec; all checked before moving to Done.\n- **Task fields (from spec):** \\`title\\` (required), \\`status\\` (required), optional \\`description\\`, \\`spec\\` (path to .md), \\`owner\\`, \\`priority\\`, \\`depends_on\\`.\n- **Spec file:** Path relative to board directory, e.g. \\`${tasksDir}/<task-id>.md\\`.\n`;\n}\n","import express, { type Request, type Response } from \"express\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { createServer } from \"node:http\";\nimport { WebSocket, WebSocketServer } from \"ws\";\nimport { fileURLToPath } from \"node:url\";\nimport { DEFAULT_RENDER_PORT, RENDER_WATCH_DEBOUNCE_MS } from \"./constants.js\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n/**\n * Resolve path to the renderer's static files.\n * - When installed from npm: bundled at package-root/renderer-dist.\n * - When running in monorepo: sibling package at packages/renderer/dist.\n */\nfunction getRendererDistPath(): string {\n const bundled = path.resolve(__dirname, \"..\", \"renderer-dist\");\n if (fs.existsSync(bundled) && fs.statSync(bundled).isDirectory()) {\n return bundled;\n }\n return path.resolve(__dirname, \"..\", \"..\", \"renderer\", \"dist\");\n}\n\n/**\n * Read board file and return content as UTF-8. Returns null on error.\n */\nfunction readBoardContent(boardPath: string): string | null {\n try {\n const resolved = path.resolve(process.cwd(), boardPath);\n return fs.readFileSync(resolved, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nexport interface RenderServerOptions {\n boardPath: string;\n port?: number;\n openBrowser?: boolean;\n}\n\n/**\n * Start the render server: static app + GET /api/board + WebSocket /api/board/watch.\n * Watches the board file and broadcasts content to WS clients on change.\n */\nexport function startRenderServer(options: RenderServerOptions): void {\n const { boardPath, port = DEFAULT_RENDER_PORT, openBrowser = false } = options;\n const rendererDist = getRendererDistPath();\n\n if (!fs.existsSync(rendererDist) || !fs.statSync(rendererDist).isDirectory()) {\n process.stderr.write(\n \"Renderer build not found. Run: pnpm build\\n\"\n );\n process.exitCode = 1;\n return;\n }\n\n const app = express();\n\n // Board file directory (for resolving spec paths relative to board)\n const resolvedBoardPath = path.resolve(process.cwd(), boardPath);\n const boardDir = path.dirname(resolvedBoardPath);\n\n // API: board file content (raw YAML)\n app.get(\"/api/board\", (_req: Request, res: Response) => {\n const content = readBoardContent(boardPath);\n if (content === null) {\n res.status(404).type(\"text/plain\").send(\"Board file not found or unreadable.\");\n return;\n }\n res.type(\"text/yaml\").send(content);\n });\n\n // API: spec file content (path relative to board file directory; only .md files)\n app.get(\"/api/spec\", (req: Request, res: Response) => {\n const rawPath = typeof req.query.path === \"string\" ? req.query.path : \"\";\n if (!rawPath || rawPath.includes(\"..\")) {\n res.status(400).type(\"text/plain\").send(\"Invalid or missing path.\");\n return;\n }\n const ext = path.extname(rawPath).toLowerCase();\n if (ext !== \".md\") {\n res.status(400).type(\"text/plain\").send(\"Only .md spec files are allowed.\");\n return;\n }\n const resolved = path.resolve(boardDir, rawPath);\n const relative = path.relative(boardDir, resolved);\n if (relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n res.status(400).type(\"text/plain\").send(\"Path must be under board directory.\");\n return;\n }\n try {\n const content = fs.readFileSync(resolved, \"utf-8\");\n res.type(\"text/markdown\").send(content);\n } catch {\n res.status(404).type(\"text/plain\").send(\"Spec file not found or unreadable.\");\n }\n });\n\n // Static files (must be after /api routes so they take precedence)\n app.use(express.static(rendererDist));\n\n // SPA fallback: serve index.html for GET requests not handled by static (Express 5 / path-to-regexp v8 reject bare '*')\n app.use((_req: Request, res: Response, next: express.NextFunction) => {\n if (_req.method !== \"GET\" || res.headersSent) return next();\n const indexHtml = path.join(rendererDist, \"index.html\");\n if (fs.existsSync(indexHtml)) {\n res.sendFile(indexHtml);\n } else {\n res.status(404).send(\"Not found\");\n }\n });\n\n const server = createServer(app);\n\n // WebSocket: /api/board/watch — broadcast board content on file change\n const wss = new WebSocketServer({ noServer: true });\n\n server.on(\"upgrade\", (request, socket, head) => {\n const url = new URL(request.url ?? \"\", `http://${request.headers.host}`);\n if (url.pathname === \"/api/board/watch\") {\n wss.handleUpgrade(request, socket, head, (ws: WebSocket) => {\n wss.emit(\"connection\", ws, request);\n });\n } else {\n socket.destroy();\n }\n });\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n function broadcastBoard(): void {\n const content = readBoardContent(boardPath);\n const payload = content ?? \"\";\n wss.clients.forEach((client: WebSocket) => {\n if (client.readyState === 1) {\n client.send(payload);\n }\n });\n }\n\n wss.on(\"connection\", (ws: WebSocket) => {\n // Send current board on connect\n const content = readBoardContent(boardPath);\n if (content !== null) {\n ws.send(content);\n }\n });\n\n try {\n fs.watch(resolvedBoardPath, { persistent: false }, () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n broadcastBoard();\n }, RENDER_WATCH_DEBOUNCE_MS);\n });\n } catch {\n // File might not exist yet; watcher will not run\n }\n\n server.on(\"error\", (err: Error & { code?: string }) => {\n process.stderr.write(`Render server error: ${err.message}\\n`);\n if (err.code === \"EADDRINUSE\") {\n process.stderr.write(`Port ${port} is in use. Try --port <number>.\\n`);\n }\n process.exitCode = 1;\n });\n\n server.listen(port, () => {\n const url = `http://localhost:${port}`;\n process.stdout.write(`Open ${url}\\n`);\n if (openBrowser) {\n import(\"open\").then(({ default: open }) => {\n open(url).catch(() => {});\n });\n }\n });\n\n const shutdown = () => {\n server.close(() => {\n process.exit(process.exitCode ?? 0);\n });\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n","import { startRenderServer } from \"../render-server.js\";\n\nexport function runRender(path: string, options: { port: number; open: boolean }): void {\n startRenderServer({\n boardPath: path,\n port: options.port,\n openBrowser: options.open,\n });\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { SPEC_FILENAME } from \"../constants.js\";\nimport { findPackageRoot } from \"../utils.js\";\n\nexport function runSpec(): void {\n const thisFile = fileURLToPath(import.meta.url);\n const startDir = path.dirname(thisFile);\n const packageRoot = findPackageRoot(startDir);\n if (!packageRoot) {\n process.stderr.write(\"statecraft spec: could not find package root\\n\");\n process.exitCode = 1;\n return;\n }\n const specPath = path.join(packageRoot, SPEC_FILENAME);\n if (!fs.existsSync(specPath)) {\n process.stderr.write(`statecraft spec: spec file not found at ${specPath}\\n`);\n process.exitCode = 1;\n return;\n }\n try {\n const content = fs.readFileSync(specPath, \"utf-8\");\n process.stdout.write(content);\n if (!content.endsWith(\"\\n\")) process.stdout.write(\"\\n\");\n process.exitCode = 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`statecraft spec: ${message}\\n`);\n process.exitCode = 1;\n }\n}\n","/**\n * Shared CLI utilities (path resolution, etc.).\n */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Walk up from startDir until a directory containing package.json is found.\n * @returns Absolute path to package root, or null if not found.\n */\nexport function findPackageRoot(startDir: string): string | null {\n let dir = path.resolve(startDir);\n for (;;) {\n const pkgPath = path.join(dir, \"package.json\");\n if (fs.existsSync(pkgPath)) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n","import { parseBoard, summarize } from \"@stcrft/statecraft-core\";\n\nexport function runSummarize(path: string): void {\n try {\n const board = parseBoard(path);\n const summary = summarize(board);\n process.stdout.write(summary);\n process.exitCode = 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(message + \"\\n\");\n process.exitCode = 1;\n }\n}\n","import { parseBoard, validate } from \"@stcrft/statecraft-core\";\n\nfunction formatValidationError(err: { message: string; path?: string }): string {\n const prefix = err.path != null && err.path !== \"\" ? `${err.path}: ` : \"\";\n return `${prefix}${err.message}`;\n}\n\nexport function runValidate(path: string): void {\n try {\n const board = parseBoard(path);\n const result = validate(board);\n if (!result.valid) {\n for (const err of result.errors) {\n process.stderr.write(formatValidationError(err) + \"\\n\");\n }\n process.exitCode = 1;\n return;\n }\n process.exitCode = 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(message + \"\\n\");\n process.exitCode = 1;\n }\n}\n"],"mappings":"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","names":["fs","path","__dirname","path","fs","path","fs","path","fileURLToPath","fs","path","fileURLToPath","path","fs","path","parseBoard","path","require","path"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/executors/init.ts","../src/executors/rule-content.ts","../src/render-server.ts","../src/executors/render.ts","../src/executors/spec.ts","../src/utils.ts","../src/executors/summarize.ts","../src/executors/validate.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { DEFAULT_BOARD_PATH, DEFAULT_RENDER_PORT } from \"./constants.js\";\nimport { runInit, runRender, runSpec, runSummarize, runValidate } from \"./executors/index.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../package.json\") as { version: string };\n\nfunction rejectMultiplePaths(extra: string[]): void {\n if (extra.length > 0) {\n process.stderr.write(\"Only one board file per run. Multiple paths are not supported.\\n\");\n process.exitCode = 1;\n }\n}\n\nconst program = new Command();\n\nprogram\n .name(\"statecraft\")\n .description(\"Validate, summarize, and render Statecraft board files\")\n .version(version);\n\nprogram\n .command(\"init\")\n .description(\"Interactive setup: create board and configure Statecraft for your workflow\")\n .action(async () => {\n await runInit().catch((err) => {\n process.stderr.write(err instanceof Error ? err.message : String(err) + \"\\n\");\n process.exitCode = 1;\n });\n });\n\nprogram\n .command(\"spec\")\n .description(\"Print the board format spec (for AI agents)\")\n .action(() => {\n runSpec();\n });\n\nprogram\n .command(\"validate\")\n .description(\"Validate a board file (exit 0 if valid, 1 on errors)\")\n .argument(\"[path]\", \"path to board file\", DEFAULT_BOARD_PATH)\n .argument(\"[extra...]\", \"ignored (only one path allowed)\")\n .action((path: string, extra: string[]) => {\n rejectMultiplePaths(extra);\n if (process.exitCode !== 1) runValidate(path);\n });\n\nprogram\n .command(\"summarize\")\n .description(\"Print a short text summary of the board\")\n .argument(\"[path]\", \"path to board file\", DEFAULT_BOARD_PATH)\n .argument(\"[extra...]\", \"ignored (only one path allowed)\")\n .action((path: string, extra: string[]) => {\n rejectMultiplePaths(extra);\n if (process.exitCode !== 1) runSummarize(path);\n });\n\nprogram\n .command(\"render\")\n .description(\"Serve the board in the browser (read-only UI)\")\n .argument(\"[path]\", \"path to board file\", DEFAULT_BOARD_PATH)\n .option(\"-p, --port <number>\", \"port for the server\", String(DEFAULT_RENDER_PORT))\n .option(\"--open\", \"open browser after starting server\")\n .action((path: string, options: { port: string; open: boolean }) => {\n const port = parseInt(options.port, 10) || DEFAULT_RENDER_PORT;\n runRender(path, { port, open: options.open ?? false });\n });\n\nprogram.parseAsync();\n","/** Default path to the board file when none is given (validate, summarize, render). */\nexport const DEFAULT_BOARD_PATH = \"./board.yaml\";\n\n/** Default port for the render server. */\nexport const DEFAULT_RENDER_PORT = 3000;\n\n/** Debounce delay (ms) for file watcher before broadcasting board updates to WebSocket clients. */\nexport const RENDER_WATCH_DEBOUNCE_MS = 100;\n\n/** Default board file path offered by init (cwd). */\nexport const INIT_DEFAULT_BOARD_PATH = \"board.yaml\";\n\n/** Default directory for task .md files (relative to board), offered by init. */\nexport const INIT_DEFAULT_TASKS_DIR = \"tasks\";\n\n/** Canonical column set per spec; init creates boards with these columns. */\nexport const CANONICAL_COLUMNS = [\"Backlog\", \"Ready\", \"In Progress\", \"Done\"] as const;\n\n/** Filename of the board format spec shipped with the CLI package (statecraft spec). */\nexport const SPEC_FILENAME = \"spec.md\";\n\n/** Init default: enforce \"create task before work\" workflow for all agents. */\nexport const INIT_STRICT_MODE_DEFAULT = true;\n\n/** Init default: require each task to have a spec .md file in the tasks directory. */\nexport const INIT_REQUIRE_SPEC_FILE_DEFAULT = true;\n\n/** Init default: include task spec .md format guidelines in generated rules. */\nexport const INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT = true;\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport readline from \"node:readline\";\nimport { stringify } from \"yaml\";\nimport {\n CANONICAL_COLUMNS,\n INIT_DEFAULT_BOARD_PATH,\n INIT_DEFAULT_TASKS_DIR,\n INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT,\n INIT_REQUIRE_SPEC_FILE_DEFAULT,\n INIT_STRICT_MODE_DEFAULT,\n} from \"../constants.js\";\nimport { buildStatecraftRuleBody, type RuleOptions } from \"./rule-content.js\";\n\nexport interface InitAnswers {\n boardName: string;\n columns: Array<{ name: string; limit?: number }>;\n boardPath: string;\n tasksDir: string;\n}\n\n/** Raw answers collected from prompts (before parsing WIP or booleans). */\ninterface CollectedInitAnswers {\n boardName: string;\n boardPath: string;\n tasksDir: string;\n wipStr: string;\n strictMode: boolean;\n requireSpecFile: boolean;\n includeTaskSpecFormat: boolean;\n generateCursorRule: boolean;\n generateClaudeRule: boolean;\n generateCodexAgents: boolean;\n}\n\nfunction question(rl: readline.Interface, prompt: string, defaultValue?: string): Promise<string> {\n const suffix = defaultValue !== undefined ? ` (default: ${defaultValue})` : \"\";\n return new Promise((resolve) => {\n rl.question(`${prompt}${suffix}: `, (answer) => {\n const trimmed = answer.trim();\n resolve(trimmed !== \"\" ? trimmed : (defaultValue ?? \"\"));\n });\n });\n}\n\nasync function getAnswer(\n rl: readline.Interface | null,\n answers: string[] | undefined,\n index: { current: number },\n prompt: string,\n defaultValue?: string\n): Promise<string> {\n if (answers !== undefined) {\n const raw = answers[index.current++] ?? \"\";\n const trimmed = raw.trim();\n return trimmed !== \"\" ? trimmed : (defaultValue ?? \"\");\n }\n return question(rl!, prompt, defaultValue);\n}\n\nfunction parseWipLimit(wipStr: string): number | undefined {\n if (wipStr === \"\") return undefined;\n const n = parseInt(wipStr, 10);\n return Number.isInteger(n) && n >= 1 ? n : undefined;\n}\n\n/** Collect all init prompts into a structured object. */\nasync function collectInitAnswers(\n rl: readline.Interface | null,\n answers: string[] | undefined,\n index: { current: number }\n): Promise<CollectedInitAnswers> {\n const boardName = await getAnswer(rl, answers, index, \"Board name\");\n const boardPath = await getAnswer(rl, answers, index, \"Path for board file\", INIT_DEFAULT_BOARD_PATH);\n const tasksDir = await getAnswer(\n rl,\n answers,\n index,\n \"Directory for task .md files (relative to board)\",\n INIT_DEFAULT_TASKS_DIR\n );\n const wipStr = await getAnswer(\n rl,\n answers,\n index,\n \"WIP limit for In Progress (optional, press Enter to skip)\",\n \"\"\n );\n const strictModeStr = await getAnswer(\n rl,\n answers,\n index,\n \"Enforce Statecraft workflow (create task before any work)? (Y/n)\",\n INIT_STRICT_MODE_DEFAULT ? \"Y\" : \"n\"\n );\n const requireSpecStr = await getAnswer(\n rl,\n answers,\n index,\n \"Require each task to have a spec .md file? (Y/n)\",\n INIT_REQUIRE_SPEC_FILE_DEFAULT ? \"Y\" : \"n\"\n );\n const includeFormatStr = await getAnswer(\n rl,\n answers,\n index,\n \"Include task spec .md format guidelines in rules? (Y/n)\",\n INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT ? \"Y\" : \"n\"\n );\n const genCursor = await getAnswer(rl, answers, index, \"Generate Cursor rule? (Y/n)\", \"Y\");\n const genClaude = await getAnswer(rl, answers, index, \"Generate Claude Code rule? (y/n)\", \"n\");\n const genCodex = await getAnswer(rl, answers, index, \"Generate Codex instructions (AGENTS.md)? (y/n)\", \"n\");\n\n return {\n boardName,\n boardPath,\n tasksDir,\n wipStr,\n strictMode: /^y(es)?$/i.test(strictModeStr.trim()),\n requireSpecFile: /^y(es)?$/i.test(requireSpecStr.trim()),\n includeTaskSpecFormat: /^y(es)?$/i.test(includeFormatStr.trim()),\n generateCursorRule: /^y(es)?$/i.test(genCursor.trim()),\n generateClaudeRule: /^y(es)?$/i.test(genClaude.trim()),\n generateCodexAgents: /^y(es)?$/i.test(genCodex.trim()),\n };\n}\n\n/** Build the board object (name, columns, empty tasks) from collected answers. */\nfunction buildBoardFromAnswers(\n boardName: string,\n tasksDir: string,\n wipStr: string\n): { board: string; columns: Array<string | { name: string; limit: number }>; tasks: Record<string, never> } {\n const inProgressLimit = parseWipLimit(wipStr);\n const columns: Array<string | { name: string; limit: number }> = [\n CANONICAL_COLUMNS[0],\n CANONICAL_COLUMNS[1],\n inProgressLimit != null ? { name: CANONICAL_COLUMNS[2], limit: inProgressLimit } : CANONICAL_COLUMNS[2],\n CANONICAL_COLUMNS[3],\n ];\n return {\n board: boardName,\n columns,\n tasks: {},\n };\n}\n\n/** Ensure parent directory exists and write file. */\nfunction ensureDirAndWrite(filePath: string, content: string): void {\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, content, \"utf-8\");\n}\n\n/** Write board YAML to cwd-relative path. */\nfunction writeBoardFile(\n cwd: string,\n boardPath: string,\n board: { board: string; columns: unknown[]; tasks: object }\n): void {\n const absolutePath = path.resolve(cwd, boardPath);\n const yamlContent = stringify(board, { lineWidth: 0 });\n ensureDirAndWrite(absolutePath, yamlContent);\n}\n\n/** Write a rule file and optionally log. */\nfunction writeRuleFile(\n filePath: string,\n content: string,\n log: (msg: string) => void\n): void {\n ensureDirAndWrite(filePath, content);\n log(filePath);\n}\n\nconst CODECX_MARKER = \"## Statecraft (generated by statecraft init)\";\n\nfunction writeCursorRule(\n cwd: string,\n boardPath: string,\n specDir: string,\n options: Partial<RuleOptions>,\n log: (msg: string) => void\n): void {\n const cursorRulesDir = path.resolve(cwd, \".cursor\", \"rules\");\n const cursorRulePath = path.join(cursorRulesDir, \"statecraft.mdc\");\n const content = buildCursorRuleContent(boardPath, specDir, options);\n writeRuleFile(cursorRulePath, content, (p) => log(`Wrote Cursor rule to ${p}`));\n}\n\nfunction writeClaudeRule(\n cwd: string,\n boardPath: string,\n specDir: string,\n options: Partial<RuleOptions>,\n log: (msg: string) => void\n): void {\n const claudeRulesDir = path.resolve(cwd, \".claude\", \"rules\");\n const claudeRulePath = path.join(claudeRulesDir, \"statecraft.md\");\n const content = buildClaudeRuleContent(boardPath, specDir, options);\n writeRuleFile(claudeRulePath, content, (p) => log(`Wrote Claude Code rule to ${p}`));\n}\n\nfunction writeCodexRule(\n cwd: string,\n boardPath: string,\n specDir: string,\n options: Partial<RuleOptions>,\n log: (msg: string) => void\n): void {\n const codexAgentsPath = path.resolve(cwd, \"AGENTS.md\");\n const codexContent = buildCodexAgentsContent(boardPath, specDir, options);\n if (fs.existsSync(codexAgentsPath)) {\n const existing = fs.readFileSync(codexAgentsPath, \"utf-8\");\n if (existing.includes(CODECX_MARKER)) {\n log(\"AGENTS.md already contains Statecraft section; skipped.\");\n return;\n }\n fs.writeFileSync(codexAgentsPath, existing.trimEnd() + \"\\n\\n\" + codexContent + \"\\n\", \"utf-8\");\n log(`Appended Statecraft section to ${codexAgentsPath}`);\n } else {\n fs.writeFileSync(codexAgentsPath, codexContent + \"\\n\", \"utf-8\");\n log(`Wrote Codex instructions to ${codexAgentsPath}`);\n }\n}\n\n// --- Public rule content builders (used by init and by tests) ---\n\nexport function buildCursorRuleContent(\n boardPath: string,\n tasksDir: string,\n options?: Partial<RuleOptions>\n): string {\n return `---\ndescription: Statecraft board and task workflow; when to update the board and how to create tasks\nalwaysApply: true\n---\n${buildStatecraftRuleBody(boardPath, tasksDir, options)}`;\n}\n\n/** Claude Code: modular rule in .claude/rules/ (markdown, no frontmatter). */\nexport function buildClaudeRuleContent(\n boardPath: string,\n tasksDir: string,\n options?: Partial<RuleOptions>\n): string {\n return buildStatecraftRuleBody(boardPath, tasksDir, options);\n}\n\n/** Codex: section for AGENTS.md at project root. */\nexport function buildCodexAgentsContent(\n boardPath: string,\n tasksDir: string,\n options?: Partial<RuleOptions>\n): string {\n return `## Statecraft (generated by statecraft init)\n\n${buildStatecraftRuleBody(boardPath, tasksDir, options)}`;\n}\n\n// --- runInit ---\n\nexport interface RunInitOptions {\n /** For tests: pre-filled answers (board name, board path, tasks dir, WIP, strict mode Y/n, require spec Y/n, task spec format Y/n, Cursor y/n, Claude y/n, Codex y/n). */\n answers?: string[];\n /** For tests: working directory for writing board and rule files (default: process.cwd()). */\n cwd?: string;\n}\n\nexport async function runInit(options?: RunInitOptions): Promise<void> {\n const answers = options?.answers;\n const cwd = options?.cwd ?? process.cwd();\n const answerIndex = { current: 0 };\n const rl = answers === undefined ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null;\n const log = (msg: string) => {\n if (rl) process.stdout.write(msg + \"\\n\");\n };\n\n try {\n if (rl) {\n process.stdout.write(\"\\nStatecraft init — create your board and connect it to your workflow.\\n\\n\");\n }\n\n const collected = await collectInitAnswers(rl, answers, answerIndex);\n if (!collected.boardName) {\n process.stderr.write(\"Board name is required.\\n\");\n process.exitCode = 1;\n return;\n }\n\n rl?.close();\n\n const board = buildBoardFromAnswers(collected.boardName, collected.tasksDir, collected.wipStr);\n writeBoardFile(cwd, collected.boardPath, board);\n\n const absolutePath = path.resolve(cwd, collected.boardPath);\n log(`\\nCreated board at ${absolutePath}`);\n log(`Task spec files: ${path.join(path.dirname(collected.boardPath), collected.tasksDir)}/<task-id>.md`);\n\n const specDir = path.join(path.dirname(collected.boardPath), collected.tasksDir);\n const ruleOptions: Partial<RuleOptions> = {\n strictMode: collected.strictMode,\n requireSpecFile: collected.requireSpecFile,\n includeTaskSpecFormat: collected.includeTaskSpecFormat,\n };\n\n if (collected.generateCursorRule) writeCursorRule(cwd, collected.boardPath, specDir, ruleOptions, log);\n if (collected.generateClaudeRule) writeClaudeRule(cwd, collected.boardPath, specDir, ruleOptions, log);\n if (collected.generateCodexAgents) writeCodexRule(cwd, collected.boardPath, specDir, ruleOptions, log);\n\n log(\"\\nRun `statecraft validate \" + collected.boardPath + \"` to validate, or `statecraft render \" + collected.boardPath + \"` to view.\");\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(message + \"\\n\");\n process.exitCode = 1;\n } finally {\n rl?.close();\n }\n}\n","import {\n INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT,\n INIT_REQUIRE_SPEC_FILE_DEFAULT,\n INIT_STRICT_MODE_DEFAULT,\n} from \"../constants.js\";\n\n/**\n * Options for generated rule content. Used by init; defaults match init prompts (Y/n).\n */\nexport interface RuleOptions {\n /** Enforce \"create task before any work\" workflow (strict mode). Default true. */\n strictMode: boolean;\n /** Require each task to have a spec .md file. Default true. */\n requireSpecFile: boolean;\n /** Include task spec .md format guidelines in the rule. Default true. */\n includeTaskSpecFormat: boolean;\n}\n\nconst DEFAULT_RULE_OPTIONS: RuleOptions = {\n strictMode: INIT_STRICT_MODE_DEFAULT,\n requireSpecFile: INIT_REQUIRE_SPEC_FILE_DEFAULT,\n includeTaskSpecFormat: INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT,\n};\n\n/**\n * Shared content for generated AI rules (Cursor, Claude Code, Codex).\n * Single source for the Statecraft rule body so init only orchestrates.\n */\nexport function buildStatecraftRuleBody(\n boardPath: string,\n tasksDir: string,\n options: Partial<RuleOptions> = {}\n): string {\n const opts = { ...DEFAULT_RULE_OPTIONS, ...options };\n\n const strictBlock =\n opts.strictMode &&\n `## ⚠️ REQUIRED BEFORE ANY OTHER ACTION (100% enforced)\n\n**Before** doing any substantive work (coding, fixing, refactoring, or answering the user's request with implementation), you **MUST**:\n\n1. **Ensure a task exists:** In \\`${boardPath}\\`, either **create** a new task under \\`tasks:\\` with \\`status: Backlog\\` (use a kebab-case id, e.g. \\`fix-header-import\\`), or use an existing task. New tasks start in **Backlog** (vague is OK).\n2. ${opts.requireSpecFile ? `**Spec file:** Create or update \\`${tasksDir}/<task-id>.md\\` (required for each task).` : `**Spec file:** Optionally create or update \\`${tasksDir}/<task-id>.md\\` with description and acceptance criteria.`}\n3. **Flow:** Do **not** put the task in In Progress until it has been refined. After discovery/planning, update the task's spec (description, acceptance criteria); when you know what to do and the spec is clear, set \\`status\\` to **Ready**. Only when you are **actually starting the work** (e.g. writing code), set \\`status\\` to **In Progress**. So: Backlog → Ready (after refinement) → In Progress (when working) → Done.\n\nYou **must not** start implementation (no code, no concrete changes) until the task is at least in **Ready** and you are about to work on it; then move to **In Progress**. If \\`${boardPath}\\` does not exist yet, create a minimal valid board file (board name, columns: Backlog, Ready, In Progress, Done, tasks: {}) and the task in Backlog before proceeding.\n\n---\n`;\n\n const taskSpecFormatSection = opts.includeTaskSpecFormat\n ? `\n## Task spec file format (\\`${tasksDir}/<task-id>.md\\`)\n\nFollow this structure so task specs are consistent and machine-friendly:\n\n- **Title:** First line or \\`# <Task title>\\` (match the board \\`title\\` or expand it).\n- **Description:** Short context or problem statement (optional \\`## Description\\`).\n- **Acceptance criteria / Definition of Done:** \\`## Acceptance criteria\\` or \\`## Definition of Done\\` with a checklist (e.g. \\`- [ ] item\\`) so completion is unambiguous. All items must be checked before moving the task to Done.\n- **Notes / Dependencies:** Optional \\`## Notes\\`, \\`## Dependencies\\` (for human context; \\`depends_on\\` in the board is the source of truth for task ordering).\n\nKeep each file focused on one task; use the task id in the filename (e.g. \\`fix-auth-timeout.md\\`).\n\n---\n`\n : \"\";\n\n const requireSpecBullet = opts.requireSpecFile\n ? `- **Spec required:** Every task must have a \\`spec\\` field pointing to \\`${tasksDir}/<task-id>.md\\`. Create the .md file when creating the task.\n`\n : \"\";\n\n return `# Statecraft\n${strictBlock || \"\"}\nThis project uses Statecraft for the task board.\n\n- **Board file:** \\`${boardPath}\\`\n- **Task spec files:** \\`${tasksDir}/<task-id>.md\\` (relative to board directory)\n- **Columns (canonical):** Backlog → Ready → In Progress → Done.\n\n## Commands\n\nUse \\`statecraft\\` if installed globally. If not in PATH, use \\`npx statecraft\\` (npm), \\`pnpm dlx statecraft\\` (pnpm), or \\`yarn dlx statecraft\\` (yarn)—e.g. \\`npx statecraft validate ${boardPath}\\`.\n\n- Get board format spec: \\`statecraft spec\\`\n- Validate board: \\`statecraft validate ${boardPath}\\`\n- View board in browser: \\`statecraft render ${boardPath}\\`\n\n## Task lifecycle (edit board and task files directly)\n\n**Flow:** Backlog → Ready → In Progress → Done. Do not skip Ready.\n\n- **Create task:** Add an entry under \\`tasks\\` with \\`status: Backlog\\` (id, title, optional description, spec, owner, priority, depends_on). ${opts.requireSpecFile ? `Create \\`${tasksDir}/<task-id>.md\\` for each task (required).` : `If needed, create \\`${tasksDir}/<task-id>.md\\` with description and DoD.`} Backlog = initial or vague; refinement comes next.\n- **Refine and move to Ready:** After discovery or planning, update the task's spec file (description, acceptance criteria). When the definition is clear and you know how to solve it, set \\`status\\` to **Ready**. Ready = \"ready to be worked on.\"\n- **Start work:** Only when you are about to do the actual work (code, fix, etc.), set the task's \\`status\\` to **In Progress**. Read the task's \\`spec\\` file if needed.\n- **Finish work:** Set the task's \\`status\\` to **Done** only when the task's acceptance criteria (in its spec file) are satisfied.\n${taskSpecFormatSection}\n## AI guidelines for creating tickets\n\n- **Keep the board in sync:** When doing substantive work, create a new task in **Backlog** (or use an existing one). After discovery/refinement, update the task spec and move to **Ready**; only when actually working move to **In Progress**, then **Done** when criteria are met. Do not skip Ready.\n${requireSpecBullet}- **Task naming:** kebab-case, verb or noun phrase (e.g. \\`fix-auth-timeout\\`).\n- **Description:** One line summary; optional markdown for context.\n- **Definition of Done:** Acceptance criteria in task spec; all checked before moving to Done.\n- **Task fields (from spec):** \\`title\\` (required), \\`status\\` (required), optional \\`description\\`, \\`spec\\` (path to .md), \\`owner\\`, \\`priority\\`, \\`depends_on\\`.\n- **Spec file:** Path relative to board directory, e.g. \\`${tasksDir}/<task-id>.md\\`.\n`;\n}\n","import express, { type Request, type Response } from \"express\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { createServer } from \"node:http\";\nimport { WebSocket, WebSocketServer } from \"ws\";\nimport { fileURLToPath } from \"node:url\";\nimport { DEFAULT_RENDER_PORT, RENDER_WATCH_DEBOUNCE_MS } from \"./constants.js\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n/**\n * Resolve path to the renderer's static files.\n * - When installed from npm: bundled at package-root/renderer-dist.\n * - When running in monorepo: sibling package at packages/renderer/dist.\n */\nfunction getRendererDistPath(): string {\n const bundled = path.resolve(__dirname, \"..\", \"renderer-dist\");\n if (fs.existsSync(bundled) && fs.statSync(bundled).isDirectory()) {\n return bundled;\n }\n return path.resolve(__dirname, \"..\", \"..\", \"renderer\", \"dist\");\n}\n\n/**\n * Read board file and return content as UTF-8. Returns null on error.\n */\nfunction readBoardContent(boardPath: string): string | null {\n try {\n const resolved = path.resolve(process.cwd(), boardPath);\n return fs.readFileSync(resolved, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nexport interface RenderServerOptions {\n boardPath: string;\n port?: number;\n openBrowser?: boolean;\n}\n\n/**\n * Start the render server: static app + GET /api/board + WebSocket /api/board/watch.\n * Watches the board file and broadcasts content to WS clients on change.\n */\nexport function startRenderServer(options: RenderServerOptions): void {\n const { boardPath, port = DEFAULT_RENDER_PORT, openBrowser = false } = options;\n const rendererDist = getRendererDistPath();\n\n if (!fs.existsSync(rendererDist) || !fs.statSync(rendererDist).isDirectory()) {\n process.stderr.write(\n \"Renderer build not found. Run: pnpm build\\n\"\n );\n process.exitCode = 1;\n return;\n }\n\n const app = express();\n\n // Board file directory (for resolving spec paths relative to board)\n const resolvedBoardPath = path.resolve(process.cwd(), boardPath);\n const boardDir = path.dirname(resolvedBoardPath);\n\n // API: board file content (raw YAML)\n app.get(\"/api/board\", (_req: Request, res: Response) => {\n const content = readBoardContent(boardPath);\n if (content === null) {\n res.status(404).type(\"text/plain\").send(\"Board file not found or unreadable.\");\n return;\n }\n res.type(\"text/yaml\").send(content);\n });\n\n // API: spec file content (path relative to board file directory; only .md files)\n app.get(\"/api/spec\", (req: Request, res: Response) => {\n const rawPath = typeof req.query.path === \"string\" ? req.query.path : \"\";\n if (!rawPath || rawPath.includes(\"..\")) {\n res.status(400).type(\"text/plain\").send(\"Invalid or missing path.\");\n return;\n }\n const ext = path.extname(rawPath).toLowerCase();\n if (ext !== \".md\") {\n res.status(400).type(\"text/plain\").send(\"Only .md spec files are allowed.\");\n return;\n }\n const resolved = path.resolve(boardDir, rawPath);\n const relative = path.relative(boardDir, resolved);\n if (relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n res.status(400).type(\"text/plain\").send(\"Path must be under board directory.\");\n return;\n }\n try {\n const content = fs.readFileSync(resolved, \"utf-8\");\n res.type(\"text/markdown\").send(content);\n } catch {\n res.status(404).type(\"text/plain\").send(\"Spec file not found or unreadable.\");\n }\n });\n\n // Static files (must be after /api routes so they take precedence)\n app.use(express.static(rendererDist));\n\n // SPA fallback: serve index.html for GET requests not handled by static (Express 5 / path-to-regexp v8 reject bare '*')\n app.use((_req: Request, res: Response, next: express.NextFunction) => {\n if (_req.method !== \"GET\" || res.headersSent) return next();\n const indexHtml = path.join(rendererDist, \"index.html\");\n if (fs.existsSync(indexHtml)) {\n res.sendFile(indexHtml);\n } else {\n res.status(404).send(\"Not found\");\n }\n });\n\n const server = createServer(app);\n\n // WebSocket: /api/board/watch — broadcast board content on file change\n const wss = new WebSocketServer({ noServer: true });\n\n server.on(\"upgrade\", (request, socket, head) => {\n const url = new URL(request.url ?? \"\", `http://${request.headers.host}`);\n if (url.pathname === \"/api/board/watch\") {\n wss.handleUpgrade(request, socket, head, (ws: WebSocket) => {\n wss.emit(\"connection\", ws, request);\n });\n } else {\n socket.destroy();\n }\n });\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n function broadcastBoard(): void {\n const content = readBoardContent(boardPath);\n const payload = content ?? \"\";\n wss.clients.forEach((client: WebSocket) => {\n if (client.readyState === 1) {\n client.send(payload);\n }\n });\n }\n\n wss.on(\"connection\", (ws: WebSocket) => {\n // Send current board on connect\n const content = readBoardContent(boardPath);\n if (content !== null) {\n ws.send(content);\n }\n });\n\n try {\n fs.watch(resolvedBoardPath, { persistent: false }, () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n broadcastBoard();\n }, RENDER_WATCH_DEBOUNCE_MS);\n });\n } catch {\n // File might not exist yet; watcher will not run\n }\n\n server.on(\"error\", (err: Error & { code?: string }) => {\n process.stderr.write(`Render server error: ${err.message}\\n`);\n if (err.code === \"EADDRINUSE\") {\n process.stderr.write(`Port ${port} is in use. Try --port <number>.\\n`);\n }\n process.exitCode = 1;\n });\n\n server.listen(port, () => {\n const url = `http://localhost:${port}`;\n process.stdout.write(`Open ${url}\\n`);\n if (openBrowser) {\n import(\"open\").then(({ default: open }) => {\n open(url).catch(() => {});\n });\n }\n });\n\n const shutdown = () => {\n server.close(() => {\n process.exit(process.exitCode ?? 0);\n });\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n","import { startRenderServer } from \"../render-server.js\";\n\nexport function runRender(path: string, options: { port: number; open: boolean }): void {\n startRenderServer({\n boardPath: path,\n port: options.port,\n openBrowser: options.open,\n });\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { SPEC_FILENAME } from \"../constants.js\";\nimport { findPackageRoot } from \"../utils.js\";\n\nexport function runSpec(): void {\n const thisFile = fileURLToPath(import.meta.url);\n const startDir = path.dirname(thisFile);\n const packageRoot = findPackageRoot(startDir);\n if (!packageRoot) {\n process.stderr.write(\"statecraft spec: could not find package root\\n\");\n process.exitCode = 1;\n return;\n }\n const specPath = path.join(packageRoot, SPEC_FILENAME);\n if (!fs.existsSync(specPath)) {\n process.stderr.write(`statecraft spec: spec file not found at ${specPath}\\n`);\n process.exitCode = 1;\n return;\n }\n try {\n const content = fs.readFileSync(specPath, \"utf-8\");\n process.stdout.write(content);\n if (!content.endsWith(\"\\n\")) process.stdout.write(\"\\n\");\n process.exitCode = 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`statecraft spec: ${message}\\n`);\n process.exitCode = 1;\n }\n}\n","/**\n * Shared CLI utilities (path resolution, etc.).\n */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Walk up from startDir until a directory containing package.json is found.\n * @returns Absolute path to package root, or null if not found.\n */\nexport function findPackageRoot(startDir: string): string | null {\n let dir = path.resolve(startDir);\n for (;;) {\n const pkgPath = path.join(dir, \"package.json\");\n if (fs.existsSync(pkgPath)) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n","import { parseBoard, summarize } from \"@stcrft/statecraft-core\";\n\nexport function runSummarize(path: string): void {\n try {\n const board = parseBoard(path);\n const summary = summarize(board);\n process.stdout.write(summary);\n process.exitCode = 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(message + \"\\n\");\n process.exitCode = 1;\n }\n}\n","import { parseBoard, validate } from \"@stcrft/statecraft-core\";\n\nfunction formatValidationError(err: { message: string; path?: string }): string {\n const prefix = err.path != null && err.path !== \"\" ? `${err.path}: ` : \"\";\n return `${prefix}${err.message}`;\n}\n\nexport function runValidate(path: string): void {\n try {\n const board = parseBoard(path);\n const result = validate(board);\n if (!result.valid) {\n for (const err of result.errors) {\n process.stderr.write(formatValidationError(err) + \"\\n\");\n }\n process.exitCode = 1;\n return;\n }\n process.exitCode = 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(message + \"\\n\");\n process.exitCode = 1;\n }\n}\n"],"mappings":"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","names":["fs","path","__dirname","path","fs","path","fs","path","fileURLToPath","fs","path","fileURLToPath","path","fs","path","parseBoard","path","require","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stcrft/statecraft",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "CLI for Statecraft: validate, summarize, and render board-as-code",
5
5
  "type": "module",
6
6
  "repository": {
@@ -25,7 +25,7 @@
25
25
  "open": "^11.0.0",
26
26
  "ws": "^8.18.0",
27
27
  "yaml": "^2.8.2",
28
- "@stcrft/statecraft-core": "1.2.0"
28
+ "@stcrft/statecraft-core": "1.3.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/express": "^5.0.0",
package/spec.md CHANGED
@@ -32,9 +32,9 @@ This document defines the Statecraft board format. A single file describes one K
32
32
 
33
33
  | Column | Meaning for AI |
34
34
  |----------------|----------------|
35
- | **Backlog** | Not yet selected for immediate work. New tasks are created here. May be unrefined or unprioritized. |
36
- | **Ready** | Prepared and selected to be worked on next. Definition is clear (e.g. in spec file), dependencies met or N/A; safe to move to In Progress when capacity is free. |
37
- | **In Progress** | Currently being worked on. Move a task here when starting work; at most one (or a configurable WIP limit) at a time per column if enforced. |
35
+ | **Backlog** | Initial or vague task. New tasks are created here. May be unrefined—title and minimal spec are enough. After discovery/planning, the agent updates the task spec (description, acceptance criteria) and moves the task to Ready. |
36
+ | **Ready** | Task is refined: definition is clear, spec file has description and acceptance criteria, and the agent (or human) knows how to solve it. Ready to be worked on. Move here **after** discovery; move to In Progress **only when** starting the actual work. |
37
+ | **In Progress** | Currently being worked on (e.g. code, fix, implementation). Move a task here only when actively doing the work—not when still planning. At most one (or a configurable WIP limit) at a time per column if enforced. |
38
38
  | **Done** | Completed and accepted. Move here when the task’s acceptance criteria (in its spec file) are satisfied. |
39
39
 
40
40
  - **Order:** `Backlog` → `Ready` → `In Progress` → `Done` (left to right).
@@ -72,7 +72,7 @@ Statecraft is built around **CRUS** (Create, Read, Update, Summarize). AI agents
72
72
 
73
73
  - **Where:** New tasks are created with `status: Backlog`.
74
74
  - **What:** Add an entry under `tasks` with at least `title` and `status`. Use a stable, kebab-case **task id** (e.g. `fix-auth-timeout`). Optionally set `description`, `spec` (path to a `.md` file relative to the board directory), `owner`, `priority`, `depends_on`.
75
- - **Spec file:** If the task has acceptance criteria or context, create a markdown file (e.g. `tasks/<task-id>.md`) and set `spec: tasks/<task-id>.md`. Path is relative to the board file’s directory.
75
+ - **Spec file:** If the task has acceptance criteria or context, create a markdown file (e.g. `tasks/<task-id>.md`) and set `spec: tasks/<task-id>.md`. Path is relative to the board file’s directory. See **Task spec file format** below for recommended structure.
76
76
 
77
77
  ### Read
78
78
 
@@ -82,9 +82,11 @@ Statecraft is built around **CRUS** (Create, Read, Update, Summarize). AI agents
82
82
 
83
83
  ### Update
84
84
 
85
- - **How:** Edit the board YAML file (and, if needed, task spec files) directly. There are no separate move APIs; changing `status` is the update.
86
- - **Prepare for work:** When a task has a clear definition and dependencies are satisfied (or N/A), set `status` to `Ready`. Do not move to In Progress until the task is Ready (or skip Ready if the workflow is minimal).
87
- - **Start work:** Set the task’s `status` to `In Progress`. Optionally read the task’s `spec` file first.
85
+ - **How:** Edit the board YAML file (and, if needed, task spec files) directly. There are no separate "move" APIs; changing `status` is the update.
86
+ - **Flow:** Backlog Ready In Progress Done. Do not skip Ready.
87
+ - **Create (Backlog):** New tasks start in Backlog. They may be vague; add at least `title` and `status`, and create the spec file if required.
88
+ - **Refine and move to Ready:** After discovery or planning, update the task’s spec file (description, acceptance criteria). When the definition is clear and you know how to solve it, set `status` to **Ready**. Ready means "ready to be worked on."
89
+ - **Start work:** Only when you are about to do the actual work (code, fix, implementation), set the task’s `status` to **In Progress**. Do not move to In Progress while still in discovery or planning. Read the task’s `spec` file if needed.
88
90
  - **Finish work:** Set the task’s `status` to `Done` only when the task’s acceptance criteria (in its spec file, if present) are satisfied. Do not move to Done without meeting the definition of done.
89
91
  - **Create / change fields:** Add or edit `title`, `status`, `description`, `spec`, `owner`, `priority`, `depends_on` in the board file; create or edit the spec file when the task has one.
90
92
 
@@ -92,6 +94,25 @@ Statecraft is built around **CRUS** (Create, Read, Update, Summarize). AI agents
92
94
 
93
95
  - Completion is represented by moving tasks to **Done**, not by deleting them. Use `statecraft summarize` (or equivalent) to produce a short summary of the board (counts, task list, WIP, blocked). Summarize replaces “delete”: tasks are completed and reasoned about, not removed.
94
96
 
97
+
98
+ ## Task spec file format (best practices)
99
+
100
+ Task spec files (e.g. `tasks/<task-id>.md`) are markdown files referenced by a task's `spec` field. Following a consistent structure keeps specs machine-friendly and clear for both AI and humans.
101
+
102
+ **Recommended structure:**
103
+
104
+ | Section | Purpose |
105
+ |--------|---------|
106
+ | **Title** | First line or `# <Task title>` — match or expand the board `title`. |
107
+ | **Description** | Optional `## Description` with short context or problem statement. |
108
+ | **Acceptance criteria / Definition of Done** | `## Acceptance criteria` or `## Definition of Done` with a checklist (e.g. `- [ ] item`). All items must be checked before moving the task to Done. |
109
+ | **Notes / Dependencies** | Optional `## Notes`, `## Dependencies` for human context; `depends_on` in the board is the source of truth for ordering. |
110
+
111
+ - Use one file per task; put the task id in the filename (e.g. `fix-auth-timeout.md`).
112
+ - Path is relative to the board file's directory.
113
+
114
+ Init can optionally add these guidelines to generated AI rules (Cursor, Claude Code, Codex) so agents follow the same format.
115
+
95
116
  ---
96
117
 
97
118
  ## Example
@@ -153,7 +174,7 @@ These constraints are the basis for parser/validator implementations; this spec
153
174
 
154
175
  ## Setup and AI workflow
155
176
 
156
- **Creating a board:** Use **`statecraft init`** to create a board file and optionally connect Statecraft to your AI workflow. Init creates a board with the **canonical columns** (Backlog, Ready, In Progress, Done) and prompts for: board name, optional WIP limit for In Progress, board file path, directory for task `.md` files (relative to the board), and whether to generate rules for **Cursor**, **Claude Code**, and/or **Codex**. It writes valid board YAML and, if you choose, tool-specific rule files so your AI assistant knows where the board is and how to follow CRUS.
177
+ **Creating a board:** Use **`statecraft init`** to create a board file and optionally connect Statecraft to your AI workflow. Init creates a board with the **canonical columns** (Backlog, Ready, In Progress, Done) and prompts for: board name, optional WIP limit for In Progress, board file path, directory for task `.md` files (relative to the board), **workflow options** (enforce “create task before any work”, require each task to have a spec `.md` file, include task spec format guidelines in rules — all default to yes), and whether to generate rules for **Cursor**, **Claude Code**, and/or **Codex**. It writes valid board YAML and, if you choose, tool-specific rule files so your AI assistant knows where the board is and how to follow CRUS.
157
178
 
158
179
  **Generated rule files:**
159
180