@stcrft/statecraft 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,6 +20,11 @@ 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. |
24
+
25
+ ## Upgrade notification
26
+
27
+ After each command, the CLI may check npm for a newer version and print a one-line message to stderr if an upgrade is available. To disable: set `STATECRAFT_NO_UPDATE_CHECK=1` or `CI=true`. When running from the repo (e.g. `pnpm cli validate`), the check is skipped. A cache in the system temp directory is used when writable (writes are best-effort and do not fail the command).
23
28
 
24
29
  ## Path handling
25
30
 
package/dist/index.js CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { createRequire } from "module";
5
+ import path7 from "path";
6
+ import { fileURLToPath as fileURLToPath3 } from "url";
5
7
  import { Command } from "commander";
6
8
 
7
9
  // src/constants.ts
@@ -15,6 +17,7 @@ var SPEC_FILENAME = "spec.md";
15
17
  var INIT_STRICT_MODE_DEFAULT = true;
16
18
  var INIT_REQUIRE_SPEC_FILE_DEFAULT = true;
17
19
  var INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT = true;
20
+ var STATECRAFT_CONFIG_FILENAME = ".statecraft.json";
18
21
 
19
22
  // src/executors/init.ts
20
23
  import fs from "fs";
@@ -201,7 +204,7 @@ function writeRuleFile(filePath, content, log) {
201
204
  ensureDirAndWrite(filePath, content);
202
205
  log(filePath);
203
206
  }
