@stcrft/statecraft 1.4.0 → 1.5.1

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
@@ -20,6 +20,7 @@ Then run `statecraft <command>`. Or use `npx @stcrft/statecraft init` (etc.) wit
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). |
22
22
  | **`statecraft render [path]`** | Serve the board in the browser (read-only UI). Options: `--port 3000` (default), `--open` (open browser). Starts a local server with `GET /api/board` and WebSocket for live updates when the file changes. |
23
+ | **`statecraft sync`** | Update generated rule files to the current template. Reads `.statecraft.json` if present; otherwise parses existing Cursor/Claude/Codex rule files. Overwrites generated content (back up if customized). `--dry-run` lists files that would be updated. |
23
24
 
24
25
  ## Upgrade notification
25
26
 
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { createRequire } from "module";
5
- import path6 from "path";
5
+ import path7 from "path";
6
6
  import { fileURLToPath as fileURLToPath3 } from "url";
7
7
  import { Command } from "commander";
8
8
 
@@ -17,6 +17,7 @@ var SPEC_FILENAME = "spec.md";
17
17
  var INIT_STRICT_MODE_DEFAULT = true;
18
18
  var INIT_REQUIRE_SPEC_FILE_DEFAULT = true;
19
19
  var INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT = true;
20
+ var STATECRAFT_CONFIG_FILENAME = ".statecraft.json";
20
21
 
21
22
  // src/executors/init.ts
22
23
  import fs from "fs";
@@ -203,7 +204,7 @@ function writeRuleFile(filePath, content, log) {
203
204
  ensureDirAndWrite(filePath, content);
204
205
  log(filePath);
205
206
  }