204
- var CODECX_MARKER = "## Statecraft (generated by statecraft init)";
207
+ var CODEX_MARKER = "## Statecraft (generated by statecraft init)";
205
208
  function writeCursorRule(cwd, boardPath, specDir, options, log) {
206
209
  const cursorRulesDir = path.resolve(cwd, ".cursor", "rules");
207
210
  const cursorRulePath = path.join(cursorRulesDir, "statecraft.mdc");
@@ -219,7 +222,7 @@ function writeCodexRule(cwd, boardPath, specDir, options, log) {
219
222
  const codexContent = buildCodexAgentsContent(boardPath, specDir, options);
220
223
  if (fs.existsSync(codexAgentsPath)) {
221
224
  const existing = fs.readFileSync(codexAgentsPath, "utf-8");
222
- if (existing.includes(CODECX_MARKER)) {
225
+ if (existing.includes(CODEX_MARKER)) {
223
226
  log("AGENTS.md already contains Statecraft section; skipped.");
224
227
  return;
225
228
  }
@@ -279,6 +282,22 @@ Created board at ${absolutePath}`);
279
282
  if (collected.generateCursorRule) writeCursorRule(cwd, collected.boardPath, specDir, ruleOptions, log);
280
283
  if (collected.generateClaudeRule) writeClaudeRule(cwd, collected.boardPath, specDir, ruleOptions, log);
281
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
+ }
282
301
  log("\nRun `statecraft validate " + collected.boardPath + "` to validate, or `statecraft render " + collected.boardPath + "` to view.");
283
302
  } catch (err) {
284
303
  const message = err instanceof Error ? err.message : String(err);
@@ -435,9 +454,9 @@ function startRenderServer(options) {
435
454
  }
436
455
 
437
456
  // src/executors/render.ts
438
- function runRender(path5, options) {
457
+ function runRender(path8, options) {
439
458
  startRenderServer({
440
- boardPath: path5,
459
+ boardPath: path8,
441
460
  port: options.port,
442
461
  openBrowser: options.open
443
462
  });
@@ -494,9 +513,9 @@ function runSpec() {
494
513
 
495
514
  // src/executors/summarize.ts
496
515
  import { parseBoard, summarize } from "@stcrft/statecraft-core";
497
- function runSummarize(path5) {
516
+ function runSummarize(path8) {
498
517
  try {
499
- const board = parseBoard(path5);
518
+ const board = parseBoard(path8);
500
519
  const summary = summarize(board);
501
520
  process.stdout.write(summary);
502
521
  process.exitCode = 0;
@@ -507,15 +526,182 @@ function runSummarize(path5) {
507
526
  }
508
527
  }
509
528
 
529
+ // src/executors/sync.ts
530
+ import fs5 from "fs";
531
+ import path5 from "path";
532
+ var BOARD_PATH_RE = /\*\*Board file:\*\*\s*`([^`]+)`/;
533
+ var TASKS_DIR_RE = /\*\*Task spec files:\*\*\s*`([^`]+)\/<task-id>\.md`/;
534
+ function ensureDirAndWrite2(filePath, content) {
535
+ const dir = path5.dirname(filePath);
536
+ if (!fs5.existsSync(dir)) {
537
+ fs5.mkdirSync(dir, { recursive: true });
538
+ }
539
+ fs5.writeFileSync(filePath, content, "utf-8");
540
+ }
541
+ function parseConfig(raw) {
542
+ try {
543
+ const data = JSON.parse(raw);
544
+ 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") {
545
+ return null;
546
+ }
547
+ return data;
548
+ } catch {
549
+ return null;
550
+ }
551
+ }
552
+ function parseBoardPathAndTasksDir(content) {
553
+ const boardMatch = content.match(BOARD_PATH_RE);
554
+ const tasksMatch = content.match(TASKS_DIR_RE);
555
+ if (!boardMatch || !tasksMatch) return null;
556
+ return { boardPath: boardMatch[1].trim(), tasksDir: tasksMatch[1].trim() };
557
+ }
558
+ function defaultRuleOptions() {
559
+ return {
560
+ strictMode: INIT_STRICT_MODE_DEFAULT,
561
+ requireSpecFile: INIT_REQUIRE_SPEC_FILE_DEFAULT,
562
+ includeTaskSpecFormat: INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT
563
+ };
564
+ }
565
+ function replaceCodexSectionInAgents(agentsPath, newSection) {
566
+ const existing = fs5.readFileSync(agentsPath, "utf-8");
567
+ const start = existing.indexOf(CODEX_MARKER);
568
+ if (start === -1) {
569
+ fs5.writeFileSync(agentsPath, existing.trimEnd() + "\n\n" + newSection.trim() + "\n", "utf-8");
570
+ return;
571
+ }
572
+ const afterMarker = start + CODEX_MARKER.length;
573
+ const nextH2 = existing.indexOf("\n## ", afterMarker);
574
+ const end = nextH2 === -1 ? existing.length : nextH2;
575
+ const rest = existing.slice(end);
576
+ const newFile = existing.slice(0, start) + newSection.trimEnd() + (rest ? "\n\n" + rest.trimStart() : "\n");
577
+ fs5.writeFileSync(agentsPath, newFile, "utf-8");
578
+ }
579
+ function runSync(options) {
580
+ const cwd = options?.cwd ?? process.cwd();
581
+ const dryRun = options?.dryRun ?? false;
582
+ const log = (msg) => process.stdout.write(msg + "\n");
583
+ const configPath = path5.resolve(cwd, STATECRAFT_CONFIG_FILENAME);
584
+ let config = null;
585
+ if (fs5.existsSync(configPath)) {
586
+ const raw = fs5.readFileSync(configPath, "utf-8");
587
+ config = parseConfig(raw);
588
+ if (!config) {
589
+ process.stderr.write(`${STATECRAFT_CONFIG_FILENAME} is invalid or missing required fields.
590
+ `);
591
+ process.exitCode = 1;
592
+ return;
593
+ }
594
+ }
595
+ let boardPath;
596
+ let tasksDir;
597
+ let ruleOptions;
598
+ let cursor;
599
+ let claude;
600
+ let codex;
601
+ if (config) {
602
+ boardPath = config.boardPath;
603
+ tasksDir = config.tasksDir;
604
+ ruleOptions = {
605
+ strictMode: config.strictMode,
606
+ requireSpecFile: config.requireSpecFile,
607
+ includeTaskSpecFormat: config.includeTaskSpecFormat
608
+ };
609
+ cursor = config.cursor;
610
+ claude = config.claude;
611
+ codex = config.codex;
612
+ } else {
613
+ const cursorPath = path5.resolve(cwd, ".cursor", "rules", "statecraft.mdc");
614
+ const claudePath = path5.resolve(cwd, ".claude", "rules", "statecraft.md");
615
+ const agentsPath = path5.resolve(cwd, "AGENTS.md");
616
+ let parsed = null;
617
+ if (fs5.existsSync(cursorPath)) {
618
+ parsed = parseBoardPathAndTasksDir(fs5.readFileSync(cursorPath, "utf-8"));
619
+ }
620
+ if (!parsed && fs5.existsSync(claudePath)) {
621
+ parsed = parseBoardPathAndTasksDir(fs5.readFileSync(claudePath, "utf-8"));
622
+ }
623
+ if (!parsed && fs5.existsSync(agentsPath)) {
624
+ const content = fs5.readFileSync(agentsPath, "utf-8");
625
+ if (content.includes(CODEX_MARKER)) parsed = parseBoardPathAndTasksDir(content);
626
+ }
627
+ if (!parsed) {
628
+ process.stderr.write("No Statecraft rule files found. Run `statecraft init` first.\n");
629
+ process.exitCode = 1;
630
+ return;
631
+ }
632
+ boardPath = parsed.boardPath;
633
+ tasksDir = parsed.tasksDir;
634
+ ruleOptions = defaultRuleOptions();
635
+ cursor = fs5.existsSync(cursorPath);
636
+ claude = fs5.existsSync(claudePath);
637
+ codex = fs5.existsSync(agentsPath) && fs5.readFileSync(agentsPath, "utf-8").includes(CODEX_MARKER);
638
+ }
639
+ const specDir = config ? path5.join(path5.dirname(boardPath), tasksDir) : tasksDir;
640
+ let updated = 0;
641
+ if (cursor) {
642
+ const cursorRulePath = path5.resolve(cwd, ".cursor", "rules", "statecraft.mdc");
643
+ const content = buildCursorRuleContent(boardPath, specDir, ruleOptions);
644
+ if (dryRun) {
645
+ log(`Would update ${path5.relative(cwd, cursorRulePath)}`);
646
+ } else {
647
+ ensureDirAndWrite2(cursorRulePath, content);
648
+ log(`Updated ${path5.relative(cwd, cursorRulePath)}`);
649
+ }
650
+ updated++;
651
+ }
652
+ if (claude) {
653
+ const claudeRulePath = path5.resolve(cwd, ".claude", "rules", "statecraft.md");
654
+ const content = buildClaudeRuleContent(boardPath, specDir, ruleOptions);
655
+ if (dryRun) {
656
+ log(`Would update ${path5.relative(cwd, claudeRulePath)}`);
657
+ } else {
658
+ ensureDirAndWrite2(claudeRulePath, content);
659
+ log(`Updated ${path5.relative(cwd, claudeRulePath)}`);
660
+ }
661
+ updated++;
662
+ }
663
+ if (codex) {
664
+ const agentsPath = path5.resolve(cwd, "AGENTS.md");
665
+ const content = buildCodexAgentsContent(boardPath, specDir, ruleOptions);
666
+ if (dryRun) {
667
+ log(`Would update Statecraft section in ${path5.relative(cwd, agentsPath)}`);
668
+ } else {
669
+ replaceCodexSectionInAgents(agentsPath, content);
670
+ log(`Updated Statecraft section in ${path5.relative(cwd, agentsPath)}`);
671
+ }
672
+ updated++;
673
+ }
674
+ if (updated === 0) {
675
+ process.stderr.write("No rule files to sync (cursor, claude, and codex are all false or no files found).\n");
676
+ process.exitCode = 1;
677
+ return;
678
+ }
679
+ if (!config && !dryRun) {
680
+ const tasksDirForConfig = path5.relative(path5.dirname(boardPath), specDir);
681
+ const newConfig = {
682
+ boardPath,
683
+ tasksDir: tasksDirForConfig,
684
+ strictMode: ruleOptions.strictMode,
685
+ requireSpecFile: ruleOptions.requireSpecFile,
686
+ includeTaskSpecFormat: ruleOptions.includeTaskSpecFormat,
687
+ cursor,
688
+ claude,
689
+ codex
690
+ };
691
+ fs5.writeFileSync(configPath, JSON.stringify(newConfig, null, 2) + "\n", "utf-8");
692
+ log(`Wrote ${STATECRAFT_CONFIG_FILENAME} for future syncs`);
693
+ }
694
+ }
695
+
510
696
  // src/executors/validate.ts
511
697
  import { parseBoard as parseBoard2, validate } from "@stcrft/statecraft-core";
512
698
  function formatValidationError(err) {
513
699
  const prefix = err.path != null && err.path !== "" ? `${err.path}: ` : "";
514
700
  return `${prefix}${err.message}`;
515
701
  }
516
- function runValidate(path5) {
702
+ function runValidate(path8) {
517
703
  try {
518
- const board = parseBoard2(path5);
704
+ const board = parseBoard2(path8);
519
705
  const result = validate(board);
520
706
  if (!result.valid) {
521
707
  for (const err of result.errors) {
@@ -532,9 +718,95 @@ function runValidate(path5) {
532
718
  }
533
719
  }
534
720
 
721
+ // src/update-check.ts
722
+ import fs6 from "fs";
723
+ import os from "os";
724
+ import path6 from "path";
725
+ import semver from "semver";
726
+ var REGISTRY_URL = "https://registry.npmjs.org/@stcrft/statecraft/latest";
727
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
728
+ var FETCH_TIMEOUT_MS = 3e3;
729
+ var CACHE_FILENAME = "statecraft-update-check.json";
730
+ function getCachePath() {
731
+ const tmp = os.tmpdir();
732
+ return path6.join(tmp, CACHE_FILENAME);
733
+ }
734
+ function readCache() {
735
+ try {
736
+ const p = getCachePath();
737
+ const raw = fs6.readFileSync(p, "utf-8");
738
+ const data = JSON.parse(raw);
739
+ if (typeof data.lastCheck === "number" && typeof data.latestVersion === "string") {
740
+ return data;
741
+ }
742
+ } catch {
743
+ }
744
+ return null;
745
+ }
746
+ function writeCache(latestVersion) {
747
+ try {
748
+ const p = getCachePath();
749
+ const data = { lastCheck: Date.now(), latestVersion };
750
+ fs6.writeFileSync(p, JSON.stringify(data), "utf-8");
751
+ } catch {
752
+ }
753
+ }
754
+ async function fetchLatestVersion() {
755
+ const controller = new AbortController();
756
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
757
+ try {
758
+ const res = await fetch(REGISTRY_URL, { signal: controller.signal });
759
+ clearTimeout(timeout);
760
+ if (!res.ok) return null;
761
+ const json = await res.json();
762
+ return typeof json.version === "string" ? json.version : null;
763
+ } catch {
764
+ clearTimeout(timeout);
765
+ return null;
766
+ }
767
+ }
768
+ function shouldSkip(options) {
769
+ if (process.env.CI === "true" || process.env.CI === "1") return true;
770
+ if (process.env.STATECRAFT_NO_UPDATE_CHECK === "1" || process.env.STATECRAFT_NO_UPDATE_CHECK === "true") return true;
771
+ if (options.isLocalDev) return true;
772
+ return false;
773
+ }
774
+ function runUpdateCheck(currentVersion, options) {
775
+ if (shouldSkip(options)) return;
776
+ void (async () => {
777
+ let latest = null;
778
+ const cached = readCache();
779
+ if (cached && Date.now() - cached.lastCheck < CACHE_TTL_MS) {
780
+ latest = cached.latestVersion;
781
+ }
782
+ if (latest === null) {
783
+ latest = await fetchLatestVersion();
784
+ if (latest !== null) writeCache(latest);
785
+ }
786
+ if (latest === null) return;
787
+ try {
788
+ if (semver.gt(latest, currentVersion)) {
789
+ process.stderr.write(
790
+ `A new version of Statecraft is available: ${latest} (you have ${currentVersion}). Upgrade: npm update -g @stcrft/statecraft
791
+ `
792
+ );
793
+ }
794
+ } catch {
795
+ }
796
+ })();
797
+ }
798
+
535
799
  // src/index.ts
536
800
  var require2 = createRequire(import.meta.url);
537
801
  var { version } = require2("../package.json");
802
+ function isLocalDev() {
803
+ try {
804
+ const dir = path7.dirname(fileURLToPath3(import.meta.url));
805
+ return /packages[/\\]cli[/\\]/.test(dir) || dir.includes("statecraft" + path7.sep + "packages" + path7.sep + "cli");
806
+ } catch {
807
+ return false;
808
+ }
809
+ }
538
810
  function rejectMultiplePaths(extra) {
539
811
  if (extra.length > 0) {
540
812
  process.stderr.write("Only one board file per run. Multiple paths are not supported.\n");
@@ -552,17 +824,22 @@ program.command("init").description("Interactive setup: create board and configu
552
824
  program.command("spec").description("Print the board format spec (for AI agents)").action(() => {
553
825
  runSpec();
554
826
  });
555
- 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((path5, extra) => {
827
+ 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) => {
556
828
  rejectMultiplePaths(extra);
557
- if (process.exitCode !== 1) runValidate(path5);
829
+ if (process.exitCode !== 1) runValidate(path8);
558
830
  });
559
- 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((path5, extra) => {
831
+ 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) => {
560
832
  rejectMultiplePaths(extra);
561
- if (process.exitCode !== 1) runSummarize(path5);
833
+ if (process.exitCode !== 1) runSummarize(path8);
562
834
  });
563
- 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((path5, options) => {
835
+ 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) => {
564
836
  const port = parseInt(options.port, 10) || DEFAULT_RENDER_PORT;
565
- runRender(path5, { port, open: options.open ?? false });
837
+ runRender(path8, { port, open: options.open ?? false });
838
+ });
839
+ 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) => {
840
+ runSync({ dryRun: options.dryRun ?? false });
841
+ });
842
+ program.parseAsync().then(() => {
843
+ runUpdateCheck(version, { isLocalDev: isLocalDev() });
566
844
  });
567
- program.parseAsync();
568
845
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/executors/init.ts","../src/executors/rule-content.ts","../src/render-server.ts","../src/executors/render.ts","../src/executors/spec.ts","../src/utils.ts","../src/executors/summarize.ts","../src/executors/validate.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { DEFAULT_BOARD_PATH, DEFAULT_RENDER_PORT } from \"./constants.js\";\nimport { runInit, runRender, runSpec, runSummarize, runValidate } from \"./executors/index.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../package.json\") as { version: string };\n\nfunction rejectMultiplePaths(extra: string[]): void {\n if (extra.length > 0) {\n process.stderr.write(\"Only one board file per run. Multiple paths are not supported.\\n\");\n process.exitCode = 1;\n }\n}\n\nconst program = new Command();\n\nprogram\n .name(\"statecraft\")\n .description(\"Validate, summarize, and render Statecraft board files\")\n .version(version);\n\nprogram\n .command(\"init\")\n .description(\"Interactive setup: create board and configure Statecraft for your workflow\")\n .action(async () => {\n await runInit().catch((err) => {\n process.stderr.write(err instanceof Error ? err.message : String(err) + \"\\n\");\n process.exitCode = 1;\n });\n });\n\nprogram\n .command(\"spec\")\n .description(\"Print the board format spec (for AI agents)\")\n .action(() => {\n runSpec();\n });\n\nprogram\n .command(\"validate\")\n .description(\"Validate a board file (exit 0 if valid, 1 on errors)\")\n .argument(\"[path]\", \"path to board file\", DEFAULT_BOARD_PATH)\n .argument(\"[extra...]\", \"ignored (only one path allowed)\")\n .action((path: string, extra: string[]) => {\n rejectMultiplePaths(extra);\n if (process.exitCode !== 1) runValidate(path);\n });\n\nprogram\n .command(\"summarize\")\n .description(\"Print a short text summary of the board\")\n .argument(\"[path]\", \"path to board file\", DEFAULT_BOARD_PATH)\n .argument(\"[extra...]\", \"ignored (only one path allowed)\")\n .action((path: string, extra: string[]) => {\n rejectMultiplePaths(extra);\n if (process.exitCode !== 1) runSummarize(path);\n });\n\nprogram\n .command(\"render\")\n .description(\"Serve the board in the browser (read-only UI)\")\n .argument(\"[path]\", \"path to board file\", DEFAULT_BOARD_PATH)\n .option(\"-p, --port <number>\", \"port for the server\", String(DEFAULT_RENDER_PORT))\n .option(\"--open\", \"open browser after starting server\")\n .action((path: string, options: { port: string; open: boolean }) => {\n const port = parseInt(options.port, 10) || DEFAULT_RENDER_PORT;\n runRender(path, { port, open: options.open ?? false });\n });\n\nprogram.parseAsync();\n","/** Default path to the board file when none is given (validate, summarize, render). */\nexport const DEFAULT_BOARD_PATH = \"./board.yaml\";\n\n/** Default port for the render server. */\nexport const DEFAULT_RENDER_PORT = 3000;\n\n/** Debounce delay (ms) for file watcher before broadcasting board updates to WebSocket clients. */\nexport const RENDER_WATCH_DEBOUNCE_MS = 100;\n\n/** Default board file path offered by init (cwd). */\nexport const INIT_DEFAULT_BOARD_PATH = \"board.yaml\";\n\n/** Default directory for task .md files (relative to board), offered by init. */\nexport const INIT_DEFAULT_TASKS_DIR = \"tasks\";\n\n/** Canonical column set per spec; init creates boards with these columns. */\nexport const CANONICAL_COLUMNS = [\"Backlog\", \"Ready\", \"In Progress\", \"Done\"] as const;\n\n/** Filename of the board format spec shipped with the CLI package (statecraft spec). */\nexport const SPEC_FILENAME = \"spec.md\";\n\n/** Init default: enforce \"create task before work\" workflow for all agents. */\nexport const INIT_STRICT_MODE_DEFAULT = true;\n\n/** Init default: require each task to have a spec .md file in the tasks directory. */\nexport const INIT_REQUIRE_SPEC_FILE_DEFAULT = true;\n\n/** Init default: include task spec .md format guidelines in generated rules. */\nexport const INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT = true;\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport readline from \"node:readline\";\nimport { stringify } from \"yaml\";\nimport {\n CANONICAL_COLUMNS,\n INIT_DEFAULT_BOARD_PATH,\n INIT_DEFAULT_TASKS_DIR,\n INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT,\n INIT_REQUIRE_SPEC_FILE_DEFAULT,\n INIT_STRICT_MODE_DEFAULT,\n} from \"../constants.js\";\nimport { buildStatecraftRuleBody, type RuleOptions } from \"./rule-content.js\";\n\nexport interface InitAnswers {\n boardName: string;\n columns: Array<{ name: string; limit?: number }>;\n boardPath: string;\n tasksDir: string;\n}\n\n/** Raw answers collected from prompts (before parsing WIP or booleans). */\ninterface CollectedInitAnswers {\n boardName: string;\n boardPath: string;\n tasksDir: string;\n wipStr: string;\n strictMode: boolean;\n requireSpecFile: boolean;\n includeTaskSpecFormat: boolean;\n generateCursorRule: boolean;\n generateClaudeRule: boolean;\n generateCodexAgents: boolean;\n}\n\nfunction question(rl: readline.Interface, prompt: string, defaultValue?: string): Promise<string> {\n const suffix = defaultValue !== undefined ? ` (default: ${defaultValue})` : \"\";\n return new Promise((resolve) => {\n rl.question(`${prompt}${suffix}: `, (answer) => {\n const trimmed = answer.trim();\n resolve(trimmed !== \"\" ? trimmed : (defaultValue ?? \"\"));\n });\n });\n}\n\nasync function getAnswer(\n rl: readline.Interface | null,\n answers: string[] | undefined,\n index: { current: number },\n prompt: string,\n defaultValue?: string\n): Promise<string> {\n if (answers !== undefined) {\n const raw = answers[index.current++] ?? \"\";\n const trimmed = raw.trim();\n return trimmed !== \"\" ? trimmed : (defaultValue ?? \"\");\n }\n return question(rl!, prompt, defaultValue);\n}\n\nfunction parseWipLimit(wipStr: string): number | undefined {\n if (wipStr === \"\") return undefined;\n const n = parseInt(wipStr, 10);\n return Number.isInteger(n) && n >= 1 ? n : undefined;\n}\n\n/** Collect all init prompts into a structured object. */\nasync function collectInitAnswers(\n rl: readline.Interface | null,\n answers: string[] | undefined,\n index: { current: number }\n): Promise<CollectedInitAnswers> {\n const boardName = await getAnswer(rl, answers, index, \"Board name\");\n const boardPath = await getAnswer(rl, answers, index, \"Path for board file\", INIT_DEFAULT_BOARD_PATH);\n const tasksDir = await getAnswer(\n rl,\n answers,\n index,\n \"Directory for task .md files (relative to board)\",\n INIT_DEFAULT_TASKS_DIR\n );\n const wipStr = await getAnswer(\n rl,\n answers,\n index,\n \"WIP limit for In Progress (optional, press Enter to skip)\",\n \"\"\n );\n const strictModeStr = await getAnswer(\n rl,\n answers,\n index,\n \"Enforce Statecraft workflow (create task before any work)? (Y/n)\",\n INIT_STRICT_MODE_DEFAULT ? \"Y\" : \"n\"\n );\n const requireSpecStr = await getAnswer(\n rl,\n answers,\n index,\n \"Require each task to have a spec .md file? (Y/n)\",\n INIT_REQUIRE_SPEC_FILE_DEFAULT ? \"Y\" : \"n\"\n );\n const includeFormatStr = await getAnswer(\n rl,\n answers,\n index,\n \"Include task spec .md format guidelines in rules? (Y/n)\",\n INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT ? \"Y\" : \"n\"\n );\n const genCursor = await getAnswer(rl, answers, index, \"Generate Cursor rule? (Y/n)\", \"Y\");\n const genClaude = await getAnswer(rl, answers, index, \"Generate Claude Code rule? (y/n)\", \"n\");\n const genCodex = await getAnswer(rl, answers, index, \"Generate Codex instructions (AGENTS.md)? (y/n)\", \"n\");\n\n return {\n boardName,\n boardPath,\n tasksDir,\n wipStr,\n strictMode: /^y(es)?$/i.test(strictModeStr.trim()),\n requireSpecFile: /^y(es)?$/i.test(requireSpecStr.trim()),\n includeTaskSpecFormat: /^y(es)?$/i.test(includeFormatStr.trim()),\n generateCursorRule: /^y(es)?$/i.test(genCursor.trim()),\n generateClaudeRule: /^y(es)?$/i.test(genClaude.trim()),\n generateCodexAgents: /^y(es)?$/i.test(genCodex.trim()),\n };\n}\n\n/** Build the board object (name, columns, empty tasks) from collected answers. */\nfunction buildBoardFromAnswers(\n boardName: string,\n tasksDir: string,\n wipStr: string\n): { board: string; columns: Array<string | { name: string; limit: number }>; tasks: Record<string, never> } {\n const inProgressLimit = parseWipLimit(wipStr);\n const columns: Array<string | { name: string; limit: number }> = [\n CANONICAL_COLUMNS[0],\n CANONICAL_COLUMNS[1],\n inProgressLimit != null ? { name: CANONICAL_COLUMNS[2], limit: inProgressLimit } : CANONICAL_COLUMNS[2],\n CANONICAL_COLUMNS[3],\n ];\n return {\n board: boardName,\n columns,\n tasks: {},\n };\n}\n\n/** Ensure parent directory exists and write file. */\nfunction ensureDirAndWrite(filePath: string, content: string): void {\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, content, \"utf-8\");\n}\n\n/** Write board YAML to cwd-relative path. */\nfunction writeBoardFile(\n cwd: string,\n boardPath: string,\n board: { board: string; columns: unknown[]; tasks: object }\n): void {\n const absolutePath = path.resolve(cwd, boardPath);\n const yamlContent = stringify(board, { lineWidth: 0 });\n ensureDirAndWrite(absolutePath, yamlContent);\n}\n\n/** Write a rule file and optionally log. */\nfunction writeRuleFile(\n filePath: string,\n content: string,\n log: (msg: string) => void\n): void {\n ensureDirAndWrite(filePath, content);\n log(filePath);\n}\n\nconst CODECX_MARKER = \"## Statecraft (generated by statecraft init)\";\n\nfunction writeCursorRule(\n cwd: string,\n boardPath: string,\n specDir: string,\n options: Partial<RuleOptions>,\n log: (msg: string) => void\n): void {\n const cursorRulesDir = path.resolve(cwd, \".cursor\", \"rules\");\n const cursorRulePath = path.join(cursorRulesDir, \"statecraft.mdc\");\n const content = buildCursorRuleContent(boardPath, specDir, options);\n writeRuleFile(cursorRulePath, content, (p) => log(`Wrote Cursor rule to ${p}`));\n}\n\nfunction writeClaudeRule(\n cwd: string,\n boardPath: string,\n specDir: string,\n options: Partial<RuleOptions>,\n log: (msg: string) => void\n): void {\n const claudeRulesDir = path.resolve(cwd, \".claude\", \"rules\");\n const claudeRulePath = path.join(claudeRulesDir, \"statecraft.md\");\n const content = buildClaudeRuleContent(boardPath, specDir, options);\n writeRuleFile(claudeRulePath, content, (p) => log(`Wrote Claude Code rule to ${p}`));\n}\n\nfunction writeCodexRule(\n cwd: string,\n boardPath: string,\n specDir: string,\n options: Partial<RuleOptions>,\n log: (msg: string) => void\n): void {\n const codexAgentsPath = path.resolve(cwd, \"AGENTS.md\");\n const codexContent = buildCodexAgentsContent(boardPath, specDir, options);\n if (fs.existsSync(codexAgentsPath)) {\n const existing = fs.readFileSync(codexAgentsPath, \"utf-8\");\n if (existing.includes(CODECX_MARKER)) {\n log(\"AGENTS.md already contains Statecraft section; skipped.\");\n return;\n }\n fs.writeFileSync(codexAgentsPath, existing.trimEnd() + \"\\n\\n\" + codexContent + \"\\n\", \"utf-8\");\n log(`Appended Statecraft section to ${codexAgentsPath}`);\n } else {\n fs.writeFileSync(codexAgentsPath, codexContent + \"\\n\", \"utf-8\");\n log(`Wrote Codex instructions to ${codexAgentsPath}`);\n }\n}\n\n// --- Public rule content builders (used by init and by tests) ---\n\nexport function buildCursorRuleContent(\n boardPath: string,\n tasksDir: string,\n options?: Partial<RuleOptions>\n): string {\n return `---\ndescription: Statecraft board and task workflow; when to update the board and how to create tasks\nalwaysApply: true\n---\n${buildStatecraftRuleBody(boardPath, tasksDir, options)}`;\n}\n\n/** Claude Code: modular rule in .claude/rules/ (markdown, no frontmatter). */\nexport function buildClaudeRuleContent(\n boardPath: string,\n tasksDir: string,\n options?: Partial<RuleOptions>\n): string {\n return buildStatecraftRuleBody(boardPath, tasksDir, options);\n}\n\n/** Codex: section for AGENTS.md at project root. */\nexport function buildCodexAgentsContent(\n boardPath: string,\n tasksDir: string,\n options?: Partial<RuleOptions>\n): string {\n return `## Statecraft (generated by statecraft init)\n\n${buildStatecraftRuleBody(boardPath, tasksDir, options)}`;\n}\n\n// --- runInit ---\n\nexport interface RunInitOptions {\n /** For tests: pre-filled answers (board name, board path, tasks dir, WIP, strict mode Y/n, require spec Y/n, task spec format Y/n, Cursor y/n, Claude y/n, Codex y/n). */\n answers?: string[];\n /** For tests: working directory for writing board and rule files (default: process.cwd()). */\n cwd?: string;\n}\n\nexport async function runInit(options?: RunInitOptions): Promise<void> {\n const answers = options?.answers;\n const cwd = options?.cwd ?? process.cwd();\n const answerIndex = { current: 0 };\n const rl = answers === undefined ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null;\n const log = (msg: string) => {\n if (rl) process.stdout.write(msg + \"\\n\");\n };\n\n try {\n if (rl) {\n process.stdout.write(\"\\nStatecraft init — create your board and connect it to your workflow.\\n\\n\");\n }\n\n const collected = await collectInitAnswers(rl, answers, answerIndex);\n if (!collected.boardName) {\n process.stderr.write(\"Board name is required.\\n\");\n process.exitCode = 1;\n return;\n }\n\n rl?.close();\n\n const board = buildBoardFromAnswers(collected.boardName, collected.tasksDir, collected.wipStr);\n writeBoardFile(cwd, collected.boardPath, board);\n\n const absolutePath = path.resolve(cwd, collected.boardPath);\n log(`\\nCreated board at ${absolutePath}`);\n log(`Task spec files: ${path.join(path.dirname(collected.boardPath), collected.tasksDir)}/<task-id>.md`);\n\n const specDir = path.join(path.dirname(collected.boardPath), collected.tasksDir);\n const ruleOptions: Partial<RuleOptions> = {\n strictMode: collected.strictMode,\n requireSpecFile: collected.requireSpecFile,\n includeTaskSpecFormat: collected.includeTaskSpecFormat,\n };\n\n if (collected.generateCursorRule) writeCursorRule(cwd, collected.boardPath, specDir, ruleOptions, log);\n if (collected.generateClaudeRule) writeClaudeRule(cwd, collected.boardPath, specDir, ruleOptions, log);\n if (collected.generateCodexAgents) writeCodexRule(cwd, collected.boardPath, specDir, ruleOptions, log);\n\n log(\"\\nRun `statecraft validate \" + collected.boardPath + \"` to validate, or `statecraft render \" + collected.boardPath + \"` to view.\");\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(message + \"\\n\");\n process.exitCode = 1;\n } finally {\n rl?.close();\n }\n}\n","import {\n INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT,\n INIT_REQUIRE_SPEC_FILE_DEFAULT,\n INIT_STRICT_MODE_DEFAULT,\n} from \"../constants.js\";\n\n/**\n * Options for generated rule content. Used by init; defaults match init prompts (Y/n).\n */\nexport interface RuleOptions {\n /** Enforce \"create task before any work\" workflow (strict mode). Default true. */\n strictMode: boolean;\n /** Require each task to have a spec .md file. Default true. */\n requireSpecFile: boolean;\n /** Include task spec .md format guidelines in the rule. Default true. */\n includeTaskSpecFormat: boolean;\n}\n\nconst DEFAULT_RULE_OPTIONS: RuleOptions = {\n strictMode: INIT_STRICT_MODE_DEFAULT,\n requireSpecFile: INIT_REQUIRE_SPEC_FILE_DEFAULT,\n includeTaskSpecFormat: INIT_INCLUDE_TASK_SPEC_FORMAT_DEFAULT,\n};\n\n/**\n * Shared content for generated AI rules (Cursor, Claude Code, Codex).\n * Single source for the Statecraft rule body so init only orchestrates.\n */\nexport function buildStatecraftRuleBody(\n boardPath: string,\n tasksDir: string,\n options: Partial<RuleOptions> = {}\n): string {\n const opts = { ...DEFAULT_RULE_OPTIONS, ...options };\n\n const strictBlock =\n opts.strictMode &&\n `## ⚠️ REQUIRED BEFORE ANY OTHER ACTION (100% enforced)\n\n**Before** doing any substantive work (coding, fixing, refactoring, or answering the user's request with implementation), you **MUST**:\n\n1. **Ensure a task exists:** In \\`${boardPath}\\`, either **create** a new task under \\`tasks:\\` with \\`status: Backlog\\` (use a kebab-case id, e.g. \\`fix-header-import\\`), or use an existing task. New tasks start in **Backlog** (vague is OK).\n2. ${opts.requireSpecFile ? `**Spec file:** Create or update \\`${tasksDir}/<task-id>.md\\` (required for each task).` : `**Spec file:** Optionally create or update \\`${tasksDir}/<task-id>.md\\` with description and acceptance criteria.`}\n3. **Flow:** Do **not** put the task in In Progress until it has been refined. After discovery/planning, update the task's spec (description, acceptance criteria); when you know what to do and the spec is clear, set \\`status\\` to **Ready**. Only when you are **actually starting the work** (e.g. writing code), set \\`status\\` to **In Progress**. So: Backlog → Ready (after refinement) → In Progress (when working) → Done.\n\nYou **must not** start implementation (no code, no concrete changes) until the task is at least in **Ready** and you are about to work on it; then move to **In Progress**. If \\`${boardPath}\\` does not exist yet, create a minimal valid board file (board name, columns: Backlog, Ready, In Progress, Done, tasks: {}) and the task in Backlog before proceeding.\n\n---\n`;\n\n const taskSpecFormatSection = opts.includeTaskSpecFormat\n ? `\n## Task spec file format (\\`${tasksDir}/<task-id>.md\\`)\n\nFollow this structure so task specs are consistent and machine-friendly:\n\n- **Title:** First line or \\`# <Task title>\\` (match the board \\`title\\` or expand it).\n- **Description:** Short context or problem statement (optional \\`## Description\\`).\n- **Acceptance criteria / Definition of Done:** \\`## Acceptance criteria\\` or \\`## Definition of Done\\` with a checklist (e.g. \\`- [ ] item\\`) so completion is unambiguous. All items must be checked before moving the task to Done.\n- **Notes / Dependencies:** Optional \\`## Notes\\`, \\`## Dependencies\\` (for human context; \\`depends_on\\` in the board is the source of truth for task ordering).\n\nKeep each file focused on one task; use the task id in the filename (e.g. \\`fix-auth-timeout.md\\`).\n\n---\n`\n : \"\";\n\n const requireSpecBullet = opts.requireSpecFile\n ? `- **Spec required:** Every task must have a \\`spec\\` field pointing to \\`${tasksDir}/<task-id>.md\\`. Create the .md file when creating the task.\n`\n : \"\";\n\n return `# Statecraft\n${strictBlock || \"\"}\nThis project uses Statecraft for the task board.\n\n- **Board file:** \\`${boardPath}\\`\n- **Task spec files:** \\`${tasksDir}/<task-id>.md\\` (relative to board directory)\n- **Columns (canonical):** Backlog → Ready → In Progress → Done.\n\n## Commands\n\nUse \\`statecraft\\` if installed globally. If not in PATH, use \\`npx statecraft\\` (npm), \\`pnpm dlx statecraft\\` (pnpm), or \\`yarn dlx statecraft\\` (yarn)—e.g. \\`npx statecraft validate ${boardPath}\\`.\n\n- Get board format spec: \\`statecraft spec\\`\n- Validate board: \\`statecraft validate ${boardPath}\\`\n- View board in browser: \\`statecraft render ${boardPath}\\`\n\n## Task lifecycle (edit board and task files directly)\n\n**Flow:** Backlog → Ready → In Progress → Done. Do not skip Ready.\n\n- **Create task:** Add an entry under \\`tasks\\` with \\`status: Backlog\\` (id, title, optional description, spec, owner, priority, depends_on). ${opts.requireSpecFile ? `Create \\`${tasksDir}/<task-id>.md\\` for each task (required).` : `If needed, create \\`${tasksDir}/<task-id>.md\\` with description and DoD.`} Backlog = initial or vague; refinement comes next.\n- **Refine and move to Ready:** After discovery or planning, update the task's spec file (description, acceptance criteria). When the definition is clear and you know how to solve it, set \\`status\\` to **Ready**. Ready = \"ready to be worked on.\"\n- **Start work:** Only when you are about to do the actual work (code, fix, etc.), set the task's \\`status\\` to **In Progress**. Read the task's \\`spec\\` file if needed.\n- **Finish work:** Set the task's \\`status\\` to **Done** only when the task's acceptance criteria (in its spec file) are satisfied.\n${taskSpecFormatSection}\n## AI guidelines for creating tickets\n\n- **Keep the board in sync:** When doing substantive work, create a new task in **Backlog** (or use an existing one). After discovery/refinement, update the task spec and move to **Ready**; only when actually working move to **In Progress**, then **Done** when criteria are met. Do not skip Ready.\n${requireSpecBullet}- **Task naming:** kebab-case, verb or noun phrase (e.g. \\`fix-auth-timeout\\`).\n- **Description:** One line summary; optional markdown for context.\n- **Definition of Done:** Acceptance criteria in task spec; all checked before moving to Done.\n- **Task fields (from spec):** \\`title\\` (required), \\`status\\` (required), optional \\`description\\`, \\`spec\\` (path to .md), \\`owner\\`, \\`priority\\`, \\`depends_on\\`.\n- **Spec file:** Path relative to board directory, e.g. \\`${tasksDir}/<task-id>.md\\`.\n`;\n}\n","import express, { type Request, type Response } from \"express\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { createServer } from \"node:http\";\nimport { WebSocket, WebSocketServer } from \"ws\";\nimport { fileURLToPath } from \"node:url\";\nimport { DEFAULT_RENDER_PORT, RENDER_WATCH_DEBOUNCE_MS } from \"./constants.js\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n/**\n * Resolve path to the renderer's static files.\n * - When installed from npm: bundled at package-root/renderer-dist.\n * - When running in monorepo: sibling package at packages/renderer/dist.\n */\nfunction getRendererDistPath(): string {\n const bundled = path.resolve(__dirname, \"..\", \"renderer-dist\");\n if (fs.existsSync(bundled) && fs.statSync(bundled).isDirectory()) {\n return bundled;\n }\n return path.resolve(__dirname, \"..\", \"..\", \"renderer\", \"dist\");\n}\n\n/**\n * Read board file and return content as UTF-8. Returns null on error.\n */\nfunction readBoardContent(boardPath: string): string | null {\n try {\n const resolved = path.resolve(process.cwd(), boardPath);\n return fs.readFileSync(resolved, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nexport interface RenderServerOptions {\n boardPath: string;\n port?: number;\n openBrowser?: boolean;\n}\n\n/**\n * Start the render server: static app + GET /api/board + WebSocket /api/board/watch.\n * Watches the board file and broadcasts content to WS clients on change.\n */\nexport function startRenderServer(options: RenderServerOptions): void {\n const { boardPath, port = DEFAULT_RENDER_PORT, openBrowser = false } = options;\n const rendererDist = getRendererDistPath();\n\n if (!fs.existsSync(rendererDist) || !fs.statSync(rendererDist).isDirectory()) {\n process.stderr.write(\n \"Renderer build not found. Run: pnpm build\\n\"\n );\n process.exitCode = 1;\n return;\n }\n\n const app = express();\n\n // Board file directory (for resolving spec paths relative to board)\n const resolvedBoardPath = path.resolve(process.cwd(), boardPath);\n const boardDir = path.dirname(resolvedBoardPath);\n\n // API: board file content (raw YAML)\n app.get(\"/api/board\", (_req: Request, res: Response) => {\n const content = readBoardContent(boardPath);\n if (content === null) {\n res.status(404).type(\"text/plain\").send(\"Board file not found or unreadable.\");\n return;\n }\n res.type(\"text/yaml\").send(content);\n });\n\n // API: spec file content (path relative to board file directory; only .md files)\n app.get(\"/api/spec\", (req: Request, res: Response) => {\n const rawPath = typeof req.query.path === \"string\" ? req.query.path : \"\";\n if (!rawPath || rawPath.includes(\"..\")) {\n res.status(400).type(\"text/plain\").send(\"Invalid or missing path.\");\n return;\n }\n const ext = path.extname(rawPath).toLowerCase();\n if (ext !== \".md\") {\n res.status(400).type(\"text/plain\").send(\"Only .md spec files are allowed.\");\n return;\n }\n const resolved = path.resolve(boardDir, rawPath);\n const relative = path.relative(boardDir, resolved);\n if (relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n res.status(400).type(\"text/plain\").send(\"Path must be under board directory.\");\n return;\n }\n try {\n const content = fs.readFileSync(resolved, \"utf-8\");\n res.type(\"text/markdown\").send(content);\n } catch {\n res.status(404).type(\"text/plain\").send(\"Spec file not found or unreadable.\");\n }\n });\n\n // Static files (must be after /api routes so they take precedence)\n app.use(express.static(rendererDist));\n\n // SPA fallback: serve index.html for GET requests not handled by static (Express 5 / path-to-regexp v8 reject bare '*')\n app.use((_req: Request, res: Response, next: express.NextFunction) => {\n if (_req.method !== \"GET\" || res.headersSent) return next();\n const indexHtml = path.join(rendererDist, \"index.html\");\n if (fs.existsSync(indexHtml)) {\n res.sendFile(indexHtml);\n } else {\n res.status(404).send(\"Not found\");\n }\n });\n\n const server = createServer(app);\n\n // WebSocket: /api/board/watch — broadcast board content on file change\n const wss = new WebSocketServer({ noServer: true });\n\n server.on(\"upgrade\", (request, socket, head) => {\n const url = new URL(request.url ?? \"\", `http://${request.headers.host}`);\n if (url.pathname === \"/api/board/watch\") {\n wss.handleUpgrade(request, socket, head, (ws: WebSocket) => {\n wss.emit(\"connection\", ws, request);\n });\n } else {\n socket.destroy();\n }\n });\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n function broadcastBoard(): void {\n const content = readBoardContent(boardPath);\n const payload = content ?? \"\";\n wss.clients.forEach((client: WebSocket) => {\n if (client.readyState === 1) {\n client.send(payload);\n }\n });\n }\n\n wss.on(\"connection\", (ws: WebSocket) => {\n // Send current board on connect\n const content = readBoardContent(boardPath);\n if (content !== null) {\n ws.send(content);\n }\n });\n\n try {\n fs.watch(resolvedBoardPath, { persistent: false }, () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n broadcastBoard();\n }, RENDER_WATCH_DEBOUNCE_MS);\n });\n } catch {\n // File might not exist yet; watcher will not run\n }\n\n server.on(\"error\", (err: Error & { code?: string }) => {\n process.stderr.write(`Render server error: ${err.message}\\n`);\n if (err.code === \"EADDRINUSE\") {\n process.stderr.write(`Port ${port} is in use. Try --port <number>.\\n`);\n }\n process.exitCode = 1;\n });\n\n server.listen(port, () => {\n const url = `http://localhost:${port}`;\n process.stdout.write(`Open ${url}\\n`);\n if (openBrowser) {\n import(\"open\").then(({ default: open }) => {\n open(url).catch(() => {});\n });\n }\n });\n\n const shutdown = () => {\n server.close(() => {\n process.exit(process.exitCode ?? 0);\n });\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n","import { startRenderServer } from \"../render-server.js\";\n\nexport function runRender(path: string, options: { port: number; open: boolean }): void {\n startRenderServer({\n boardPath: path,\n port: options.port,\n openBrowser: options.open,\n });\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { SPEC_FILENAME } from \"../constants.js\";\nimport { findPackageRoot } from \"../utils.js\";\n\nexport function runSpec(): void {\n const thisFile = fileURLToPath(import.meta.url);\n const startDir = path.dirname(thisFile);\n const packageRoot = findPackageRoot(startDir);\n if (!packageRoot) {\n process.stderr.write(\"statecraft spec: could not find package root\\n\");\n process.exitCode = 1;\n return;\n }\n const specPath = path.join(packageRoot, SPEC_FILENAME);\n if (!fs.existsSync(specPath)) {\n process.stderr.write(`statecraft spec: spec file not found at ${specPath}\\n`);\n process.exitCode = 1;\n return;\n }\n try {\n const content = fs.readFileSync(specPath, \"utf-8\");\n process.stdout.write(content);\n if (!content.endsWith(\"\\n\")) process.stdout.write(\"\\n\");\n process.exitCode = 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`statecraft spec: ${message}\\n`);\n process.exitCode = 1;\n }\n}\n","/**\n * Shared CLI utilities (path resolution, etc.).\n */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Walk up from startDir until a directory containing package.json is found.\n * @returns Absolute path to package root, or null if not found.\n */\nexport function findPackageRoot(startDir: string): string | null {\n let dir = path.resolve(startDir);\n for (;;) {\n const pkgPath = path.join(dir, \"package.json\");\n if (fs.existsSync(pkgPath)) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n","import { parseBoard, summarize } from \"@stcrft/statecraft-core\";\n\nexport function runSummarize(path: string): void {\n try {\n const board = parseBoard(path);\n const summary = summarize(board);\n process.stdout.write(summary);\n process.exitCode = 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(message + \"\\n\");\n process.exitCode = 1;\n }\n}\n","import { parseBoard, validate } from \"@stcrft/statecraft-core\";\n\nfunction formatValidationError(err: { message: string; path?: string }): string {\n const prefix = err.path != null && err.path !== \"\" ? `${err.path}: ` : \"\";\n return `${prefix}${err.message}`;\n}\n\nexport function runValidate(path: string): void {\n try {\n const board = parseBoard(path);\n const result = validate(board);\n if (!result.valid) {\n for (const err of result.errors) {\n process.stderr.write(formatValidationError(err) + \"\\n\");\n }\n process.exitCode = 1;\n return;\n }\n process.exitCode = 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(message + \"\\n\");\n process.exitCode = 1;\n }\n}\n"],"mappings":"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","names":["fs","path","__dirname","path","fs","path","fs","path","fileURLToPath","fs","path","fileURLToPath","path","fs","path","parseBoard","path","require","path"]}
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\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 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.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "CLI for Statecraft: validate, summarize, and render board-as-code",
5
5
  "type": "module",
6
6
  "repository": {
@@ -25,7 +25,8 @@
25
25
  "open": "^11.0.0",
26
26
  "ws": "^8.18.0",
27
27
  "yaml": "^2.8.2",
28
- "@stcrft/statecraft-core": "1.3.0"
28
+ "semver": "^7.6.3",
29
+ "@stcrft/statecraft-core": "1.5.0"
29
30
  },
30
31
  "devDependencies": {
31
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