206
- var CODECX_MARKER = "## Statecraft (generated by statecraft init)";
207
+ var CODEX_MARKER = "## Statecraft (generated by statecraft init)";
207
208
  function writeCursorRule(cwd, boardPath, specDir, options, log) {
208
209
  const cursorRulesDir = path.resolve(cwd, ".cursor", "rules");
209
210
  const cursorRulePath = path.join(cursorRulesDir, "statecraft.mdc");
@@ -221,7 +222,7 @@ function writeCodexRule(cwd, boardPath, specDir, options, log) {
221
222
  const codexContent = buildCodexAgentsContent(boardPath, specDir, options);
222
223
  if (fs.existsSync(codexAgentsPath)) {
223
224
  const existing = fs.readFileSync(codexAgentsPath, "utf-8");
224
- if (existing.includes(CODECX_MARKER)) {
225
+ if (existing.includes(CODEX_MARKER)) {
225
226
  log("AGENTS.md already contains Statecraft section; skipped.");
226
227
  return;
227
228
  }
@@ -281,6 +282,22 @@ Created board at ${absolutePath}`);
281
282
  if (collected.generateCursorRule) writeCursorRule(cwd, collected.boardPath, specDir, ruleOptions, log);
282
283
  if (collected.generateClaudeRule) writeClaudeRule(cwd, collected.boardPath, specDir, ruleOptions, log);
283
284
  if (collected.generateCodexAgents) writeCodexRule(cwd, collected.boardPath, specDir, ruleOptions, log);
285
+ const anyRules = collected.generateCursorRule || collected.generateClaudeRule || collected.generateCodexAgents;
286
+ if (anyRules) {
287
+ const configPath = path.resolve(cwd, STATECRAFT_CONFIG_FILENAME);
288
+ const config = {
289
+ boardPath: collected.boardPath,
290
+ tasksDir: collected.tasksDir,
291
+ strictMode: collected.strictMode,
292
+ requireSpecFile: collected.requireSpecFile,
293
+ includeTaskSpecFormat: collected.includeTaskSpecFormat,
294
+ cursor: collected.generateCursorRule,
295
+ claude: collected.generateClaudeRule,
296
+ codex: collected.generateCodexAgents
297
+ };
298
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
299
+ log(`Wrote ${STATECRAFT_CONFIG_FILENAME}`);
300
+ }
284
301
  log("\nRun `statecraft validate " + collected.boardPath + "` to validate, or `statecraft render " + collected.boardPath + "` to view.");
285
302
  } catch (err) {
286
303
  const message = err instanceof Error ? err.message : String(err);
@@ -382,6 +399,7 @@ function startRenderServer(options) {
382
399
  }
383
400
  });
384
401
  let debounceTimer = null;
402
+ let boardWatcher = null;
385
403
  function broadcastBoard() {
386
404
  const content = readBoardContent(boardPath);
387
405
  const payload = content ?? "";
@@ -398,7 +416,7 @@ function startRenderServer(options) {
398
416
  }
399
417
  });
400
418
  try {
401
- fs2.watch(resolvedBoardPath, { persistent: false }, () => {
419
+ boardWatcher = fs2.watch(resolvedBoardPath, { persistent: false }, () => {
402
420
  if (debounceTimer) clearTimeout(debounceTimer);
403
421
  debounceTimer = setTimeout(() => {
404
422
  debounceTimer = null;
@@ -427,9 +445,23 @@ function startRenderServer(options) {
427
445
  });
428
446
  }
429
447
  });
448
+ let shuttingDown = false;
430
449
  const shutdown = () => {
431
- server.close(() => {
432
- process.exit(process.exitCode ?? 0);
450
+ if (shuttingDown) {
451
+ process.exit(1);
452
+ return;
453
+ }
454
+ shuttingDown = true;
455
+ if (debounceTimer) {
456
+ clearTimeout(debounceTimer);
457
+ debounceTimer = null;
458
+ }
459
+ boardWatcher?.close();
460
+ boardWatcher = null;
461
+ wss.close(() => {
462
+ server.close(() => {
463
+ process.exit(process.exitCode ?? 0);
464
+ });
433
465
  });
434
466
  };
435
467
  process.on("SIGINT", shutdown);
@@ -437,9 +469,9 @@ function startRenderServer(options) {
437
469
  }
438
470
 
439
471
  // src/executors/render.ts
440
- function runRender(path7, options) {
472
+ function runRender(path8, options) {
441
473
  startRenderServer({
442
- boardPath: path7,
474
+ boardPath: path8,
443
475
  port: options.port,
444
476
  openBrowser: options.open
445
477
  });
@@ -496,9 +528,9 @@ function runSpec() {
496
528
 
497
529
  // src/executors/summarize.ts
498
530
  import { parseBoard, summarize } from "@stcrft/statecraft-core";
499
- function runSummarize(path7) {
531
+ function runSummarize(path8) {
500
532
  try {
501
- const board = parseBoard(path7);
533
+ const board = parseBoard(path8);
502
534
  const summary = summarize(board);
503
535
  process.stdout.write(summary);
504
536
  process.exitCode = 0;
@@ -509,15 +541,182 @@ function runSummarize(path7) {
509
541
  }
510
542
  }
511
543
 
544
+ // src/executors/sync.ts
545
+ import fs5 from "fs";
546
+ import path5 from "path";
547
+ var BOARD_PATH_RE = /\*\*Board file:\*\*\s*`([^`]+)`/;
548
+ var TASKS_DIR_RE = /\*\*Task spec files:\*\*\s*`([^`]+)\/<task-id>\.md`/;
549
+ function ensureDirAndWrite2(filePath, content) {
550
+ const dir = path5.dirname(filePath);
551
+ if (!fs5.existsSync(dir)) {
552
+ fs5.mkdirSync(dir, { recursive: true });
553
+ }
554
+ fs5.writeFileSync(filePath, content, "utf-8");
555
+ }
556
+ function parseConfig(raw) {
557
+ try {
558
+ const data = JSON.parse(raw);
559
+ if (typeof data.boardPath !== "string" || typeof data.tasksDir !== "string" || typeof data.strictMode !== "boolean" || typeof data.requireSpecFile !== "boolean" || typeof data.includeTaskSpecFormat !== "boolean" || typeof data.cursor !== "boolean" || typeof data.claude !== "boolean" || typeof data.codex !== "boolean") {
560
+ return null;
561
+ }
562
+ return data;
563
+ } catch {
564
+ return null;
565
+ }
566
+ }
567
+ function parseBoardPathAndTasksDir(content) {
568
+ const boardMatch = content.match(BOARD_PATH_RE);
569
+ const tasksMatch = content.match(TASKS_DIR_RE);
570
+ if (!boardMatch || !tasksMatch) return null;
571
+ return { boardPath: boardMatch[1].trim(), tasksDir: tasksMatch[1].trim() };
572
+ }
573
+ function defaultRuleOptions() {
574
+ return {
575
+ strictMode: INIT_STRICT_MODE_DEFAULT,
576
+ requireSpecFile: INIT_REQUIRE_SPEC_FILE_DEFAULT,
577
+ includeTaskSpecFormat: INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT
578
+ };
579
+ }
580
+ function replaceCodexSectionInAgents(agentsPath, newSection) {
581
+ const existing = fs5.readFileSync(agentsPath, "utf-8");
582
+ const start = existing.indexOf(CODEX_MARKER);
583
+ if (start === -1) {
584
+ fs5.writeFileSync(agentsPath, existing.trimEnd() + "\n\n" + newSection.trim() + "\n", "utf-8");
585
+ return;
586
+ }
587
+ const afterMarker = start + CODEX_MARKER.length;
588
+ const nextH2 = existing.indexOf("\n## ", afterMarker);
589
+ const end = nextH2 === -1 ? existing.length : nextH2;
590
+ const rest = existing.slice(end);
591
+ const newFile = existing.slice(0, start) + newSection.trimEnd() + (rest ? "\n\n" + rest.trimStart() : "\n");
592
+ fs5.writeFileSync(agentsPath, newFile, "utf-8");
593
+ }
594
+ function runSync(options) {
595
+ const cwd = options?.cwd ?? process.cwd();
596
+ const dryRun = options?.dryRun ?? false;
597
+ const log = (msg) => process.stdout.write(msg + "\n");
598
+ const configPath = path5.resolve(cwd, STATECRAFT_CONFIG_FILENAME);
599
+ let config = null;
600
+ if (fs5.existsSync(configPath)) {
601
+ const raw = fs5.readFileSync(configPath, "utf-8");
602
+ config = parseConfig(raw);
603
+ if (!config) {
604
+ process.stderr.write(`${STATECRAFT_CONFIG_FILENAME} is invalid or missing required fields.
605
+ `);
606
+ process.exitCode = 1;
607
+ return;
608
+ }
609
+ }
610
+ let boardPath;
611
+ let tasksDir;
612
+ let ruleOptions;
613
+ let cursor;
614
+ let claude;
615
+ let codex;
616
+ if (config) {
617
+ boardPath = config.boardPath;
618
+ tasksDir = config.tasksDir;
619
+ ruleOptions = {
620
+ strictMode: config.strictMode,
621
+ requireSpecFile: config.requireSpecFile,
622
+ includeTaskSpecFormat: config.includeTaskSpecFormat
623
+ };
624
+ cursor = config.cursor;
625
+ claude = config.claude;
626
+ codex = config.codex;
627
+ } else {
628
+ const cursorPath = path5.resolve(cwd, ".cursor", "rules", "statecraft.mdc");
629
+ const claudePath = path5.resolve(cwd, ".claude", "rules", "statecraft.md");
630
+ const agentsPath = path5.resolve(cwd, "AGENTS.md");
631
+ let parsed = null;
632
+ if (fs5.existsSync(cursorPath)) {
633
+ parsed = parseBoardPathAndTasksDir(fs5.readFileSync(cursorPath, "utf-8"));
634
+ }
635
+ if (!parsed && fs5.existsSync(claudePath)) {
636
+ parsed = parseBoardPathAndTasksDir(fs5.readFileSync(claudePath, "utf-8"));
637
+ }
638
+ if (!parsed && fs5.existsSync(agentsPath)) {
639
+ const content = fs5.readFileSync(agentsPath, "utf-8");
640
+ if (content.includes(CODEX_MARKER)) parsed = parseBoardPathAndTasksDir(content);
641
+ }
642
+ if (!parsed) {
643
+ process.stderr.write("No Statecraft rule files found. Run `statecraft init` first.\n");
644
+ process.exitCode = 1;
645
+ return;
646
+ }
647
+ boardPath = parsed.boardPath;
648
+ tasksDir = parsed.tasksDir;
649
+ ruleOptions = defaultRuleOptions();
650
+ cursor = fs5.existsSync(cursorPath);
651
+ claude = fs5.existsSync(claudePath);
652
+ codex = fs5.existsSync(agentsPath) && fs5.readFileSync(agentsPath, "utf-8").includes(CODEX_MARKER);
653
+ }
654
+ const specDir = config ? path5.join(path5.dirname(boardPath), tasksDir) : tasksDir;
655
+ let updated = 0;
656
+ if (cursor) {
657
+ const cursorRulePath = path5.resolve(cwd, ".cursor", "rules", "statecraft.mdc");
658
+ const content = buildCursorRuleContent(boardPath, specDir, ruleOptions);
659
+ if (dryRun) {
660
+ log(`Would update ${path5.relative(cwd, cursorRulePath)}`);
661
+ } else {
662
+ ensureDirAndWrite2(cursorRulePath, content);
663
+ log(`Updated ${path5.relative(cwd, cursorRulePath)}`);
664
+ }
665
+ updated++;
666
+ }
667
+ if (claude) {
668
+ const claudeRulePath = path5.resolve(cwd, ".claude", "rules", "statecraft.md");
669
+ const content = buildClaudeRuleContent(boardPath, specDir, ruleOptions);
670
+ if (dryRun) {
671
+ log(`Would update ${path5.relative(cwd, claudeRulePath)}`);
672
+ } else {
673
+ ensureDirAndWrite2(claudeRulePath, content);
674
+ log(`Updated ${path5.relative(cwd, claudeRulePath)}`);
675
+ }
676
+ updated++;
677
+ }
678
+ if (codex) {
679
+ const agentsPath = path5.resolve(cwd, "AGENTS.md");
680
+ const content = buildCodexAgentsContent(boardPath, specDir, ruleOptions);
681
+ if (dryRun) {
682
+ log(`Would update Statecraft section in ${path5.relative(cwd, agentsPath)}`);
683
+ } else {
684
+ replaceCodexSectionInAgents(agentsPath, content);
685
+ log(`Updated Statecraft section in ${path5.relative(cwd, agentsPath)}`);
686
+ }
687
+ updated++;
688
+ }
689
+ if (updated === 0) {
690
+ process.stderr.write("No rule files to sync (cursor, claude, and codex are all false or no files found).\n");
691
+ process.exitCode = 1;
692
+ return;
693
+ }
694
+ if (!config && !dryRun) {
695
+ const tasksDirForConfig = path5.relative(path5.dirname(boardPath), specDir);
696
+ const newConfig = {
697
+ boardPath,
698
+ tasksDir: tasksDirForConfig,
699
+ strictMode: ruleOptions.strictMode,
700
+ requireSpecFile: ruleOptions.requireSpecFile,
701
+ includeTaskSpecFormat: ruleOptions.includeTaskSpecFormat,
702
+ cursor,
703
+ claude,
704
+ codex
705
+ };
706
+ fs5.writeFileSync(configPath, JSON.stringify(newConfig, null, 2) + "\n", "utf-8");
707
+ log(`Wrote ${STATECRAFT_CONFIG_FILENAME} for future syncs`);
708
+ }
709
+ }
710
+
512
711
  // src/executors/validate.ts
513
712
  import { parseBoard as parseBoard2, validate } from "@stcrft/statecraft-core";
514
713
  function formatValidationError(err) {
515
714
  const prefix = err.path != null && err.path !== "" ? `${err.path}: ` : "";
516
715
  return `${prefix}${err.message}`;
517
716
  }
518
- function runValidate(path7) {
717
+ function runValidate(path8) {
519
718
  try {
520
- const board = parseBoard2(path7);
719
+ const board = parseBoard2(path8);
521
720
  const result = validate(board);
522
721
  if (!result.valid) {
523
722
  for (const err of result.errors) {
@@ -535,9 +734,9 @@ function runValidate(path7) {
535
734
  }
536
735
 
537
736
  // src/update-check.ts
538
- import fs5 from "fs";
737
+ import fs6 from "fs";
539
738
  import os from "os";
540
- import path5 from "path";
739
+ import path6 from "path";
541
740
  import semver from "semver";
542
741
  var REGISTRY_URL = "https://registry.npmjs.org/@stcrft/statecraft/latest";
543
742
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -545,12 +744,12 @@ var FETCH_TIMEOUT_MS = 3e3;
545
744
  var CACHE_FILENAME = "statecraft-update-check.json";
546
745
  function getCachePath() {
547
746
  const tmp = os.tmpdir();
548
- return path5.join(tmp, CACHE_FILENAME);
747
+ return path6.join(tmp, CACHE_FILENAME);
549
748
  }
550
749
  function readCache() {
551
750
  try {
552
751
  const p = getCachePath();
553
- const raw = fs5.readFileSync(p, "utf-8");
752
+ const raw = fs6.readFileSync(p, "utf-8");
554
753
  const data = JSON.parse(raw);
555
754
  if (typeof data.lastCheck === "number" && typeof data.latestVersion === "string") {
556
755
  return data;
@@ -563,7 +762,7 @@ function writeCache(latestVersion) {
563
762
  try {
564
763
  const p = getCachePath();
565
764
  const data = { lastCheck: Date.now(), latestVersion };
566
- fs5.writeFileSync(p, JSON.stringify(data), "utf-8");
765
+ fs6.writeFileSync(p, JSON.stringify(data), "utf-8");
567
766
  } catch {
568
767
  }
569
768
  }
@@ -617,8 +816,8 @@ var require2 = createRequire(import.meta.url);
617
816
  var { version } = require2("../package.json");
618
817
  function isLocalDev() {
619
818
  try {
620
- const dir = path6.dirname(fileURLToPath3(import.meta.url));
621
- return /packages[/\\]cli[/\\]/.test(dir) || dir.includes("statecraft" + path6.sep + "packages" + path6.sep + "cli");
819
+ const dir = path7.dirname(fileURLToPath3(import.meta.url));
820
+ return /packages[/\\]cli[/\\]/.test(dir) || dir.includes("statecraft" + path7.sep + "packages" + path7.sep + "cli");
622
821
  } catch {
623
822
  return false;
624
823
  }
@@ -640,17 +839,20 @@ program.command("init").description("Interactive setup: create board and configu
640
839
  program.command("spec").description("Print the board format spec (for AI agents)").action(() => {
641
840
  runSpec();
642
841
  });
643
- program.command("validate").description("Validate a board file (exit 0 if valid, 1 on errors)").argument("[path]", "path to board file", DEFAULT_BOARD_PATH).argument("[extra...]", "ignored (only one path allowed)").action((path7, extra) => {
842
+ program.command("validate").description("Validate a board file (exit 0 if valid, 1 on errors)").argument("[path]", "path to board file", DEFAULT_BOARD_PATH).argument("[extra...]", "ignored (only one path allowed)").action((path8, extra) => {
644
843
  rejectMultiplePaths(extra);
645
- if (process.exitCode !== 1) runValidate(path7);
844
+ if (process.exitCode !== 1) runValidate(path8);
646
845
  });
647
- program.command("summarize").description("Print a short text summary of the board").argument("[path]", "path to board file", DEFAULT_BOARD_PATH).argument("[extra...]", "ignored (only one path allowed)").action((path7, extra) => {
846
+ program.command("summarize").description("Print a short text summary of the board").argument("[path]", "path to board file", DEFAULT_BOARD_PATH).argument("[extra...]", "ignored (only one path allowed)").action((path8, extra) => {
648
847
  rejectMultiplePaths(extra);
649
- if (process.exitCode !== 1) runSummarize(path7);
848
+ if (process.exitCode !== 1) runSummarize(path8);
650
849
  });
651
- program.command("render").description("Serve the board in the browser (read-only UI)").argument("[path]", "path to board file", DEFAULT_BOARD_PATH).option("-p, --port <number>", "port for the server", String(DEFAULT_RENDER_PORT)).option("--open", "open browser after starting server").action((path7, options) => {
850
+ program.command("render").description("Serve the board in the browser (read-only UI)").argument("[path]", "path to board file", DEFAULT_BOARD_PATH).option("-p, --port <number>", "port for the server", String(DEFAULT_RENDER_PORT)).option("--open", "open browser after starting server").action((path8, options) => {
652
851
  const port = parseInt(options.port, 10) || DEFAULT_RENDER_PORT;
653
- runRender(path7, { port, open: options.open ?? false });
852
+ runRender(path8, { port, open: options.open ?? false });
853
+ });
854
+ program.command("sync").description("Update generated rule files (Cursor, Claude, Codex) to the current template").option("--dry-run", "list files that would be updated without writing").action((options) => {
855
+ runSync({ dryRun: options.dryRun ?? false });
654
856
  });
655
857
  program.parseAsync().then(() => {
656
858
  runUpdateCheck(version, { isLocalDev: isLocalDev() });
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","../src/update-check.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { createRequire } from \"node:module\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport { DEFAULT_BOARD_PATH, DEFAULT_RENDER_PORT } from \"./constants.js\";\nimport { runInit, runRender, runSpec, runSummarize, runValidate } from \"./executors/index.js\";\nimport { runUpdateCheck } from \"./update-check.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../package.json\") as { version: string };\n\n/** True when running from monorepo (e.g. pnpm cli validate). */\nfunction isLocalDev(): boolean {\n try {\n const dir = path.dirname(fileURLToPath(import.meta.url));\n return /packages[/\\\\]cli[/\\\\]/.test(dir) || dir.includes(\"statecraft\" + path.sep + \"packages\" + path.sep + \"cli\");\n } catch {\n return false;\n }\n}\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().then(() => {\n runUpdateCheck(version, { isLocalDev: isLocalDev() });\n});\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","/**\n * Optional update check: notify users when a newer version of Statecraft is available.\n * Runs fire-and-forget after commands; uses a cache in the temp directory when writable.\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport semver from \"semver\";\n\nconst REGISTRY_URL = \"https://registry.npmjs.org/@stcrft/statecraft/latest\";\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst FETCH_TIMEOUT_MS = 3000;\nconst CACHE_FILENAME = \"statecraft-update-check.json\";\n\ninterface CacheShape {\n lastCheck: number;\n latestVersion: string;\n}\n\nfunction getCachePath(): string {\n const tmp = os.tmpdir();\n return path.join(tmp, CACHE_FILENAME);\n}\n\nfunction readCache(): CacheShape | null {\n try {\n const p = getCachePath();\n const raw = fs.readFileSync(p, \"utf-8\");\n const data = JSON.parse(raw) as CacheShape;\n if (typeof data.lastCheck === \"number\" && typeof data.latestVersion === \"string\") {\n return data;\n }\n } catch {\n // ignore\n }\n return null;\n}\n\nfunction writeCache(latestVersion: string): void {\n try {\n const p = getCachePath();\n const data: CacheShape = { lastCheck: Date.now(), latestVersion };\n fs.writeFileSync(p, JSON.stringify(data), \"utf-8\");\n } catch {\n // best-effort: do not fail if temp dir is read-only or other error\n }\n}\n\nasync function fetchLatestVersion(): Promise<string | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(REGISTRY_URL, { signal: controller.signal });\n clearTimeout(timeout);\n if (!res.ok) return null;\n const json = (await res.json()) as { version?: string };\n return typeof json.version === \"string\" ? json.version : null;\n } catch {\n clearTimeout(timeout);\n return null;\n }\n}\n\nfunction shouldSkip(options: { isLocalDev: boolean }): boolean {\n if (process.env.CI === \"true\" || process.env.CI === \"1\") return true;\n if (process.env.STATECRAFT_NO_UPDATE_CHECK === \"1\" || process.env.STATECRAFT_NO_UPDATE_CHECK === \"true\") return true;\n if (options.isLocalDev) return true;\n return false;\n}\n\n/**\n * Run the update check (fire-and-forget). Call after the command has finished.\n * Skips when CI, STATECRAFT_NO_UPDATE_CHECK, or running from local dev.\n * Uses cache in temp dir when writable; does not fail if cache write fails.\n */\nexport function runUpdateCheck(currentVersion: string, options: { isLocalDev: boolean }): void {\n if (shouldSkip(options)) return;\n\n void (async () => {\n let latest: string | null = null;\n const cached = readCache();\n if (cached && Date.now() - cached.lastCheck < CACHE_TTL_MS) {\n latest = cached.latestVersion;\n }\n if (latest === null) {\n latest = await fetchLatestVersion();\n if (latest !== null) writeCache(latest);\n }\n if (latest === null) return;\n try {\n if (semver.gt(latest, currentVersion)) {\n process.stderr.write(\n `A new version of Statecraft is available: ${latest} (you have ${currentVersion}). Upgrade: npm update -g @stcrft/statecraft\\n`\n );\n }\n } catch {\n // invalid semver or other compare error: do nothing\n }\n })();\n}\n"],"mappings":"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","names":["path","fileURLToPath","fs","path","__dirname","path","fs","path","fs","path","fileURLToPath","fs","path","fileURLToPath","path","fs","path","parseBoard","path","fs","path","require","path","fileURLToPath"]}
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/sync.ts","../src/executors/validate.ts","../src/update-check.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { createRequire } from \"node:module\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport { DEFAULT_BOARD_PATH, DEFAULT_RENDER_PORT } from \"./constants.js\";\nimport { runInit, runRender, runSpec, runSummarize, runSync, runValidate } from \"./executors/index.js\";\nimport { runUpdateCheck } from \"./update-check.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../package.json\") as { version: string };\n\n/** True when running from monorepo (e.g. pnpm cli validate). */\nfunction isLocalDev(): boolean {\n try {\n const dir = path.dirname(fileURLToPath(import.meta.url));\n return /packages[/\\\\]cli[/\\\\]/.test(dir) || dir.includes(\"statecraft\" + path.sep + \"packages\" + path.sep + \"cli\");\n } catch {\n return false;\n }\n}\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\n .command(\"sync\")\n .description(\"Update generated rule files (Cursor, Claude, Codex) to the current template\")\n .option(\"--dry-run\", \"list files that would be updated without writing\")\n .action((options: { dryRun?: boolean }) => {\n runSync({ dryRun: options.dryRun ?? false });\n });\n\nprogram.parseAsync().then(() => {\n runUpdateCheck(version, { isLocalDev: isLocalDev() });\n});\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\n/** Filename for Statecraft config written by init and read by sync (.statecraft.json). */\nexport const STATECRAFT_CONFIG_FILENAME = \".statecraft.json\";\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 STATECRAFT_CONFIG_FILENAME,\n} from \"../constants.js\";\nimport { buildStatecraftRuleBody, type RuleOptions } from \"./rule-content.js\";\n\n/** Config persisted by init and read by sync. */\nexport interface StatecraftConfig {\n boardPath: string;\n tasksDir: string;\n strictMode: boolean;\n requireSpecFile: boolean;\n includeTaskSpecFormat: boolean;\n cursor: boolean;\n claude: boolean;\n codex: boolean;\n}\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\n/** Section header in AGENTS.md for the Statecraft block; sync replaces from here to next ## or EOF. */\nexport const CODEX_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(CODEX_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 const anyRules = collected.generateCursorRule || collected.generateClaudeRule || collected.generateCodexAgents;\n if (anyRules) {\n const configPath = path.resolve(cwd, STATECRAFT_CONFIG_FILENAME);\n const config: StatecraftConfig = {\n boardPath: collected.boardPath,\n tasksDir: collected.tasksDir,\n strictMode: collected.strictMode,\n requireSpecFile: collected.requireSpecFile,\n includeTaskSpecFormat: collected.includeTaskSpecFormat,\n cursor: collected.generateCursorRule,\n claude: collected.generateClaudeRule,\n codex: collected.generateCodexAgents,\n };\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n log(`Wrote ${STATECRAFT_CONFIG_FILENAME}`);\n }\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 let boardWatcher: ReturnType<typeof fs.watch> | 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 boardWatcher = 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 let shuttingDown = false;\n const shutdown = () => {\n if (shuttingDown) {\n process.exit(1);\n return;\n }\n shuttingDown = true;\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n debounceTimer = null;\n }\n boardWatcher?.close();\n boardWatcher = null;\n wss.close(() => {\n server.close(() => {\n process.exit(process.exitCode ?? 0);\n });\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 fs from \"node:fs\";\nimport path from \"node:path\";\nimport {\n INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT,\n INIT_REQUIRE_SPEC_FILE_DEFAULT,\n INIT_STRICT_MODE_DEFAULT,\n STATECRAFT_CONFIG_FILENAME,\n} from \"../constants.js\";\nimport {\n buildClaudeRuleContent,\n buildCodexAgentsContent,\n buildCursorRuleContent,\n CODEX_MARKER,\n type StatecraftConfig,\n} from \"./init.js\";\nimport type { RuleOptions } from \"./rule-content.js\";\n\nconst BOARD_PATH_RE = /\\*\\*Board file:\\*\\*\\s*`([^`]+)`/;\nconst TASKS_DIR_RE = /\\*\\*Task spec files:\\*\\*\\s*`([^`]+)\\/<task-id>\\.md`/;\n\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\nfunction parseConfig(raw: string): StatecraftConfig | null {\n try {\n const data = JSON.parse(raw) as Record<string, unknown>;\n if (\n typeof data.boardPath !== \"string\" ||\n typeof data.tasksDir !== \"string\" ||\n typeof data.strictMode !== \"boolean\" ||\n typeof data.requireSpecFile !== \"boolean\" ||\n typeof data.includeTaskSpecFormat !== \"boolean\" ||\n typeof data.cursor !== \"boolean\" ||\n typeof data.claude !== \"boolean\" ||\n typeof data.codex !== \"boolean\"\n ) {\n return null;\n }\n return data as StatecraftConfig;\n } catch {\n return null;\n }\n}\n\nfunction parseBoardPathAndTasksDir(content: string): { boardPath: string; tasksDir: string } | null {\n const boardMatch = content.match(BOARD_PATH_RE);\n const tasksMatch = content.match(TASKS_DIR_RE);\n if (!boardMatch || !tasksMatch) return null;\n return { boardPath: boardMatch[1].trim(), tasksDir: tasksMatch[1].trim() };\n}\n\nfunction defaultRuleOptions(): RuleOptions {\n return {\n strictMode: INIT_STRICT_MODE_DEFAULT,\n requireSpecFile: INIT_REQUIRE_SPEC_FILE_DEFAULT,\n includeTaskSpecFormat: INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT,\n };\n}\n\nfunction replaceCodexSectionInAgents(agentsPath: string, newSection: string): void {\n const existing = fs.readFileSync(agentsPath, \"utf-8\");\n const start = existing.indexOf(CODEX_MARKER);\n if (start === -1) {\n fs.writeFileSync(agentsPath, existing.trimEnd() + \"\\n\\n\" + newSection.trim() + \"\\n\", \"utf-8\");\n return;\n }\n const afterMarker = start + CODEX_MARKER.length;\n const nextH2 = existing.indexOf(\"\\n## \", afterMarker);\n const end = nextH2 === -1 ? existing.length : nextH2;\n const rest = existing.slice(end);\n const newFile = existing.slice(0, start) + newSection.trimEnd() + (rest ? \"\\n\\n\" + rest.trimStart() : \"\\n\");\n fs.writeFileSync(agentsPath, newFile, \"utf-8\");\n}\n\nexport interface RunSyncOptions {\n cwd?: string;\n dryRun?: boolean;\n}\n\nexport function runSync(options?: RunSyncOptions): void {\n const cwd = options?.cwd ?? process.cwd();\n const dryRun = options?.dryRun ?? false;\n const log = (msg: string) => process.stdout.write(msg + \"\\n\");\n\n const configPath = path.resolve(cwd, STATECRAFT_CONFIG_FILENAME);\n let config: StatecraftConfig | null = null;\n if (fs.existsSync(configPath)) {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n config = parseConfig(raw);\n if (!config) {\n process.stderr.write(`${STATECRAFT_CONFIG_FILENAME} is invalid or missing required fields.\\n`);\n process.exitCode = 1;\n return;\n }\n }\n\n let boardPath: string;\n let tasksDir: string;\n let ruleOptions: RuleOptions;\n let cursor: boolean;\n let claude: boolean;\n let codex: boolean;\n\n if (config) {\n boardPath = config.boardPath;\n tasksDir = config.tasksDir;\n ruleOptions = {\n strictMode: config.strictMode,\n requireSpecFile: config.requireSpecFile,\n includeTaskSpecFormat: config.includeTaskSpecFormat,\n };\n cursor = config.cursor;\n claude = config.claude;\n codex = config.codex;\n } else {\n const cursorPath = path.resolve(cwd, \".cursor\", \"rules\", \"statecraft.mdc\");\n const claudePath = path.resolve(cwd, \".claude\", \"rules\", \"statecraft.md\");\n const agentsPath = path.resolve(cwd, \"AGENTS.md\");\n\n let parsed: { boardPath: string; tasksDir: string } | null = null;\n if (fs.existsSync(cursorPath)) {\n parsed = parseBoardPathAndTasksDir(fs.readFileSync(cursorPath, \"utf-8\"));\n }\n if (!parsed && fs.existsSync(claudePath)) {\n parsed = parseBoardPathAndTasksDir(fs.readFileSync(claudePath, \"utf-8\"));\n }\n if (!parsed && fs.existsSync(agentsPath)) {\n const content = fs.readFileSync(agentsPath, \"utf-8\");\n if (content.includes(CODEX_MARKER)) parsed = parseBoardPathAndTasksDir(content);\n }\n\n if (!parsed) {\n process.stderr.write(\"No Statecraft rule files found. Run `statecraft init` first.\\n\");\n process.exitCode = 1;\n return;\n }\n\n boardPath = parsed.boardPath;\n tasksDir = parsed.tasksDir; // full path as in rule (e.g. docs-private/tasks)\n ruleOptions = defaultRuleOptions();\n cursor = fs.existsSync(cursorPath);\n claude = fs.existsSync(claudePath);\n codex = fs.existsSync(agentsPath) && fs.readFileSync(agentsPath, \"utf-8\").includes(CODEX_MARKER);\n }\n\n const specDir = config\n ? path.join(path.dirname(boardPath), tasksDir)\n : tasksDir;\n let updated = 0;\n\n if (cursor) {\n const cursorRulePath = path.resolve(cwd, \".cursor\", \"rules\", \"statecraft.mdc\");\n const content = buildCursorRuleContent(boardPath, specDir, ruleOptions);\n if (dryRun) {\n log(`Would update ${path.relative(cwd, cursorRulePath)}`);\n } else {\n ensureDirAndWrite(cursorRulePath, content);\n log(`Updated ${path.relative(cwd, cursorRulePath)}`);\n }\n updated++;\n }\n\n if (claude) {\n const claudeRulePath = path.resolve(cwd, \".claude\", \"rules\", \"statecraft.md\");\n const content = buildClaudeRuleContent(boardPath, specDir, ruleOptions);\n if (dryRun) {\n log(`Would update ${path.relative(cwd, claudeRulePath)}`);\n } else {\n ensureDirAndWrite(claudeRulePath, content);\n log(`Updated ${path.relative(cwd, claudeRulePath)}`);\n }\n updated++;\n }\n\n if (codex) {\n const agentsPath = path.resolve(cwd, \"AGENTS.md\");\n const content = buildCodexAgentsContent(boardPath, specDir, ruleOptions);\n if (dryRun) {\n log(`Would update Statecraft section in ${path.relative(cwd, agentsPath)}`);\n } else {\n replaceCodexSectionInAgents(agentsPath, content);\n log(`Updated Statecraft section in ${path.relative(cwd, agentsPath)}`);\n }\n updated++;\n }\n\n if (updated === 0) {\n process.stderr.write(\"No rule files to sync (cursor, claude, and codex are all false or no files found).\\n\");\n process.exitCode = 1;\n return;\n }\n\n if (!config && !dryRun) {\n const tasksDirForConfig = path.relative(path.dirname(boardPath), specDir);\n const newConfig: StatecraftConfig = {\n boardPath,\n tasksDir: tasksDirForConfig,\n strictMode: ruleOptions.strictMode,\n requireSpecFile: ruleOptions.requireSpecFile,\n includeTaskSpecFormat: ruleOptions.includeTaskSpecFormat,\n cursor,\n claude,\n codex,\n };\n fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2) + \"\\n\", \"utf-8\");\n log(`Wrote ${STATECRAFT_CONFIG_FILENAME} for future syncs`);\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","/**\n * Optional update check: notify users when a newer version of Statecraft is available.\n * Runs fire-and-forget after commands; uses a cache in the temp directory when writable.\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport semver from \"semver\";\n\nconst REGISTRY_URL = \"https://registry.npmjs.org/@stcrft/statecraft/latest\";\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst FETCH_TIMEOUT_MS = 3000;\nconst CACHE_FILENAME = \"statecraft-update-check.json\";\n\ninterface CacheShape {\n lastCheck: number;\n latestVersion: string;\n}\n\nfunction getCachePath(): string {\n const tmp = os.tmpdir();\n return path.join(tmp, CACHE_FILENAME);\n}\n\nfunction readCache(): CacheShape | null {\n try {\n const p = getCachePath();\n const raw = fs.readFileSync(p, \"utf-8\");\n const data = JSON.parse(raw) as CacheShape;\n if (typeof data.lastCheck === \"number\" && typeof data.latestVersion === \"string\") {\n return data;\n }\n } catch {\n // ignore\n }\n return null;\n}\n\nfunction writeCache(latestVersion: string): void {\n try {\n const p = getCachePath();\n const data: CacheShape = { lastCheck: Date.now(), latestVersion };\n fs.writeFileSync(p, JSON.stringify(data), \"utf-8\");\n } catch {\n // best-effort: do not fail if temp dir is read-only or other error\n }\n}\n\nasync function fetchLatestVersion(): Promise<string | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(REGISTRY_URL, { signal: controller.signal });\n clearTimeout(timeout);\n if (!res.ok) return null;\n const json = (await res.json()) as { version?: string };\n return typeof json.version === \"string\" ? json.version : null;\n } catch {\n clearTimeout(timeout);\n return null;\n }\n}\n\nfunction shouldSkip(options: { isLocalDev: boolean }): boolean {\n if (process.env.CI === \"true\" || process.env.CI === \"1\") return true;\n if (process.env.STATECRAFT_NO_UPDATE_CHECK === \"1\" || process.env.STATECRAFT_NO_UPDATE_CHECK === \"true\") return true;\n if (options.isLocalDev) return true;\n return false;\n}\n\n/**\n * Run the update check (fire-and-forget). Call after the command has finished.\n * Skips when CI, STATECRAFT_NO_UPDATE_CHECK, or running from local dev.\n * Uses cache in temp dir when writable; does not fail if cache write fails.\n */\nexport function runUpdateCheck(currentVersion: string, options: { isLocalDev: boolean }): void {\n if (shouldSkip(options)) return;\n\n void (async () => {\n let latest: string | null = null;\n const cached = readCache();\n if (cached && Date.now() - cached.lastCheck < CACHE_TTL_MS) {\n latest = cached.latestVersion;\n }\n if (latest === null) {\n latest = await fetchLatestVersion();\n if (latest !== null) writeCache(latest);\n }\n if (latest === null) return;\n try {\n if (semver.gt(latest, currentVersion)) {\n process.stderr.write(\n `A new version of Statecraft is available: ${latest} (you have ${currentVersion}). Upgrade: npm update -g @stcrft/statecraft\\n`\n );\n }\n } catch {\n // invalid semver or other compare error: do nothing\n }\n })();\n}\n"],"mappings":"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","names":["path","fileURLToPath","fs","path","__dirname","path","fs","path","fs","path","fileURLToPath","fs","path","fileURLToPath","path","fs","path","fs","path","ensureDirAndWrite","path","fs","parseBoard","path","fs","path","require","path","fileURLToPath"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stcrft/statecraft",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "CLI for Statecraft: validate, summarize, and render board-as-code",
5
5
  "type": "module",
6
6
  "repository": {
@@ -26,7 +26,7 @@
26
26
  "ws": "^8.18.0",
27
27
  "yaml": "^2.8.2",
28
28
  "semver": "^7.6.3",
29
- "@stcrft/statecraft-core": "1.4.0"
29
+ "@stcrft/statecraft-core": "1.5.1"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/express": "^5.0.0",
package/spec.md CHANGED
@@ -174,7 +174,7 @@ These constraints are the basis for parser/validator implementations; this spec
174
174
 
175
175
  ## Setup and AI workflow
176
176
 
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.
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. When init generates any rule file, it also writes **`.statecraft.json`** in the project root (board path, tasks dir, and options); you can commit it or add to `.gitignore`. Run **`statecraft sync`** to refresh generated rule files to the current template after upgrading Statecraft; sync overwrites generated content (back up customizations first).
178
178
 
179
179
  **Generated rule files:**
180
180