@stackwright-pro/raft 1.0.0-alpha.112 → 1.0.0-alpha.114

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/dist/index.js CHANGED
@@ -2,9 +2,9 @@
2
2
  "use strict";
3
3
 
4
4
  // src/index.ts
5
- var import_fs2 = require("fs");
6
- var import_path2 = require("path");
7
- var import_child_process2 = require("child_process");
5
+ var import_fs3 = require("fs");
6
+ var import_path3 = require("path");
7
+ var import_child_process3 = require("child_process");
8
8
  var import_integrity = require("@stackwright-pro/mcp/integrity");
9
9
  var import_type_schemas = require("@stackwright-pro/mcp/type-schemas");
10
10
 
@@ -63,7 +63,10 @@ function parseArgs(argv) {
63
63
  passEnvKeys: [],
64
64
  nonInteractive: false,
65
65
  devOnly: false,
66
- useCasePath: null
66
+ useCasePath: null,
67
+ skipQa: false,
68
+ qaOnly: false,
69
+ qaDevServerTimeoutMs: 6e4
67
70
  };
68
71
  const raw = argv.slice(2);
69
72
  for (let i = 0; i < raw.length; i++) {
@@ -91,7 +94,21 @@ function parseArgs(argv) {
91
94
  case "--use-case":
92
95
  args.useCasePath = raw[++i] ?? die("--use-case requires a file path argument");
93
96
  break;
97
+ case "--skip-qa":
98
+ args.skipQa = true;
99
+ break;
100
+ case "--qa-only":
101
+ args.qaOnly = true;
102
+ break;
94
103
  default:
104
+ if (token.startsWith("--qa-dev-server-timeout-ms=")) {
105
+ const val = parseInt(token.split("=")[1] ?? "", 10);
106
+ if (isNaN(val) || val <= 0) {
107
+ die("--qa-dev-server-timeout-ms requires a positive integer (ms)");
108
+ }
109
+ args.qaDevServerTimeoutMs = val;
110
+ break;
111
+ }
95
112
  die(`Unknown option: ${token}
96
113
  Run with --help for usage.`);
97
114
  }
@@ -106,13 +123,16 @@ function printHelp() {
106
123
  Usage: launch-raft [options]
107
124
 
108
125
  Options:
109
- --project-root <path> Project root directory (default: cwd)
110
- --verbose Enable verbose logging
111
- --pass-env <KEY> Forward an additional env var to code-puppy (repeatable)
112
- --non-interactive Skip all TUI prompts, use sane defaults for questions
113
- --dev-only Mock auth only \u2014 roles derived from use case, no real providers
114
- --use-case <file> Seed "what to build" from a file (plain text or markdown)
115
- --help, -h Show this help
126
+ --project-root <path> Project root directory (default: cwd)
127
+ --verbose Enable verbose logging
128
+ --pass-env <KEY> Forward an additional env var to code-puppy (repeatable)
129
+ --non-interactive Skip all TUI prompts, use sane defaults for questions
130
+ --dev-only Mock auth only \u2014 roles derived from use case, no real providers
131
+ --use-case <file> Seed "what to build" from a file (plain text or markdown)
132
+ --skip-qa Skip the visual QA gate (useful for CI or fast iterations)
133
+ --qa-only Skip the otter pipeline; spawn dev server and run QA directly
134
+ --qa-dev-server-timeout-ms=<n> Dev server startup timeout in ms for QA gate (default: 60000)
135
+ --help, -h Show this help
116
136
 
117
137
  Prerequisites:
118
138
  pip install stackwright-puppy # provides raft-puppy + code-puppy alias
@@ -485,10 +505,18 @@ function ensureMcpConfig(projectRoot) {
485
505
  } catch {
486
506
  }
487
507
  }
508
+ const shimName = (0, import_os.platform)() === "win32" ? "stackwright-pro-mcp.cmd" : "stackwright-pro-mcp";
509
+ const shimPath = (0, import_path.join)(projectRoot, "node_modules", ".bin", shimName);
510
+ if (!(0, import_fs.existsSync)(shimPath)) {
511
+ die(
512
+ `MCP server shim not found: ${shimPath}
513
+ Run \`pnpm install\` (or your package manager equivalent) before \`raft\`.`
514
+ );
515
+ }
488
516
  const serverConfig = {
489
517
  type: "stdio",
490
- command: "pnpm",
491
- args: ["exec", "stackwright-pro-mcp"],
518
+ command: shimPath,
519
+ args: [],
492
520
  enabled: true,
493
521
  cwd: projectRoot
494
522
  };
@@ -631,9 +659,9 @@ function syncAgents(projectRoot, isVerbose = false) {
631
659
  );
632
660
  }
633
661
  }
634
- function buildSpawnConfig(platform, executable, binaryFd) {
662
+ function buildSpawnConfig(platform2, executable, binaryFd) {
635
663
  const PINNED_CHILD_FD = 3;
636
- if (platform === "linux") {
664
+ if (platform2 === "linux") {
637
665
  return {
638
666
  spawnTarget: `/proc/self/fd/${PINNED_CHILD_FD}`,
639
667
  stdioConfig: ["inherit", "inherit", "inherit", binaryFd]
@@ -645,6 +673,174 @@ function buildSpawnConfig(platform, executable, binaryFd) {
645
673
  };
646
674
  }
647
675
 
676
+ // src/dev-server.ts
677
+ var import_child_process2 = require("child_process");
678
+ var import_fs2 = require("fs");
679
+ var import_path2 = require("path");
680
+ var DevServerStartupError = class extends Error {
681
+ constructor(message, logTail, elapsedMs) {
682
+ super(message);
683
+ this.logTail = logTail;
684
+ this.elapsedMs = elapsedMs;
685
+ this.name = "DevServerStartupError";
686
+ }
687
+ };
688
+ function detectPrismConfig(projectRoot, defaultPrismUrl = "http://localhost:4010") {
689
+ try {
690
+ const pkgPath = (0, import_path2.join)(projectRoot, "package.json");
691
+ const raw = (0, import_fs2.readFileSync)(pkgPath, "utf-8");
692
+ const pkg = JSON.parse(raw);
693
+ const scripts = pkg["scripts"];
694
+ const devScript = typeof scripts?.["dev"] === "string" ? scripts["dev"] : "";
695
+ const hasConcurrently = devScript.includes("concurrently");
696
+ const hasPrism = /prism/i.test(devScript);
697
+ return { hasPrism: hasConcurrently && hasPrism, prismUrl: defaultPrismUrl };
698
+ } catch {
699
+ return { hasPrism: false, prismUrl: defaultPrismUrl };
700
+ }
701
+ }
702
+ function tailLog(logPath, maxLines) {
703
+ try {
704
+ const content = (0, import_fs2.readFileSync)(logPath, "utf-8");
705
+ const lines = content.split("\n").filter((l) => l.length > 0);
706
+ return lines.slice(-maxLines);
707
+ } catch {
708
+ return [];
709
+ }
710
+ }
711
+ function sleep(ms) {
712
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
713
+ }
714
+ async function singleFetch(url, timeoutMs) {
715
+ const controller = new AbortController();
716
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
717
+ try {
718
+ const res = await globalThis.fetch(url, { signal: controller.signal });
719
+ clearTimeout(timer);
720
+ return res.status >= 200 && res.status < 300;
721
+ } catch {
722
+ clearTimeout(timer);
723
+ return false;
724
+ }
725
+ }
726
+ async function pollUntilAllReady(urls, timeoutMs, pollIntervalMs, perRequestTimeoutMs) {
727
+ const deadline = Date.now() + timeoutMs;
728
+ while (Date.now() < deadline) {
729
+ const remaining = deadline - Date.now();
730
+ const perReq = Math.min(perRequestTimeoutMs, remaining);
731
+ const results = await Promise.all(urls.map((url) => singleFetch(url, perReq)));
732
+ if (results.every(Boolean)) return true;
733
+ const sleepMs = Math.min(pollIntervalMs, deadline - Date.now());
734
+ if (sleepMs <= 0) break;
735
+ await sleep(sleepMs);
736
+ }
737
+ return false;
738
+ }
739
+ async function killChildProcess(child, pid) {
740
+ return new Promise((resolve3) => {
741
+ let settled = false;
742
+ const settle = () => {
743
+ if (!settled) {
744
+ settled = true;
745
+ clearTimeout(escalateTimer);
746
+ resolve3();
747
+ }
748
+ };
749
+ child.once("exit", settle);
750
+ const escalateTimer = setTimeout(() => {
751
+ try {
752
+ process.kill(pid, "SIGKILL");
753
+ } catch {
754
+ }
755
+ settle();
756
+ }, 5e3);
757
+ try {
758
+ process.kill(pid, "SIGTERM");
759
+ } catch {
760
+ settle();
761
+ }
762
+ });
763
+ }
764
+ async function withDevServer(projectRoot, options, body) {
765
+ const {
766
+ timeoutMs = 6e4,
767
+ baseUrl: baseUrlOpt,
768
+ prismUrl: prismUrlOpt,
769
+ detectPrism: shouldDetectPrism = true
770
+ } = options;
771
+ const baseUrl = baseUrlOpt ?? "http://localhost:3000";
772
+ let prismUrl;
773
+ if (prismUrlOpt !== void 0) {
774
+ prismUrl = prismUrlOpt;
775
+ } else if (shouldDetectPrism) {
776
+ const cfg = detectPrismConfig(projectRoot);
777
+ prismUrl = cfg.hasPrism ? cfg.prismUrl : void 0;
778
+ }
779
+ const stackwrightDir = (0, import_path2.join)(projectRoot, ".stackwright");
780
+ (0, import_fs2.mkdirSync)(stackwrightDir, { recursive: true });
781
+ const logPath = (0, import_path2.join)(stackwrightDir, "dev-server.log");
782
+ const logFd = (0, import_fs2.openSync)(logPath, "w");
783
+ const readyFilePath = (0, import_path2.join)(stackwrightDir, "dev-server-ready.json");
784
+ const startedAt = Date.now();
785
+ let child;
786
+ let pid;
787
+ let spawnErr;
788
+ try {
789
+ child = (0, import_child_process2.spawn)("pnpm", ["dev"], {
790
+ cwd: projectRoot,
791
+ detached: false,
792
+ stdio: ["ignore", logFd, logFd]
793
+ });
794
+ pid = child.pid;
795
+ child.on("error", (err) => {
796
+ spawnErr = err;
797
+ });
798
+ if (pid === void 0) {
799
+ throw new DevServerStartupError(
800
+ "pnpm dev failed to spawn (no PID)",
801
+ tailLog(logPath, 50),
802
+ Date.now() - startedAt
803
+ );
804
+ }
805
+ const urlsToProbe = [baseUrl, ...prismUrl ? [prismUrl] : []];
806
+ const ready = await pollUntilAllReady(urlsToProbe, timeoutMs, 2e3, 1e3);
807
+ if (!ready) {
808
+ const elapsedMs = Date.now() - startedAt;
809
+ const tail = tailLog(logPath, 50);
810
+ const spawnErrMsg = spawnErr ? ` Spawn error: ${spawnErr.message}.` : "";
811
+ throw new DevServerStartupError(
812
+ `Dev server did not become ready within ${timeoutMs}ms.${spawnErrMsg}`,
813
+ tail,
814
+ elapsedMs
815
+ );
816
+ }
817
+ const info = {
818
+ pid,
819
+ baseUrl,
820
+ ...prismUrl ? { prismUrl } : {},
821
+ startedAt,
822
+ logPath
823
+ };
824
+ (0, import_fs2.writeFileSync)(
825
+ readyFilePath,
826
+ JSON.stringify({ ...info, readyAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
827
+ );
828
+ return await body(info);
829
+ } finally {
830
+ if (child && pid !== void 0) {
831
+ await killChildProcess(child, pid);
832
+ }
833
+ try {
834
+ (0, import_fs2.unlinkSync)(readyFilePath);
835
+ } catch {
836
+ }
837
+ try {
838
+ (0, import_fs2.closeSync)(logFd);
839
+ } catch {
840
+ }
841
+ }
842
+ }
843
+
648
844
  // src/index.ts
649
845
  var MAX_RETRIES = 3;
650
846
  var BACKOFF_INITIAL_MS = 5e3;
@@ -652,28 +848,28 @@ var BACKOFF_MAX_MS = 6e4;
652
848
  function backoffMs(attempt) {
653
849
  return Math.min(BACKOFF_INITIAL_MS * Math.pow(2, attempt), BACKOFF_MAX_MS);
654
850
  }
655
- function main() {
851
+ async function main() {
656
852
  const args = parseArgs(process.argv);
657
853
  if (args.help) {
658
854
  printHelp();
659
855
  process.exit(0);
660
856
  }
661
- const projectRoot = (0, import_path2.resolve)(args.projectRoot);
662
- if (!(0, import_fs2.existsSync)(projectRoot)) {
857
+ const projectRoot = (0, import_path3.resolve)(args.projectRoot);
858
+ if (!(0, import_fs3.existsSync)(projectRoot)) {
663
859
  die(`Project root does not exist: ${projectRoot}`);
664
860
  }
665
- if (!(0, import_fs2.existsSync)((0, import_path2.join)(projectRoot, "package.json"))) {
861
+ if (!(0, import_fs3.existsSync)((0, import_path3.join)(projectRoot, "package.json"))) {
666
862
  die("No package.json found. Run npx @stackwright-pro/launch-stackwright-pro first.");
667
863
  }
668
864
  log("Launching Pro Otter Raft...");
669
865
  writeInitContext(projectRoot);
670
866
  verbose("Init context written", args.verbose);
671
867
  try {
672
- const stackwrightDir = (0, import_path2.join)(projectRoot, ".stackwright");
673
- (0, import_fs2.mkdirSync)(stackwrightDir, { recursive: true });
868
+ const stackwrightDir = (0, import_path3.join)(projectRoot, ".stackwright");
869
+ (0, import_fs3.mkdirSync)(stackwrightDir, { recursive: true });
674
870
  const schemaSummary = (0, import_type_schemas.buildTypeSchemaSummary)();
675
- (0, import_fs2.writeFileSync)(
676
- (0, import_path2.join)(stackwrightDir, "type-schemas.json"),
871
+ (0, import_fs3.writeFileSync)(
872
+ (0, import_path3.join)(stackwrightDir, "type-schemas.json"),
677
873
  JSON.stringify(schemaSummary, null, 2) + "\n"
678
874
  );
679
875
  verbose("Type schemas sink written", args.verbose);
@@ -681,25 +877,32 @@ function main() {
681
877
  console.warn("\u26A0\uFE0F Could not write type-schemas.json:", String(err));
682
878
  }
683
879
  {
684
- const stackwrightDir = (0, import_path2.join)(projectRoot, ".stackwright");
685
- const initContextPath = (0, import_path2.join)(stackwrightDir, "init-context.json");
686
- const initRaw = (0, import_fs2.readFileSync)(initContextPath, "utf-8");
880
+ const stackwrightDir = (0, import_path3.join)(projectRoot, ".stackwright");
881
+ const initContextPath = (0, import_path3.join)(stackwrightDir, "init-context.json");
882
+ const initRaw = (0, import_fs3.readFileSync)(initContextPath, "utf-8");
687
883
  const initCtx = JSON.parse(initRaw);
688
884
  if (args.nonInteractive) initCtx["nonInteractive"] = true;
689
885
  if (args.devOnly) initCtx["devOnly"] = true;
690
- (0, import_fs2.writeFileSync)(initContextPath, JSON.stringify(initCtx, null, 2) + "\n", "utf-8");
886
+ initCtx["skipQa"] = args.skipQa;
887
+ initCtx["qaOnly"] = args.qaOnly;
888
+ initCtx["devServer"] = {
889
+ managed: true,
890
+ baseUrl: "http://localhost:3000",
891
+ timeoutMs: args.qaDevServerTimeoutMs
892
+ };
893
+ (0, import_fs3.writeFileSync)(initContextPath, JSON.stringify(initCtx, null, 2) + "\n", "utf-8");
691
894
  verbose("Raft flags written to init-context", args.verbose);
692
895
  }
693
896
  {
694
- const buildContextPath = (0, import_path2.join)(projectRoot, ".stackwright", "build-context.json");
897
+ const buildContextPath = (0, import_path3.join)(projectRoot, ".stackwright", "build-context.json");
695
898
  const MAX_USE_CASE_BYTES = 1 * 1024 * 1024;
696
899
  const WARN_USE_CASE_BYTES = 200 * 1024;
697
900
  if (args.useCasePath) {
698
- const resolvedPath = (0, import_path2.resolve)(args.useCasePath);
699
- if (!(0, import_fs2.existsSync)(resolvedPath)) {
901
+ const resolvedPath = (0, import_path3.resolve)(args.useCasePath);
902
+ if (!(0, import_fs3.existsSync)(resolvedPath)) {
700
903
  die(`Use-case file not found: ${resolvedPath}`);
701
904
  }
702
- const content = (0, import_fs2.readFileSync)(resolvedPath, "utf-8");
905
+ const content = (0, import_fs3.readFileSync)(resolvedPath, "utf-8");
703
906
  if (content.length > MAX_USE_CASE_BYTES) {
704
907
  die(
705
908
  `Use-case file exceeds ${(MAX_USE_CASE_BYTES / 1024 / 1024).toFixed(0)}MB (got ${(content.length / 1024).toFixed(0)}KB). Trim the file or split into sections.`
@@ -715,15 +918,15 @@ function main() {
715
918
  savedAt: (/* @__PURE__ */ new Date()).toISOString(),
716
919
  buildContext: content
717
920
  };
718
- (0, import_fs2.writeFileSync)(buildContextPath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
921
+ (0, import_fs3.writeFileSync)(buildContextPath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
719
922
  log(`Build context seeded from ${args.useCasePath}`);
720
- } else if (args.nonInteractive && !(0, import_fs2.existsSync)(buildContextPath)) {
923
+ } else if (args.nonInteractive && !(0, import_fs3.existsSync)(buildContextPath)) {
721
924
  const payload = {
722
925
  version: "1.0",
723
926
  savedAt: (/* @__PURE__ */ new Date()).toISOString(),
724
927
  buildContext: "Build a sample application from the available API specs and data sources with default settings. Use all available endpoints and standard page layouts."
725
928
  };
726
- (0, import_fs2.writeFileSync)(buildContextPath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
929
+ (0, import_fs3.writeFileSync)(buildContextPath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
727
930
  log("Build context seeded with generic defaults (--non-interactive, no --use-case)");
728
931
  }
729
932
  }
@@ -754,10 +957,22 @@ function main() {
754
957
  const { path: executable, fd: initialFd } = findCodePuppy();
755
958
  verbose(`Resolved raft-puppy / code-puppy: ${executable}`, args.verbose);
756
959
  try {
757
- (0, import_fs2.closeSync)(initialFd);
960
+ (0, import_fs3.closeSync)(initialFd);
758
961
  } catch {
759
962
  }
760
963
  validateBinaryVersion(executable);
964
+ if (args.qaOnly) {
965
+ log("--qa-only mode: skipping otter pipeline, spawning dev server for visual QA...");
966
+ await withDevServer(projectRoot, { timeoutMs: args.qaDevServerTimeoutMs }, async (info) => {
967
+ log("--qa-only: dev server ready \u2705");
968
+ log(` pid: ${info.pid}`);
969
+ log(` baseUrl: ${info.baseUrl}`);
970
+ if (info.prismUrl) log(` prismUrl: ${info.prismUrl}`);
971
+ log(` logPath: ${info.logPath}`);
972
+ log("\u23F8 QA otter invocation not yet implemented (blocked on swp-wde8 + swp-kjkg)");
973
+ });
974
+ process.exit(0);
975
+ }
761
976
  log("Spawning raft-puppy / code-puppy raft session...");
762
977
  let retryCount = 0;
763
978
  let userInterrupted = false;
@@ -780,13 +995,13 @@ function main() {
780
995
  `cmd: ${spawnTarget} ${spawnArgs.join(" ")}${spawnTarget !== resolvedBinary ? ` (real: ${resolvedBinary})` : ""}${isResume ? ` [retry ${retryCount}/${MAX_RETRIES}]` : ""}`,
781
996
  args.verbose
782
997
  );
783
- const child = (0, import_child_process2.spawn)(spawnTarget, spawnArgs, {
998
+ const child = (0, import_child_process3.spawn)(spawnTarget, spawnArgs, {
784
999
  stdio: stdioConfig,
785
1000
  cwd: projectRoot,
786
1001
  env: buildCodePuppyEnv({ STACKWRIGHT_PROJECT_ROOT: projectRoot }, args.passEnvKeys)
787
1002
  });
788
1003
  try {
789
- (0, import_fs2.closeSync)(binaryFd);
1004
+ (0, import_fs3.closeSync)(binaryFd);
790
1005
  } catch {
791
1006
  }
792
1007
  const forward = (signal) => {
@@ -829,7 +1044,17 @@ function main() {
829
1044
  }
830
1045
  });
831
1046
  }
1047
+ if (args.skipQa) {
1048
+ log("\u26A0\uFE0F Visual QA skipped by --skip-qa flag");
1049
+ } else {
1050
+ log(
1051
+ "\u23F8 Visual QA stub \u2014 dev server lifecycle is wired, QA otter invocation lands in swp-wde8/swp-kjkg"
1052
+ );
1053
+ }
832
1054
  spawnSession(false);
833
1055
  }
834
- main();
1056
+ main().catch((err) => {
1057
+ const msg = err instanceof Error ? err.message : String(err);
1058
+ die(`Unexpected fatal error: ${msg}`);
1059
+ });
835
1060
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/lib.ts"],"sourcesContent":["/**\n * @stackwright-pro/raft — Launch the Pro Otter Raft\n *\n * Writes init-context, verifies otter integrity, and spawns code-puppy\n * in foreman mode. The MCP tools handle everything after that.\n *\n * Replaces the deprecated Python cli_adapter.py → ForemanSession → os.execvpe() chain.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, closeSync } from 'fs';\nimport { join, resolve } from 'path';\nimport { spawn } from 'child_process';\nimport { verifyAllOtters } from '@stackwright-pro/mcp/integrity';\nimport { buildTypeSchemaSummary } from '@stackwright-pro/mcp/type-schemas';\nimport {\n parseArgs,\n printHelp,\n writeInitContext,\n findCodePuppy,\n validateBinaryVersion,\n migrateWorkspaceDir,\n ensureWorkspaceConfig,\n ensureMcpConfig,\n syncAgents,\n printResumeStatus,\n isPipelineResumable,\n resolveOtterDir,\n buildCodePuppyEnv,\n buildSpawnConfig,\n die,\n log,\n verbose,\n} from './lib.js';\n\n// ─── Retry / Backoff Constants ───────────────────────────────────────────────\n\n/** Max automatic re-spawn attempts after an unexpected non-zero exit. */\nconst MAX_RETRIES = 3;\n/** Initial backoff delay in ms. Doubles on each attempt, capped at MAX_BACKOFF_MS. */\nconst BACKOFF_INITIAL_MS = 5_000;\n/** Hard ceiling on backoff delay. */\nconst BACKOFF_MAX_MS = 60_000;\n\n/** Exponential backoff: 5s → 10s → 20s → (cap) 60s. */\nfunction backoffMs(attempt: number): number {\n return Math.min(BACKOFF_INITIAL_MS * Math.pow(2, attempt), BACKOFF_MAX_MS);\n}\n\n// ─── Main ────────────────────────────────────────────────────────────────────\n\nfunction main(): void {\n const args = parseArgs(process.argv);\n\n if (args.help) {\n printHelp();\n process.exit(0);\n }\n\n const projectRoot = resolve(args.projectRoot);\n if (!existsSync(projectRoot)) {\n die(`Project root does not exist: ${projectRoot}`);\n }\n\n // Sanity check: is this a Stackwright project?\n if (!existsSync(join(projectRoot, 'package.json'))) {\n die('No package.json found. Run npx @stackwright-pro/launch-stackwright-pro first.');\n }\n\n log('Launching Pro Otter Raft...');\n\n // 1. Write/enrich init context\n writeInitContext(projectRoot);\n verbose('Init context written', args.verbose);\n\n // 1a. Write type schemas sink for foreman routing\n try {\n const stackwrightDir = join(projectRoot, '.stackwright');\n mkdirSync(stackwrightDir, { recursive: true });\n const schemaSummary = buildTypeSchemaSummary();\n writeFileSync(\n join(stackwrightDir, 'type-schemas.json'),\n JSON.stringify(schemaSummary, null, 2) + '\\n'\n );\n verbose('Type schemas sink written', args.verbose);\n } catch (err) {\n // Non-blocking — foreman can still operate without type-schemas.json\n console.warn('⚠️ Could not write type-schemas.json:', String(err));\n }\n\n // 1b. Write raft flags to init-context for foreman consumption\n {\n const stackwrightDir = join(projectRoot, '.stackwright');\n const initContextPath = join(stackwrightDir, 'init-context.json');\n\n // Merge flags into existing init-context (writeInitContext already created it)\n const initRaw = readFileSync(initContextPath, 'utf-8');\n const initCtx = JSON.parse(initRaw) as Record<string, unknown>;\n\n if (args.nonInteractive) initCtx['nonInteractive'] = true;\n if (args.devOnly) initCtx['devOnly'] = true;\n\n writeFileSync(initContextPath, JSON.stringify(initCtx, null, 2) + '\\n', 'utf-8');\n verbose('Raft flags written to init-context', args.verbose);\n }\n\n // 1c. Seed build context from --use-case file or generic fallback\n {\n const buildContextPath = join(projectRoot, '.stackwright', 'build-context.json');\n const MAX_USE_CASE_BYTES = 1 * 1024 * 1024; // 1MB\n const WARN_USE_CASE_BYTES = 200 * 1024; // 200KB\n\n if (args.useCasePath) {\n const resolvedPath = resolve(args.useCasePath);\n if (!existsSync(resolvedPath)) {\n die(`Use-case file not found: ${resolvedPath}`);\n }\n\n const content = readFileSync(resolvedPath, 'utf-8');\n if (content.length > MAX_USE_CASE_BYTES) {\n die(\n `Use-case file exceeds ${(MAX_USE_CASE_BYTES / 1024 / 1024).toFixed(0)}MB ` +\n `(got ${(content.length / 1024).toFixed(0)}KB). Trim the file or split into sections.`\n );\n }\n if (content.length > WARN_USE_CASE_BYTES) {\n console.warn(\n `⚠️ Large use-case file (${(content.length / 1024).toFixed(0)}KB) — ` +\n `specialist otters will receive the full context via build prompts.`\n );\n }\n\n const payload = {\n version: '1.0',\n savedAt: new Date().toISOString(),\n buildContext: content,\n };\n writeFileSync(buildContextPath, JSON.stringify(payload, null, 2) + '\\n', 'utf-8');\n log(`Build context seeded from ${args.useCasePath}`);\n } else if (args.nonInteractive && !existsSync(buildContextPath)) {\n // Generic fallback for --non-interactive without --use-case\n const payload = {\n version: '1.0',\n savedAt: new Date().toISOString(),\n buildContext:\n 'Build a sample application from the available API specs and data sources ' +\n 'with default settings. Use all available endpoints and standard page layouts.',\n };\n writeFileSync(buildContextPath, JSON.stringify(payload, null, 2) + '\\n', 'utf-8');\n log('Build context seeded with generic defaults (--non-interactive, no --use-case)');\n }\n }\n\n // 1d. Migrate legacy .code-puppy/ to .code_puppy/ if needed\n migrateWorkspaceDir(projectRoot);\n\n // 1e. Ensure workspace config — write .code_puppy/config.json (profile: strict-local)\n // Must come before ensureMcpConfig so the workspace dir exists.\n ensureWorkspaceConfig(projectRoot);\n\n // 1f. Register MCP server — do this BEFORE any die() calls so clean installs\n // get MCP config even if otter install is incomplete\n ensureMcpConfig(projectRoot);\n\n // 1g. Sync agent files from installed @stackwright-pro/otters — always reflects\n // the installed version without relying on postinstall hooks\n syncAgents(projectRoot, args.verbose);\n\n // 2. Verify otter integrity\n const otterDir = resolveOtterDir(projectRoot);\n if (!otterDir) {\n die(\n 'Could not find otter directory. Is @stackwright-pro/otters installed?\\n' +\n ' Run: pnpm add @stackwright-pro/otters'\n );\n }\n\n const result = verifyAllOtters(otterDir);\n if (result.failed.length > 0) {\n console.warn('⚠️ Otter integrity check warnings (non-blocking):');\n for (const f of result.failed) {\n console.warn(` ${f.filename}: ${f.error}`);\n }\n console.warn(' Note: SHA-256 pinning will be replaced by PKI-signed deployment manifests.');\n console.warn(\n ' See: https://github.com/Per-Aspera-LLC/stackwright-pro/issues (signing model issue)'\n );\n } else {\n log(`✅ All ${result.verified.length} otters verified`);\n }\n\n // 3. Print resume status\n printResumeStatus(projectRoot);\n\n // 4. Resolve raft-puppy / code-puppy and validate version (once, before retry loop)\n const { path: executable, fd: initialFd } = findCodePuppy();\n verbose(`Resolved raft-puppy / code-puppy: ${executable}`, args.verbose);\n try {\n closeSync(initialFd);\n } catch {\n /* best-effort */\n }\n\n // 4a. Pre-flight: validate binary version before spawning\n validateBinaryVersion(executable);\n\n // 5. Spawn raft-puppy / code-puppy — with automatic retry on unexpected exits.\n //\n // If code-puppy exits with a non-zero code (e.g. Cloudflare 400 mid-session,\n // transient API error) AND the pipeline state is resumable (status = 'questions'\n // or 'execution'), we wait with exponential backoff then re-spawn with a \"Resume\"\n // prompt. The Foreman reads pipeline-state.json and picks up where it left off.\n //\n // SIGINT/SIGTERM from the user are NOT retried — they abort cleanly.\n log('Spawning raft-puppy / code-puppy raft session...');\n\n let retryCount = 0;\n let userInterrupted = false;\n\n function spawnSession(isResume: boolean): void {\n // Re-open the binary on each spawn attempt so we get a fresh inode lock.\n // On Linux: spawn via /proc/self/fd/N — the fd was opened (and inode-checked) by\n // findCodePuppy(), so an attacker cannot swap the binary between validation and exec.\n //\n // Shebang-aware: raft-puppy is a Python script, not an ELF binary. The kernel's\n // shebang handler re-execs the interpreter (python) with the fd path as a string\n // argument. O_CLOEXEC (set by Node's openSync) would close the fd during that exec,\n // causing Python to get ENOENT on /proc/self/fd/N.\n //\n // Fix: pass the fd through the stdio array at index 3. dup2() clears O_CLOEXEC,\n // so fd 3 survives the interpreter exec. We then spawn via /proc/self/fd/3.\n //\n // On macOS: /proc does not exist — we fall back to the validated path.\n // Residual TOCTOU on macOS is documented and accepted for the current threat model.\n // See: CWE-367, GH#128, beads issue stackwright-pro-s1g.\n const { path: resolvedBinary, fd: binaryFd } = findCodePuppy();\n\n if (process.platform !== 'linux') {\n verbose(\n `macOS/other: spawning via path (residual TOCTOU — /proc unavailable). ` +\n `See GH#128 for threat model documentation.`,\n args.verbose\n );\n }\n\n // First run: \"Begin\". Retry runs: \"Resume\" so the Foreman reads pipeline-state.json.\n const prompt = isResume ? 'Resume' : 'Begin';\n const spawnArgs = [prompt, '--interactive', '--agent', 'stackwright-pro-foreman-otter'];\n\n const { spawnTarget, stdioConfig } = buildSpawnConfig(\n process.platform,\n resolvedBinary,\n binaryFd\n );\n\n verbose(\n `cmd: ${spawnTarget} ${spawnArgs.join(' ')}` +\n `${spawnTarget !== resolvedBinary ? ` (real: ${resolvedBinary})` : ''}` +\n `${isResume ? ` [retry ${retryCount}/${MAX_RETRIES}]` : ''}`,\n args.verbose\n );\n\n const child = spawn(spawnTarget, spawnArgs, {\n stdio: stdioConfig,\n cwd: projectRoot,\n env: buildCodePuppyEnv({ STACKWRIGHT_PROJECT_ROOT: projectRoot }, args.passEnvKeys),\n });\n\n // Close the parent's copy of the fd. On Linux the child inherited it via dup2\n // to fd 3 (without O_CLOEXEC) so the child's copy survives exec independently.\n try {\n closeSync(binaryFd);\n } catch {\n /* best-effort */\n }\n\n // Forward signals to child — let it clean up gracefully.\n // Track interrupts so we DON'T retry on user-initiated termination.\n const forward = (signal: NodeJS.Signals): void => {\n if (child.pid) child.kill(signal);\n };\n const onSigint = (): void => {\n userInterrupted = true;\n forward('SIGINT');\n };\n const onSigterm = (): void => {\n userInterrupted = true;\n forward('SIGTERM');\n };\n process.on('SIGINT', onSigint);\n process.on('SIGTERM', onSigterm);\n\n child.on('error', (err) => die(`Failed to spawn raft-puppy / code-puppy: ${err.message}`));\n child.on('close', (code, signal) => {\n process.off('SIGINT', onSigint);\n process.off('SIGTERM', onSigterm);\n\n // Signal exit (e.g. SIGINT from user) — re-raise and exit cleanly\n if (signal) {\n process.kill(process.pid, signal);\n return;\n }\n\n // Clean exit or user-interrupted — done\n if (code === 0 || userInterrupted) {\n process.exit(code ?? 0);\n return;\n }\n\n // Non-zero exit — check if retry is warranted\n if (retryCount < MAX_RETRIES && isPipelineResumable(projectRoot)) {\n retryCount++;\n const delay = backoffMs(retryCount - 1);\n log(\n `⚠️ Raft session exited unexpectedly (code ${code ?? '?'}). ` +\n `Pipeline is mid-run — retrying in ${delay / 1_000}s… ` +\n `(attempt ${retryCount}/${MAX_RETRIES})`\n );\n setTimeout(() => spawnSession(true), delay);\n } else {\n if (retryCount >= MAX_RETRIES) {\n log(`❌ Max retries (${MAX_RETRIES}) reached — giving up. Check logs above for errors.`);\n }\n process.exit(code ?? 1);\n }\n });\n }\n\n spawnSession(false);\n}\n\nmain();\n","/**\n * @stackwright-pro/raft — Pure utility functions\n *\n * Extracted from the CLI entry point so they're independently testable.\n * All side-effectful helpers (die, log, verbose) live here too —\n * the CLI entry point (`index.ts`) just wires them into `main()`.\n */\n\nimport { spawnSync } from 'child_process';\nimport {\n existsSync,\n lstatSync,\n mkdirSync,\n openSync,\n fstatSync,\n closeSync,\n readFileSync,\n readdirSync,\n realpathSync,\n renameSync,\n writeFileSync,\n rmSync,\n} from 'fs';\nimport { join, resolve } from 'path';\nimport { hostname, homedir } from 'os';\n\n// ─── Code-Puppy Environment Allowlist ────────────────────────────────────────\n//\n// CWE-526: Sensitive environment variables must not be passed to child processes.\n// CVSS v4.0 ~3.0. The spawn call in index.ts MUST use buildCodePuppyEnv() — never\n// spread ...process.env directly.\n//\n// Only env vars in this allowlist (or matching the prefix list) are forwarded to\n// the code-puppy child process. AWS_*, GITHUB_TOKEN, DATABASE_URL, NPM_TOKEN, etc.\n// are silently stripped.\n\n/**\n * Exact env var names that code-puppy legitimately needs.\n */\nconst CODE_PUPPY_ENV_ALLOWLIST: ReadonlySet<string> = new Set([\n // Shell / execution environment\n 'PATH',\n 'HOME',\n 'USER',\n 'USERNAME',\n 'SHELL',\n 'TMPDIR',\n 'TMP',\n 'TEMP',\n // Terminal\n 'TERM',\n 'COLORTERM',\n 'COLUMNS',\n 'LINES',\n 'NO_COLOR',\n 'FORCE_COLOR',\n // Language / locale\n 'LANG',\n 'LC_ALL',\n 'LC_CTYPE',\n 'LC_MESSAGES',\n // Network proxy — needed for outbound LLM API calls behind corporate proxies\n 'HTTP_PROXY',\n 'HTTPS_PROXY',\n 'NO_PROXY',\n 'http_proxy',\n 'https_proxy',\n 'no_proxy',\n // LLM API keys — the only credentials code-puppy should have\n 'ANTHROPIC_API_KEY',\n 'ANTHROPIC_BASE_URL',\n 'OPENAI_API_KEY',\n // Node runtime\n 'NODE_ENV',\n // Stackwright-specific\n 'STACKWRIGHT_PROJECT_ROOT',\n 'STACKWRIGHT_SKIP_PREFLIGHT',\n 'STACKWRIGHT_CODE_PUPPY_PATH',\n]);\n\n/**\n * Prefix-based allowlist — any env var starting with these prefixes is allowed.\n * Covers families like PYTHONPATH, PYTHONHOME, LC_NUMERIC, etc.\n */\nconst CODE_PUPPY_ENV_PREFIXES: readonly string[] = ['PYTHON', 'LC_'];\n\n// ─── Pre-flight Constants ────────────────────────────────────────────────────\n\n/** Minimum code-puppy / raft-puppy version required by this raft release.\n *\n * raft-puppy / code-puppy uses 0.0.x patch-only versioning on PyPI (e.g. 0.0.575).\n *\n * Bump this when a new raft-puppy feature becomes a hard dependency.\n * 0.0.575 — first version with the project_workspace builtin plugin\n * (profile-based scoping). Older versions silently degrade to additive\n * merge mode, which breaks raft's reproducibility guarantee.\n *\n * Dev builds (0.0.0 / 0.0.0-*) are handled separately by the isDevBuild bypass.\n */\nexport const MIN_SUPPORTED_CODE_PUPPY_VERSION = '0.0.575';\n\n// ─── CLI Argument Parsing ────────────────────────────────────────────────────\n\nexport interface ParsedArgs {\n projectRoot: string;\n verbose: boolean;\n help: boolean;\n /** Keys explicitly passed via --pass-env flags. */\n passEnvKeys: string[];\n /** Run without TUI prompts — use sane defaults for all questions. */\n nonInteractive: boolean;\n /** Skip real auth config — use mock auth with roles derived from use case. */\n devOnly: boolean;\n /** Path to a use-case file that seeds the build context. */\n useCasePath: string | null;\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const args: ParsedArgs = {\n projectRoot: process.cwd(),\n verbose: false,\n help: false,\n passEnvKeys: [],\n nonInteractive: false,\n devOnly: false,\n useCasePath: null,\n };\n\n const raw = argv.slice(2);\n for (let i = 0; i < raw.length; i++) {\n const token = raw[i];\n switch (token) {\n case '--project-root':\n args.projectRoot = raw[++i] ?? die('--project-root requires a path argument');\n break;\n case '--verbose':\n args.verbose = true;\n break;\n case '--help':\n case '-h':\n args.help = true;\n break;\n case '--pass-env':\n args.passEnvKeys.push(raw[++i] ?? die('--pass-env requires a KEY argument'));\n break;\n case '--non-interactive':\n args.nonInteractive = true;\n break;\n case '--dev-only':\n args.devOnly = true;\n break;\n case '--use-case':\n args.useCasePath = raw[++i] ?? die('--use-case requires a file path argument');\n break;\n default:\n die(`Unknown option: ${token}\\nRun with --help for usage.`);\n }\n }\n\n return args;\n}\n\nexport function printHelp(): void {\n console.log(\n `\n🦦 @stackwright-pro/raft — Spawn raft-puppy (or code-puppy) in foreman mode\n\nUsage: launch-raft [options]\n\nOptions:\n --project-root <path> Project root directory (default: cwd)\n --verbose Enable verbose logging\n --pass-env <KEY> Forward an additional env var to code-puppy (repeatable)\n --non-interactive Skip all TUI prompts, use sane defaults for questions\n --dev-only Mock auth only — roles derived from use case, no real providers\n --use-case <file> Seed \"what to build\" from a file (plain text or markdown)\n --help, -h Show this help\n\nPrerequisites:\n pip install stackwright-puppy # provides raft-puppy + code-puppy alias\n Or: pip install code-puppy # fallback (MCP tools may not auto-start)\n`.trim()\n );\n}\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n\nexport function die(message: string): never {\n console.error(`❌ ${message}`);\n process.exit(1);\n}\n\nexport function log(message: string): void {\n console.log(`🦦 ${message}`);\n}\n\nexport function verbose(message: string, isVerbose: boolean): void {\n if (isVerbose) {\n console.log(` ${message}`);\n }\n}\n\n/**\n * Build a restricted environment for the code-puppy child process.\n *\n * Filters `process.env` to only keys in CODE_PUPPY_ENV_ALLOWLIST or matching\n * CODE_PUPPY_ENV_PREFIXES, then merges in `extraVars` (always wins) and any\n * keys explicitly requested via `--pass-env` (escape hatch).\n *\n * CWE-526 mitigation: credentials like AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN,\n * DATABASE_URL, NPM_TOKEN, etc. are silently excluded.\n *\n * @param extraVars Vars to force-inject (e.g. STACKWRIGHT_PROJECT_ROOT).\n * @param passEnvKeys Additional keys from --pass-env flags.\n */\nexport function buildCodePuppyEnv(\n extraVars: Record<string, string> = {},\n passEnvKeys: string[] = []\n): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {};\n\n for (const [key, value] of Object.entries(process.env)) {\n if (value === undefined) continue;\n\n const allowed =\n CODE_PUPPY_ENV_ALLOWLIST.has(key) ||\n CODE_PUPPY_ENV_PREFIXES.some((prefix) => key.startsWith(prefix)) ||\n passEnvKeys.includes(key);\n\n if (allowed) {\n env[key] = value;\n }\n }\n\n // extraVars always win — they're injected by the raft, not from the parent env\n for (const [key, value] of Object.entries(extraVars)) {\n env[key] = value;\n }\n\n return env;\n}\n\n// ─── Pipeline Lock ──────────────────────────────────────────────────────────\n\nconst LOCK_FILE = '.stackwright/.lock';\n\nexport function acquireLock(projectRoot: string): boolean {\n const lockPath = join(projectRoot, LOCK_FILE);\n\n // Symlink guard\n if (existsSync(lockPath) && lstatSync(lockPath).isSymbolicLink()) {\n die('.stackwright/.lock is a symlink — refusing to acquire lock. Check for tampering.');\n }\n\n const pid = process.pid;\n const host = hostname();\n const timestamp = new Date().toISOString();\n\n const lockContent = JSON.stringify({\n pid,\n hostname: host,\n acquiredAt: timestamp,\n version: '1.0',\n });\n\n mkdirSync(join(projectRoot, '.stackwright'), { recursive: true });\n\n try {\n // O_EXCL makes this atomic on POSIX — fails if file already exists\n writeFileSync(lockPath, lockContent, { flag: 'wx' });\n return true;\n } catch (err) {\n // EEXIST means lock already held by another process\n if (err instanceof Error && 'code' in err && (err as { code: string }).code === 'EEXIST') {\n // Lock exists — try to read it and check if the process is still alive\n try {\n const existing = JSON.parse(readFileSync(lockPath, 'utf8')) as Record<string, unknown>;\n const oldPid = existing['pid'] as number;\n\n // On Unix, check if process still exists via kill(0, pid)\n try {\n process.kill(oldPid, 0); // Signal 0 = check existence only\n // Process exists — lock is held by live process, cannot acquire\n return false;\n } catch {\n // Process is dead — stale lock, can take over\n writeFileSync(lockPath, lockContent, 'utf-8');\n return true;\n }\n } catch {\n // Can't read lock file or not JSON — treat as stale, take over\n writeFileSync(lockPath, lockContent, 'utf-8');\n return true;\n }\n }\n throw err;\n }\n}\n\nexport function releaseLock(projectRoot: string): void {\n const lockPath = join(projectRoot, LOCK_FILE);\n try {\n rmSync(lockPath);\n } catch {\n // Lock file may not exist — that's fine\n }\n}\n\n// ─── Workspace Directory Migration ────────────────────────────────────────────\n\n/**\n * Migrate the legacy `.code-puppy/` workspace directory to `.code_puppy/`.\n *\n * PR #290 renamed the workspace directory from `.code-puppy` (hyphen) to\n * `.code_puppy` (underscore) to match the canonical `PROJECT_WORKSPACE_DIR_NAME`\n * in code-puppy's `config.py`. Projects scaffolded before that change still\n * have the old directory; without migration, `get_project_workspace()` walks\n * past it and falls through to `$HOME/.code_puppy/` (global config), causing\n * `strict-local` profile and local MCP server loading to silently fail.\n *\n * Safe to call on every launch — no-ops when there's nothing to migrate.\n */\nexport function migrateWorkspaceDir(projectRoot: string): void {\n const oldDir = join(projectRoot, '.code-puppy');\n const newDir = join(projectRoot, '.code_puppy');\n\n const oldExists = existsSync(oldDir);\n const newExists = existsSync(newDir);\n\n if (!oldExists) return; // Nothing to migrate\n\n // Symlink guards — refuse to follow symlinks on either directory\n if (lstatSync(oldDir).isSymbolicLink()) {\n die('.code-puppy/ is a symlink — refusing to migrate. Check for tampering.');\n }\n if (newExists && lstatSync(newDir).isSymbolicLink()) {\n die('.code_puppy/ is a symlink — refusing to migrate. Check for tampering.');\n }\n\n if (!newExists) {\n // Clean migration: old exists, new doesn't → rename\n renameSync(oldDir, newDir);\n log('Migrated .code-puppy/ → .code_puppy/ (underscore convention)');\n } else {\n // Both exist — remove old to avoid confusion (underscore is canonical)\n rmSync(oldDir, { recursive: true });\n log('Removed stale .code-puppy/ (underscore version already exists)');\n }\n}\n\n// ─── Write Init Context ─────────────────────────────────────────────────────\n\nexport function writeInitContext(projectRoot: string): void {\n // Acquire pipeline lock — prevent concurrent launch-raft processes\n if (!acquireLock(projectRoot)) {\n let existingPid: string | number = 'unknown';\n try {\n const lockPath = join(projectRoot, LOCK_FILE);\n if (existsSync(lockPath)) {\n const lockData = JSON.parse(readFileSync(lockPath, 'utf8')) as Record<string, unknown>;\n const rawPid = lockData['pid'];\n existingPid = typeof rawPid === 'string' || typeof rawPid === 'number' ? rawPid : 'unknown';\n }\n } catch {\n // Couldn't read lock file\n }\n die(\n `Pipeline lock already held by PID ${existingPid}. Another launch-raft process is running.`\n );\n }\n\n const stackwrightDir = join(projectRoot, '.stackwright');\n const initContextPath = join(stackwrightDir, 'init-context.json');\n\n // Merge, don't clobber — respect anything the launcher already wrote\n const MAX_INIT_CONTEXT_BYTES = 1 * 1024 * 1024; // 1MB\n let existing: Record<string, unknown> = {};\n try {\n const raw = readFileSync(initContextPath, 'utf-8');\n\n // Size guard — reject oversized init-context.json before parsing\n if (raw.length > MAX_INIT_CONTEXT_BYTES) {\n die(\n `init-context.json exceeds ${MAX_INIT_CONTEXT_BYTES.toLocaleString()} bytes (got ${raw.length.toLocaleString()}). Refusing to parse. This may be an attack or a corrupted file.`\n );\n }\n\n existing = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n // Fresh project or malformed file — start from scratch\n }\n\n // Enrich: only fill in gaps\n existing['projectRoot'] = projectRoot;\n\n if (!existing['projectName']) {\n try {\n const pkgRaw = readFileSync(join(projectRoot, 'package.json'), 'utf-8');\n const pkg = JSON.parse(pkgRaw) as Record<string, unknown>;\n if (typeof pkg['name'] === 'string') {\n existing['projectName'] = pkg['name'];\n }\n } catch {\n // No package.json name — that's fine\n }\n }\n\n if (!existing['specPath']) {\n try {\n const specsDir = join(projectRoot, 'specs');\n if (existsSync(specsDir)) {\n const files = readdirSync(specsDir);\n const first = files[0];\n if (first) {\n existing['specPath'] = join('specs', first);\n }\n }\n } catch {\n // No specs dir — that's fine\n }\n }\n\n if (!existing['theme']) {\n try {\n const ymlPath = join(projectRoot, 'stackwright.yml');\n const ymlContent = readFileSync(ymlPath, 'utf-8');\n const match = /theme:\\s*\\n\\s+id:\\s*(.+)/.exec(ymlContent);\n if (match?.[1]) {\n existing['theme'] = match[1].trim();\n }\n } catch {\n // No stackwright.yml — that's fine\n }\n }\n\n existing['generatedBy'] = 'launch-raft';\n existing['version'] = '1.0';\n\n mkdirSync(stackwrightDir, { recursive: true });\n\n // Symlink guard — refuse to follow symlinks (prevents symlink-based overwrites)\n if (existsSync(stackwrightDir) && lstatSync(stackwrightDir).isSymbolicLink()) {\n die('.stackwright is a symlink — refusing to write. Check for tampering.');\n }\n if (existsSync(initContextPath) && lstatSync(initContextPath).isSymbolicLink()) {\n die('init-context.json is a symlink — refusing to write. Check for tampering.');\n }\n\n writeFileSync(initContextPath, JSON.stringify(existing, null, 2), 'utf-8');\n}\n\n// ─── Shutdown handler — release lock on exit ──────────────────────────────────\n\nprocess.on('exit', () => {\n try {\n releaseLock(process.cwd());\n } catch {\n // Lock release is best-effort on shutdown — don't block exit\n }\n});\nprocess.on('SIGINT', () => {\n try {\n releaseLock(process.cwd());\n } catch {\n // Lock release is best-effort on signal — don't block exit\n }\n process.exit(0);\n});\nprocess.on('SIGTERM', () => {\n try {\n releaseLock(process.cwd());\n } catch {\n // Lock release is best-effort on signal — don't block exit\n }\n process.exit(0);\n});\n\n// ─── Trusted Binary Directories ────────────────────────────────────────────\n\n/**\n * Returns the ordered list of trusted installation directories to probe\n * for raft-puppy / code-puppy before falling back to $PATH (`which`).\n *\n * These represent well-known, user-controlled install locations that a\n * malicious npm postinstall script cannot shadow by merely prepending to\n * $PATH — providing a meaningful defence against CWE-426 (Untrusted\n * Search Path). raft-puppy / code-puppy are Python tools, so pip user\n * install (~/.local/bin), brew, and system package managers cover all\n * supported install methods.\n *\n * Exported for unit testing.\n */\nexport function getTrustedBinaryDirs(): string[] {\n return [\n join(homedir(), '.local', 'bin'), // pip user install, uvx, pipx (Linux/macOS)\n '/opt/homebrew/bin', // brew Apple Silicon\n '/usr/local/bin', // brew Intel, pip system install\n '/usr/bin', // system package managers (apt, dnf)\n ];\n}\n\n// ─── Find code-puppy Executable ─────────────────────────────────────────────\n\n/**\n * Result of findCodePuppy() — includes the resolved real path AND an open\n * O_RDONLY file descriptor to close the TOCTOU window between validation\n * and spawn. Keep the fd alive until spawn() returns (Linux: exec via\n * /proc/self/fd/N pins the inode; macOS: fd is best-effort, residual\n * TOCTOU documented).\n */\nexport interface FindCodePuppyResult {\n /** Resolved real path (symlinks followed) — use for display/logging only on Linux */\n path: string;\n /** Open O_RDONLY fd — caller MUST closeSync(fd) after spawn() returns */\n fd: number;\n}\n\nexport function findCodePuppy(trustedDirsOverride?: string[]): FindCodePuppyResult {\n let candidate: string | null = null;\n\n // 1. Explicit env var override — highest priority\n const envPath = process.env['STACKWRIGHT_CODE_PUPPY_PATH'];\n if (envPath) {\n candidate = envPath;\n }\n\n // 2. Trusted installation paths — probe before falling back to $PATH.\n // Prevents CWE-426: a malicious npm postinstall script cannot shadow\n // these paths by merely prepending to $PATH.\n if (!candidate) {\n const trustedDirs = trustedDirsOverride ?? getTrustedBinaryDirs();\n outer: for (const bin of ['raft-puppy', 'code-puppy']) {\n for (const dir of trustedDirs) {\n const p = join(dir, bin);\n if (existsSync(p)) {\n candidate = p;\n break outer;\n }\n }\n }\n }\n\n // 3. Prefer raft-puppy (stackwright-puppy fork) over vanilla code-puppy.\n // raft-puppy ships the MCP auto-enable fix and local .code-puppy.json\n // loading — both required for the raft to work on a clean install.\n // Falls back to code-puppy so existing installs don't break.\n if (!candidate) {\n for (const bin of ['raft-puppy', 'code-puppy']) {\n // spawnSync avoids spawning a shell — args passed directly to execvp,\n // no shell interpolation, no injection surface (CWE-78 / detect-child-process).\n const whichResult = spawnSync('which', [bin], { encoding: 'utf-8' });\n if (whichResult.status === 0 && whichResult.stdout) {\n candidate = whichResult.stdout.trim();\n if (candidate) break;\n }\n }\n }\n\n if (!candidate) {\n die(\n 'raft-puppy (or code-puppy) not found.\\n' +\n '\\n' +\n 'Install the Stackwright-patched build (recommended):\\n' +\n ' pip install stackwright-puppy\\n' +\n '\\n' +\n 'Or install vanilla code-puppy (MCP tools may not auto-start):\\n' +\n ' pip install code-puppy\\n' +\n '\\n' +\n 'Or set STACKWRIGHT_CODE_PUPPY_PATH to the binary path.'\n );\n }\n\n // Resolve to absolute path — prevents bare-name or relative-path shenanigans\n const resolved = resolve(candidate);\n\n if (!existsSync(resolved)) {\n die(`raft-puppy/code-puppy not found at resolved path: ${resolved}`);\n }\n\n // Follow the full symlink chain to the real binary.\n // pip/pipx/uvx installs create a wrapper symlink in ~/.local/bin/ that points\n // to the actual binary inside a virtualenv — this is expected and safe.\n let realBinary: string;\n try {\n realBinary = realpathSync(resolved);\n } catch {\n die(\n `raft-puppy at ${resolved} has a broken symlink — cannot resolve to a real path. ` +\n `Try reinstalling stackwright-puppy or set STACKWRIGHT_CODE_PUPPY_PATH to the real binary.`\n );\n }\n\n if (!existsSync(realBinary)) {\n die(`raft-puppy symlink at ${resolved} points to a missing file: ${realBinary}`);\n }\n\n // Open an fd BEFORE permission checks — fstatSync(fd) operates on the already-open\n // inode, so the checks and the spawn target cannot diverge. CWE-367 mitigation.\n let fd: number;\n try {\n fd = openSync(realBinary, 'r');\n } catch (err) {\n die(\n `Cannot open raft-puppy at ${realBinary} for validation: ${String(err)}. ` +\n `Try reinstalling stackwright-puppy or set STACKWRIGHT_CODE_PUPPY_PATH.`\n );\n }\n\n // Use fstatSync(fd) — operates on the pinned inode, not the filename.\n const stat = fstatSync(fd!);\n\n // Refuse world-writable or group-writable binaries\n if (stat.mode & 0o022) {\n closeSync(fd!);\n die(\n `raft-puppy at ${realBinary} is group- or world-writable (mode: ${(stat.mode & 0o777).toString(8)}) — refusing to exec.`\n );\n }\n\n // Refuse setuid/setgid binaries\n if (stat.mode & 0o6000) {\n closeSync(fd!);\n die(`raft-puppy at ${realBinary} has setuid/setgid bits — refusing to exec.`);\n }\n\n return { path: realBinary, fd: fd! };\n}\n\n// ─── Pre-flight Environment Validation ──────────────────────────────────────\n\nfunction parseSemver(version: string): [number, number, number] {\n const parts = version.split('-')[0]!.split('.').map(Number);\n return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];\n}\n\nfunction semverGte(a: string, b: string): boolean {\n const [aMaj, aMin, aPatch] = parseSemver(a);\n const [bMaj, bMin, bPatch] = parseSemver(b);\n if (aMaj !== bMaj) return aMaj > bMaj;\n if (aMin !== bMin) return aMin > bMin;\n return aPatch >= bPatch;\n}\n\n/**\n * Validates the runtime environment before spawning code-puppy.\n * Calls die() (process.exit(1)) with a human-readable remediation message\n * on failure — never reaches the spawn if a check fails.\n *\n * Auth checking is intentionally NOT done here — code-puppy/raft-puppy owns\n * the auth layer and will surface a clear error at spawn time for any\n * missing/invalid credentials. This keeps the raft agnostic to model\n * providers (Anthropic API key, claude auth login OAuth, AWS Bedrock,\n * Google Vertex, Ollama, air-gapped inference, etc.).\n *\n * Checks:\n * 1. STACKWRIGHT_SKIP_PREFLIGHT — if 'true', skip all checks and return\n * 2. code-puppy --version reports >= MIN_SUPPORTED_CODE_PUPPY_VERSION\n */\nexport function validateBinaryVersion(binaryPath: string): void {\n // Escape hatch for air-gapped / custom model-provider deployments.\n // Set STACKWRIGHT_SKIP_PREFLIGHT=true to bypass pre-flight checks.\n if (process.env['STACKWRIGHT_SKIP_PREFLIGHT'] === 'true') {\n log('Pre-flight checks skipped via STACKWRIGHT_SKIP_PREFLIGHT');\n return;\n }\n\n // 1. Version check — run binary with --version, parse semver, compare\n // binaryPath is validated by findCodePuppy(): realpathSync'd, symlink-checked,\n // mode/setuid-checked, and confirmed to exist. spawnSync passes args directly\n // to execvp — no shell spawned, no quoting needed (CWE-78).\n // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process\n const versionStartTime = Date.now();\n const versionResult = spawnSync(binaryPath, ['--version'], {\n encoding: 'utf-8',\n // 15s: uv-installed Python tools cold-start (venv activation + interpreter\n // startup) can take 5-10s on first invocation; subsequent runs are ms.\n timeout: 15000,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const versionElapsed = Date.now() - versionStartTime;\n\n if (versionResult.error !== undefined || versionResult.status !== 0) {\n const errorDetail = versionResult.error\n ? `${(versionResult.error as NodeJS.ErrnoException).code ?? 'unknown'}: ${versionResult.error.message}`\n : `exit status ${String(versionResult.status)}`;\n const stdoutSnip = (versionResult.stdout ?? '').trim() || '<empty>';\n const stderrSnip = (versionResult.stderr ?? '').trim() || '<empty>';\n die(\n `Could not determine raft-puppy / code-puppy version.\\n` +\n ` Binary: ${binaryPath}\\n` +\n ` Failure: ${errorDetail}\\n` +\n ` Elapsed: ${String(versionElapsed)}ms (timeout: 15000ms)\\n` +\n ` Stdout: ${stdoutSnip}\\n` +\n ` Stderr: ${stderrSnip}\\n` +\n ` Required: ${MIN_SUPPORTED_CODE_PUPPY_VERSION}\\n` +\n `\\n` +\n ` Run: pip install --upgrade stackwright-puppy\\n` +\n ` Or: pip install --upgrade code-puppy\\n` +\n `\\n` +\n ` If the binary works manually but fails here, set\\n` +\n ` STACKWRIGHT_SKIP_PREFLIGHT=true to bypass this check.`\n );\n }\n\n // Some CLIs print --version to stderr instead of stdout. Accept either;\n // prefer stdout, fall back to stderr if stdout is empty.\n const versionStdout = versionResult.stdout.trim();\n const versionStderr = versionResult.stderr.trim();\n const versionOutput = versionStdout || versionStderr;\n\n const match = /(\\d+\\.\\d+\\.\\d+(?:-[^\\s]+)?)/.exec(versionOutput);\n if (!match || !match[1]) {\n die(\n `Could not parse version from raft-puppy / code-puppy output.\\n` +\n ` Binary: ${binaryPath}\\n` +\n ` Stdout: ${JSON.stringify(versionStdout) || '<empty>'}\\n` +\n ` Stderr: ${JSON.stringify(versionStderr) || '<empty>'}\\n` +\n ` Required: ${MIN_SUPPORTED_CODE_PUPPY_VERSION}\\n` +\n `\\n` +\n ` Run: pip install --upgrade stackwright-puppy\\n` +\n ` Or: pip install --upgrade code-puppy`\n );\n }\n\n const installedVersion = match[1];\n\n // Dev builds self-identify as 0.0.0-dev (or bare 0.0.0) — treat as a\n // local-source build and warn instead of hard-failing the version gate.\n // Any real published release should use a proper semver (e.g. 0.1.0-alpha.1).\n // NOTE: 0.0.0 bare is also treated as dev — it's never a valid published version.\n const isDevBuild = installedVersion === '0.0.0' || /^0\\.0\\.0-.+$/.test(installedVersion);\n if (isDevBuild) {\n console.warn(\n `⚠️ Dev build detected (${installedVersion}) — skipping minimum version check.\\n` +\n ` Minimum required for production: ${MIN_SUPPORTED_CODE_PUPPY_VERSION}\\n` +\n ` To suppress this warning in CI: set STACKWRIGHT_SKIP_PREFLIGHT=true`\n );\n return;\n }\n\n if (!semverGte(installedVersion, MIN_SUPPORTED_CODE_PUPPY_VERSION)) {\n die(\n `raft-puppy / code-puppy ${installedVersion} is below the minimum required version.\\n` +\n ` Installed: ${installedVersion}\\n` +\n ` Minimum required: ${MIN_SUPPORTED_CODE_PUPPY_VERSION}\\n` +\n `\\n` +\n ` Run: pip install --upgrade stackwright-puppy\\n` +\n ` Or: pip install --upgrade code-puppy`\n );\n }\n}\n\n// ─── Ensure MCP Config ─────────────────────────────────────────────────────\n\nexport function ensureMcpConfig(projectRoot: string): void {\n const workspaceDir = join(projectRoot, '.code_puppy');\n const configPath = join(workspaceDir, 'mcp_servers.json');\n\n // Symlink guard — consistent with other security patterns\n if (existsSync(configPath) && lstatSync(configPath).isSymbolicLink()) {\n die('.code_puppy/mcp_servers.json is a symlink — refusing to write. Check for tampering.');\n }\n\n mkdirSync(workspaceDir, { recursive: true });\n\n // Read existing → merge, don't clobber other registered servers\n let existing: { mcp_servers?: Record<string, unknown> } = {};\n if (existsSync(configPath)) {\n try {\n const raw = readFileSync(configPath, 'utf-8');\n existing = JSON.parse(raw) as { mcp_servers?: Record<string, unknown> };\n } catch {\n // Malformed file — start fresh\n }\n }\n\n // Resolve cwd at write time using the projectRoot parameter already in scope.\n // A literal '${PROJECT_ROOT}' placeholder was previously written here under the\n // assumption that raft-puppy would expand it at runtime — it doesn't.\n // Writing the real path avoids ENOENT when the MCP server is spawned.\n const serverConfig = {\n type: 'stdio',\n command: 'pnpm',\n args: ['exec', 'stackwright-pro-mcp'],\n enabled: true,\n cwd: projectRoot,\n };\n\n const merged = {\n ...existing,\n mcp_servers: {\n ...(existing.mcp_servers ?? {}),\n 'stackwright-pro-mcp': serverConfig,\n },\n };\n\n // Atomic write via .tmp rename swap\n const tmpPath = `${configPath}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(merged, null, 2), 'utf-8');\n renameSync(tmpPath, configPath);\n\n log('MCP server registered → .code_puppy/mcp_servers.json');\n}\n\n// ─── Ensure Workspace Config ─────────────────────────────────────────────────\n\n/**\n * Write .code_puppy/config.json with profile: \"strict-local\".\n *\n * code-puppy ≥ 0.0.575 uses a profile-based workspace scoping system\n * (the `project_workspace` builtin plugin). `strict-local` scopes ALL\n * six extension surfaces — agents, skills, plugins, mcp, hooks,\n * file_permissions — to the project directory only. This ensures raft\n * runs are fully isolated from the user's global ~/.code_puppy/ config,\n * which is raft's core reproducibility guarantee.\n *\n * Merges with any existing config.json without clobbering other fields.\n * Uses atomic write (.tmp rename swap) and symlink guards throughout.\n */\nexport function ensureWorkspaceConfig(projectRoot: string): void {\n const workspaceDir = join(projectRoot, '.code_puppy');\n const configPath = join(workspaceDir, 'config.json');\n\n // Symlink guard on the .code_puppy/ dir itself\n if (existsSync(workspaceDir) && lstatSync(workspaceDir).isSymbolicLink()) {\n die('.code_puppy/ is a symlink — refusing to write. Check for tampering.');\n }\n\n mkdirSync(workspaceDir, { recursive: true });\n\n // Symlink guard on config.json\n if (existsSync(configPath) && lstatSync(configPath).isSymbolicLink()) {\n die('.code_puppy/config.json is a symlink — refusing to write. Check for tampering.');\n }\n\n // Merge — don't clobber existing fields\n let existing: Record<string, unknown> = {};\n if (existsSync(configPath)) {\n try {\n existing = JSON.parse(readFileSync(configPath, 'utf-8')) as Record<string, unknown>;\n } catch {\n // Malformed — start fresh\n }\n }\n\n const merged = {\n ...existing,\n $schema: 'https://stackwright.dev/schemas/workspace-config/v2.json',\n profile: 'strict-local',\n };\n\n // Atomic write via .tmp rename swap\n const tmpPath = `${configPath}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(merged, null, 2), 'utf-8');\n renameSync(tmpPath, configPath);\n\n log('Workspace config written → .code_puppy/config.json (profile: strict-local)');\n}\n\n// ─── Print Resume Status ────────────────────────────────────────────────────\n\nexport function printResumeStatus(projectRoot: string): void {\n const pipelineStatePath = join(projectRoot, '.stackwright', 'pipeline-state.json');\n\n try {\n const raw = readFileSync(pipelineStatePath, 'utf-8');\n const state = JSON.parse(raw) as Record<string, unknown>;\n const status = state['status'];\n const phases = state['phases'];\n\n if (typeof phases !== 'object' || phases === null || Array.isArray(phases)) {\n return;\n }\n\n const phaseEntries = Object.values(phases as Record<string, Record<string, unknown>>);\n const totalPhases = phaseEntries.length || 8;\n\n switch (status) {\n case 'setup':\n log('📍 Starting fresh');\n break;\n case 'questions': {\n const answered = phaseEntries.filter((p) => p['answered'] === true).length;\n log(`📍 Resuming: questions phase (${answered}/${totalPhases} phases answered)`);\n break;\n }\n case 'execution': {\n const executed = phaseEntries.filter((p) => p['executed'] === true).length;\n log(`📍 Resuming: execution phase (${executed}/${totalPhases} phases complete)`);\n break;\n }\n case 'done':\n log('📍 Pipeline complete — re-entering to review');\n break;\n default:\n break;\n }\n } catch {\n // No pipeline state yet — fresh project\n }\n}\n\n// ─── Pipeline Resumability Check ─────────────────────────────────────────────\n\n/**\n * Returns true if the pipeline is in a state that can be automatically resumed.\n *\n * Used by the retry/backoff logic in index.ts to decide whether a non-zero\n * code-puppy exit warrants a re-spawn. 'questions' and 'execution' statuses\n * mean the Foreman was mid-run; 'done' means it finished cleanly; no file\n * means it never started (no point retrying a blank slate).\n */\nexport function isPipelineResumable(projectRoot: string): boolean {\n const pipelineStatePath = join(projectRoot, '.stackwright', 'pipeline-state.json');\n try {\n const raw = readFileSync(pipelineStatePath, 'utf-8');\n const state = JSON.parse(raw) as Record<string, unknown>;\n const status = state['status'];\n return status === 'questions' || status === 'execution';\n } catch {\n return false;\n }\n}\n\n// ─── Resolve Otter Directory ────────────────────────────────────────────────\n\nexport function resolveOtterDir(projectRoot: string): string | null {\n const candidates = [\n join(projectRoot, 'node_modules', '@stackwright-pro', 'otters', 'src'),\n join(projectRoot, 'packages', 'otters', 'src'),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\n// ─── Sync Agents ────────────────────────────────────────────────────────────\n\n/**\n * Copy all *-otter.json files from the resolved otters package to\n * .code_puppy/agents/ on every raft startup.\n *\n * This mirrors the pattern used by ensureMcpConfig() for mcp_servers.json.\n * Relying solely on the postinstall hook in @stackwright-pro/otters is fragile:\n * pnpm/npm don't always re-run scripts on package updates, and postinstall\n * order is non-deterministic when multiple packages run scripts. Active sync\n * on every raft launch guarantees agents are always the installed version.\n */\nexport function syncAgents(projectRoot: string, isVerbose: boolean = false): void {\n const agentsDir = join(projectRoot, '.code_puppy', 'agents');\n\n const otterDir = resolveOtterDir(projectRoot);\n if (!otterDir) {\n verbose('No otters directory found — skipping agent sync', isVerbose);\n return;\n }\n\n // Symlink guard on agents dir\n if (existsSync(agentsDir) && lstatSync(agentsDir).isSymbolicLink()) {\n die('.code_puppy/agents is a symlink — refusing to write. Check for tampering.');\n }\n\n mkdirSync(agentsDir, { recursive: true });\n\n let synced = 0;\n let skipped = 0;\n\n try {\n const files = readdirSync(otterDir);\n for (const file of files) {\n if (!file.endsWith('-otter.json')) continue;\n\n const src = join(otterDir, file);\n const dest = join(agentsDir, file);\n\n // Symlink guard on individual file\n if (existsSync(dest) && lstatSync(dest).isSymbolicLink()) {\n verbose(`Skipping ${file} — dest is a symlink`, isVerbose);\n skipped++;\n continue;\n }\n\n // Atomic write: copy to .tmp then rename\n const tmp = `${dest}.tmp`;\n const content = readFileSync(src);\n writeFileSync(tmp, content);\n renameSync(tmp, dest);\n verbose(`Synced: ${file}`, isVerbose);\n synced++;\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n // Non-fatal: log and continue — a stale agent is better than a crash\n console.warn(`⚠️ Agent sync partial failure: ${msg}`);\n return;\n }\n\n if (synced > 0) {\n log(\n `Agents synced → .code_puppy/agents/ (${synced} otters${skipped > 0 ? `, ${skipped} skipped` : ''})`\n );\n }\n}\n\n// ─── Spawn Config Builder ───────────────────────────────────────────────────\n\n/**\n * Build the spawn target and stdio config for code-puppy/raft-puppy.\n *\n * On Linux: pass the validated binary fd through stdio at index 3 (dup2 clears\n * O_CLOEXEC), then exec via /proc/self/fd/3. This works for both ELF and\n * shebang (Python) scripts — the fd survives the interpreter's execve.\n *\n * On macOS/other: /proc does not exist — fall back to the validated path.\n * Residual TOCTOU on macOS is documented and accepted.\n *\n * Extracted from main() for testability.\n *\n * @param platform - process.platform value\n * @param executable - resolved binary path (from findCodePuppy)\n * @param binaryFd - open fd to the binary (from findCodePuppy)\n */\nexport function buildSpawnConfig(\n platform: string,\n executable: string,\n binaryFd: number\n): { spawnTarget: string; stdioConfig: import('child_process').StdioOptions } {\n const PINNED_CHILD_FD = 3;\n\n if (platform === 'linux') {\n return {\n spawnTarget: `/proc/self/fd/${PINNED_CHILD_FD}`,\n stdioConfig: ['inherit', 'inherit', 'inherit', binaryFd],\n };\n }\n\n return {\n spawnTarget: executable,\n stdioConfig: 'inherit',\n };\n}\n"],"mappings":"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","names":["import_fs","import_path","import_child_process"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/lib.ts","../src/dev-server.ts"],"sourcesContent":["/**\n * @stackwright-pro/raft — Launch the Pro Otter Raft\n *\n * Writes init-context, verifies otter integrity, and spawns code-puppy\n * in foreman mode. The MCP tools handle everything after that.\n *\n * Replaces the deprecated Python cli_adapter.py → ForemanSession → os.execvpe() chain.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, closeSync } from 'fs';\nimport { join, resolve } from 'path';\nimport { spawn } from 'child_process';\nimport { verifyAllOtters } from '@stackwright-pro/mcp/integrity';\nimport { buildTypeSchemaSummary } from '@stackwright-pro/mcp/type-schemas';\nimport {\n parseArgs,\n printHelp,\n writeInitContext,\n findCodePuppy,\n validateBinaryVersion,\n migrateWorkspaceDir,\n ensureWorkspaceConfig,\n ensureMcpConfig,\n syncAgents,\n printResumeStatus,\n isPipelineResumable,\n resolveOtterDir,\n buildCodePuppyEnv,\n buildSpawnConfig,\n die,\n log,\n verbose,\n} from './lib.js';\nimport { withDevServer } from './dev-server.js';\n\n// ─── Retry / Backoff Constants ───────────────────────────────────────────────\n\n/** Max automatic re-spawn attempts after an unexpected non-zero exit. */\nconst MAX_RETRIES = 3;\n/** Initial backoff delay in ms. Doubles on each attempt, capped at MAX_BACKOFF_MS. */\nconst BACKOFF_INITIAL_MS = 5_000;\n/** Hard ceiling on backoff delay. */\nconst BACKOFF_MAX_MS = 60_000;\n\n/** Exponential backoff: 5s → 10s → 20s → (cap) 60s. */\nfunction backoffMs(attempt: number): number {\n return Math.min(BACKOFF_INITIAL_MS * Math.pow(2, attempt), BACKOFF_MAX_MS);\n}\n\n// ─── Main ────────────────────────────────────────────────────────────────────\n\nasync function main(): Promise<void> {\n const args = parseArgs(process.argv);\n\n if (args.help) {\n printHelp();\n process.exit(0);\n }\n\n const projectRoot = resolve(args.projectRoot);\n if (!existsSync(projectRoot)) {\n die(`Project root does not exist: ${projectRoot}`);\n }\n\n // Sanity check: is this a Stackwright project?\n if (!existsSync(join(projectRoot, 'package.json'))) {\n die('No package.json found. Run npx @stackwright-pro/launch-stackwright-pro first.');\n }\n\n log('Launching Pro Otter Raft...');\n\n // 1. Write/enrich init context\n writeInitContext(projectRoot);\n verbose('Init context written', args.verbose);\n\n // 1a. Write type schemas sink for foreman routing\n try {\n const stackwrightDir = join(projectRoot, '.stackwright');\n mkdirSync(stackwrightDir, { recursive: true });\n const schemaSummary = buildTypeSchemaSummary();\n writeFileSync(\n join(stackwrightDir, 'type-schemas.json'),\n JSON.stringify(schemaSummary, null, 2) + '\\n'\n );\n verbose('Type schemas sink written', args.verbose);\n } catch (err) {\n // Non-blocking — foreman can still operate without type-schemas.json\n console.warn('⚠️ Could not write type-schemas.json:', String(err));\n }\n\n // 1b. Write raft flags to init-context for foreman consumption\n {\n const stackwrightDir = join(projectRoot, '.stackwright');\n const initContextPath = join(stackwrightDir, 'init-context.json');\n\n // Merge flags into existing init-context (writeInitContext already created it)\n const initRaw = readFileSync(initContextPath, 'utf-8');\n const initCtx = JSON.parse(initRaw) as Record<string, unknown>;\n\n if (args.nonInteractive) initCtx['nonInteractive'] = true;\n if (args.devOnly) initCtx['devOnly'] = true;\n\n // QA gate flags (swp-sc4x / Bead F)\n initCtx['skipQa'] = args.skipQa;\n initCtx['qaOnly'] = args.qaOnly;\n initCtx['devServer'] = {\n managed: true,\n baseUrl: 'http://localhost:3000',\n timeoutMs: args.qaDevServerTimeoutMs,\n };\n\n writeFileSync(initContextPath, JSON.stringify(initCtx, null, 2) + '\\n', 'utf-8');\n verbose('Raft flags written to init-context', args.verbose);\n }\n\n // 1c. Seed build context from --use-case file or generic fallback\n {\n const buildContextPath = join(projectRoot, '.stackwright', 'build-context.json');\n const MAX_USE_CASE_BYTES = 1 * 1024 * 1024; // 1MB\n const WARN_USE_CASE_BYTES = 200 * 1024; // 200KB\n\n if (args.useCasePath) {\n const resolvedPath = resolve(args.useCasePath);\n if (!existsSync(resolvedPath)) {\n die(`Use-case file not found: ${resolvedPath}`);\n }\n\n const content = readFileSync(resolvedPath, 'utf-8');\n if (content.length > MAX_USE_CASE_BYTES) {\n die(\n `Use-case file exceeds ${(MAX_USE_CASE_BYTES / 1024 / 1024).toFixed(0)}MB ` +\n `(got ${(content.length / 1024).toFixed(0)}KB). Trim the file or split into sections.`\n );\n }\n if (content.length > WARN_USE_CASE_BYTES) {\n console.warn(\n `⚠️ Large use-case file (${(content.length / 1024).toFixed(0)}KB) — ` +\n `specialist otters will receive the full context via build prompts.`\n );\n }\n\n const payload = {\n version: '1.0',\n savedAt: new Date().toISOString(),\n buildContext: content,\n };\n writeFileSync(buildContextPath, JSON.stringify(payload, null, 2) + '\\n', 'utf-8');\n log(`Build context seeded from ${args.useCasePath}`);\n } else if (args.nonInteractive && !existsSync(buildContextPath)) {\n // Generic fallback for --non-interactive without --use-case\n const payload = {\n version: '1.0',\n savedAt: new Date().toISOString(),\n buildContext:\n 'Build a sample application from the available API specs and data sources ' +\n 'with default settings. Use all available endpoints and standard page layouts.',\n };\n writeFileSync(buildContextPath, JSON.stringify(payload, null, 2) + '\\n', 'utf-8');\n log('Build context seeded with generic defaults (--non-interactive, no --use-case)');\n }\n }\n\n // 1d. Migrate legacy .code-puppy/ to .code_puppy/ if needed\n migrateWorkspaceDir(projectRoot);\n\n // 1e. Ensure workspace config — write .code_puppy/config.json (profile: strict-local)\n // Must come before ensureMcpConfig so the workspace dir exists.\n ensureWorkspaceConfig(projectRoot);\n\n // 1f. Register MCP server — do this BEFORE any die() calls so clean installs\n // get MCP config even if otter install is incomplete\n ensureMcpConfig(projectRoot);\n\n // 1g. Sync agent files from installed @stackwright-pro/otters — always reflects\n // the installed version without relying on postinstall hooks\n syncAgents(projectRoot, args.verbose);\n\n // 2. Verify otter integrity\n const otterDir = resolveOtterDir(projectRoot);\n if (!otterDir) {\n die(\n 'Could not find otter directory. Is @stackwright-pro/otters installed?\\n' +\n ' Run: pnpm add @stackwright-pro/otters'\n );\n }\n\n const result = verifyAllOtters(otterDir);\n if (result.failed.length > 0) {\n console.warn('⚠️ Otter integrity check warnings (non-blocking):');\n for (const f of result.failed) {\n console.warn(` ${f.filename}: ${f.error}`);\n }\n console.warn(' Note: SHA-256 pinning will be replaced by PKI-signed deployment manifests.');\n console.warn(\n ' See: https://github.com/Per-Aspera-LLC/stackwright-pro/issues (signing model issue)'\n );\n } else {\n log(`✅ All ${result.verified.length} otters verified`);\n }\n\n // 3. Print resume status\n printResumeStatus(projectRoot);\n\n // 4. Resolve raft-puppy / code-puppy and validate version (once, before retry loop)\n const { path: executable, fd: initialFd } = findCodePuppy();\n verbose(`Resolved raft-puppy / code-puppy: ${executable}`, args.verbose);\n try {\n closeSync(initialFd);\n } catch {\n /* best-effort */\n }\n\n // 4a. Pre-flight: validate binary version before spawning\n validateBinaryVersion(executable);\n\n // 5. Spawn raft-puppy / code-puppy — with automatic retry on unexpected exits.\n //\n // If code-puppy exits with a non-zero code (e.g. Cloudflare 400 mid-session,\n // transient API error) AND the pipeline state is resumable (status = 'questions'\n // or 'execution'), we wait with exponential backoff then re-spawn with a \"Resume\"\n // prompt. The Foreman reads pipeline-state.json and picks up where it left off.\n //\n // SIGINT/SIGTERM from the user are NOT retried — they abort cleanly.\n // ── QA-only short-circuit (swp-sc4x / Bead F) ────────────────────────────\n // --qa-only: skip the full otter pipeline and jump straight to the visual QA gate.\n // The withDevServer bracket is live; the QA otter invocation lands in swp-wde8.\n if (args.qaOnly) {\n log('--qa-only mode: skipping otter pipeline, spawning dev server for visual QA...');\n await withDevServer(projectRoot, { timeoutMs: args.qaDevServerTimeoutMs }, async (info) => {\n // TODO(swp-wde8): invoke QA otter directly via code-puppy.\n // For now, log info and exit — the bracket is wired and tested.\n log('--qa-only: dev server ready ✅');\n log(` pid: ${info.pid}`);\n log(` baseUrl: ${info.baseUrl}`);\n if (info.prismUrl) log(` prismUrl: ${info.prismUrl}`);\n log(` logPath: ${info.logPath}`);\n log('⏸ QA otter invocation not yet implemented (blocked on swp-wde8 + swp-kjkg)');\n });\n process.exit(0);\n }\n\n log('Spawning raft-puppy / code-puppy raft session...');\n\n let retryCount = 0;\n let userInterrupted = false;\n\n function spawnSession(isResume: boolean): void {\n // Re-open the binary on each spawn attempt so we get a fresh inode lock.\n // On Linux: spawn via /proc/self/fd/N — the fd was opened (and inode-checked) by\n // findCodePuppy(), so an attacker cannot swap the binary between validation and exec.\n //\n // Shebang-aware: raft-puppy is a Python script, not an ELF binary. The kernel's\n // shebang handler re-execs the interpreter (python) with the fd path as a string\n // argument. O_CLOEXEC (set by Node's openSync) would close the fd during that exec,\n // causing Python to get ENOENT on /proc/self/fd/N.\n //\n // Fix: pass the fd through the stdio array at index 3. dup2() clears O_CLOEXEC,\n // so fd 3 survives the interpreter exec. We then spawn via /proc/self/fd/3.\n //\n // On macOS: /proc does not exist — we fall back to the validated path.\n // Residual TOCTOU on macOS is documented and accepted for the current threat model.\n // See: CWE-367, GH#128, beads issue stackwright-pro-s1g.\n const { path: resolvedBinary, fd: binaryFd } = findCodePuppy();\n\n if (process.platform !== 'linux') {\n verbose(\n `macOS/other: spawning via path (residual TOCTOU — /proc unavailable). ` +\n `See GH#128 for threat model documentation.`,\n args.verbose\n );\n }\n\n // First run: \"Begin\". Retry runs: \"Resume\" so the Foreman reads pipeline-state.json.\n const prompt = isResume ? 'Resume' : 'Begin';\n const spawnArgs = [prompt, '--interactive', '--agent', 'stackwright-pro-foreman-otter'];\n\n const { spawnTarget, stdioConfig } = buildSpawnConfig(\n process.platform,\n resolvedBinary,\n binaryFd\n );\n\n verbose(\n `cmd: ${spawnTarget} ${spawnArgs.join(' ')}` +\n `${spawnTarget !== resolvedBinary ? ` (real: ${resolvedBinary})` : ''}` +\n `${isResume ? ` [retry ${retryCount}/${MAX_RETRIES}]` : ''}`,\n args.verbose\n );\n\n const child = spawn(spawnTarget, spawnArgs, {\n stdio: stdioConfig,\n cwd: projectRoot,\n env: buildCodePuppyEnv({ STACKWRIGHT_PROJECT_ROOT: projectRoot }, args.passEnvKeys),\n });\n\n // Close the parent's copy of the fd. On Linux the child inherited it via dup2\n // to fd 3 (without O_CLOEXEC) so the child's copy survives exec independently.\n try {\n closeSync(binaryFd);\n } catch {\n /* best-effort */\n }\n\n // Forward signals to child — let it clean up gracefully.\n // Track interrupts so we DON'T retry on user-initiated termination.\n const forward = (signal: NodeJS.Signals): void => {\n if (child.pid) child.kill(signal);\n };\n const onSigint = (): void => {\n userInterrupted = true;\n forward('SIGINT');\n };\n const onSigterm = (): void => {\n userInterrupted = true;\n forward('SIGTERM');\n };\n process.on('SIGINT', onSigint);\n process.on('SIGTERM', onSigterm);\n\n child.on('error', (err) => die(`Failed to spawn raft-puppy / code-puppy: ${err.message}`));\n child.on('close', (code, signal) => {\n process.off('SIGINT', onSigint);\n process.off('SIGTERM', onSigterm);\n\n // Signal exit (e.g. SIGINT from user) — re-raise and exit cleanly\n if (signal) {\n process.kill(process.pid, signal);\n return;\n }\n\n // Clean exit or user-interrupted — done\n if (code === 0 || userInterrupted) {\n process.exit(code ?? 0);\n return;\n }\n\n // Non-zero exit — check if retry is warranted\n if (retryCount < MAX_RETRIES && isPipelineResumable(projectRoot)) {\n retryCount++;\n const delay = backoffMs(retryCount - 1);\n log(\n `⚠️ Raft session exited unexpectedly (code ${code ?? '?'}). ` +\n `Pipeline is mid-run — retrying in ${delay / 1_000}s… ` +\n `(attempt ${retryCount}/${MAX_RETRIES})`\n );\n setTimeout(() => spawnSession(true), delay);\n } else {\n if (retryCount >= MAX_RETRIES) {\n log(`❌ Max retries (${MAX_RETRIES}) reached — giving up. Check logs above for errors.`);\n }\n process.exit(code ?? 1);\n }\n });\n }\n\n // ── QA gate status log (swp-sc4x / Bead F) ──────────────────────────────\n // The dev-server lifecycle module is wired (withDevServer in dev-server.ts).\n // Actual QA otter invocation is deferred to Bead D (swp-wde8) + Bead E (swp-kjkg).\n if (args.skipQa) {\n log('⚠️ Visual QA skipped by --skip-qa flag');\n } else {\n log(\n '⏸ Visual QA stub — dev server lifecycle is wired, QA otter invocation lands in swp-wde8/swp-kjkg'\n );\n }\n\n spawnSession(false);\n}\n\nmain().catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err);\n die(`Unexpected fatal error: ${msg}`);\n});\n","/**\n * @stackwright-pro/raft — Pure utility functions\n *\n * Extracted from the CLI entry point so they're independently testable.\n * All side-effectful helpers (die, log, verbose) live here too —\n * the CLI entry point (`index.ts`) just wires them into `main()`.\n */\n\nimport { spawnSync } from 'child_process';\nimport {\n existsSync,\n lstatSync,\n mkdirSync,\n openSync,\n fstatSync,\n closeSync,\n readFileSync,\n readdirSync,\n realpathSync,\n renameSync,\n writeFileSync,\n rmSync,\n} from 'fs';\nimport { join, resolve } from 'path';\nimport { hostname, homedir, platform } from 'os';\n\n// ─── Code-Puppy Environment Allowlist ────────────────────────────────────────\n//\n// CWE-526: Sensitive environment variables must not be passed to child processes.\n// CVSS v4.0 ~3.0. The spawn call in index.ts MUST use buildCodePuppyEnv() — never\n// spread ...process.env directly.\n//\n// Only env vars in this allowlist (or matching the prefix list) are forwarded to\n// the code-puppy child process. AWS_*, GITHUB_TOKEN, DATABASE_URL, NPM_TOKEN, etc.\n// are silently stripped.\n\n/**\n * Exact env var names that code-puppy legitimately needs.\n */\nconst CODE_PUPPY_ENV_ALLOWLIST: ReadonlySet<string> = new Set([\n // Shell / execution environment\n 'PATH',\n 'HOME',\n 'USER',\n 'USERNAME',\n 'SHELL',\n 'TMPDIR',\n 'TMP',\n 'TEMP',\n // Terminal\n 'TERM',\n 'COLORTERM',\n 'COLUMNS',\n 'LINES',\n 'NO_COLOR',\n 'FORCE_COLOR',\n // Language / locale\n 'LANG',\n 'LC_ALL',\n 'LC_CTYPE',\n 'LC_MESSAGES',\n // Network proxy — needed for outbound LLM API calls behind corporate proxies\n 'HTTP_PROXY',\n 'HTTPS_PROXY',\n 'NO_PROXY',\n 'http_proxy',\n 'https_proxy',\n 'no_proxy',\n // LLM API keys — the only credentials code-puppy should have\n 'ANTHROPIC_API_KEY',\n 'ANTHROPIC_BASE_URL',\n 'OPENAI_API_KEY',\n // Node runtime\n 'NODE_ENV',\n // Stackwright-specific\n 'STACKWRIGHT_PROJECT_ROOT',\n 'STACKWRIGHT_SKIP_PREFLIGHT',\n 'STACKWRIGHT_CODE_PUPPY_PATH',\n]);\n\n/**\n * Prefix-based allowlist — any env var starting with these prefixes is allowed.\n * Covers families like PYTHONPATH, PYTHONHOME, LC_NUMERIC, etc.\n */\nconst CODE_PUPPY_ENV_PREFIXES: readonly string[] = ['PYTHON', 'LC_'];\n\n// ─── Pre-flight Constants ────────────────────────────────────────────────────\n\n/** Minimum code-puppy / raft-puppy version required by this raft release.\n *\n * raft-puppy / code-puppy uses 0.0.x patch-only versioning on PyPI (e.g. 0.0.575).\n *\n * Bump this when a new raft-puppy feature becomes a hard dependency.\n * 0.0.575 — first version with the project_workspace builtin plugin\n * (profile-based scoping). Older versions silently degrade to additive\n * merge mode, which breaks raft's reproducibility guarantee.\n *\n * Dev builds (0.0.0 / 0.0.0-*) are handled separately by the isDevBuild bypass.\n */\nexport const MIN_SUPPORTED_CODE_PUPPY_VERSION = '0.0.575';\n\n// ─── CLI Argument Parsing ────────────────────────────────────────────────────\n\nexport interface ParsedArgs {\n projectRoot: string;\n verbose: boolean;\n help: boolean;\n /** Keys explicitly passed via --pass-env flags. */\n passEnvKeys: string[];\n /** Run without TUI prompts — use sane defaults for all questions. */\n nonInteractive: boolean;\n /** Skip real auth config — use mock auth with roles derived from use case. */\n devOnly: boolean;\n /** Path to a use-case file that seeds the build context. */\n useCasePath: string | null;\n /** Skip the visual QA gate entirely (writes skipQa: true to init-context). */\n skipQa: boolean;\n /** Skip the otter pipeline and jump straight to visual QA (writes qaOnly: true). */\n qaOnly: boolean;\n /** Dev server startup timeout in ms for the visual QA gate. Default: 60000. */\n qaDevServerTimeoutMs: number;\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const args: ParsedArgs = {\n projectRoot: process.cwd(),\n verbose: false,\n help: false,\n passEnvKeys: [],\n nonInteractive: false,\n devOnly: false,\n useCasePath: null,\n skipQa: false,\n qaOnly: false,\n qaDevServerTimeoutMs: 60_000,\n };\n\n const raw = argv.slice(2);\n for (let i = 0; i < raw.length; i++) {\n const token = raw[i];\n switch (token) {\n case '--project-root':\n args.projectRoot = raw[++i] ?? die('--project-root requires a path argument');\n break;\n case '--verbose':\n args.verbose = true;\n break;\n case '--help':\n case '-h':\n args.help = true;\n break;\n case '--pass-env':\n args.passEnvKeys.push(raw[++i] ?? die('--pass-env requires a KEY argument'));\n break;\n case '--non-interactive':\n args.nonInteractive = true;\n break;\n case '--dev-only':\n args.devOnly = true;\n break;\n case '--use-case':\n args.useCasePath = raw[++i] ?? die('--use-case requires a file path argument');\n break;\n case '--skip-qa':\n args.skipQa = true;\n break;\n case '--qa-only':\n args.qaOnly = true;\n break;\n default:\n // --qa-dev-server-timeout-ms=<n> form\n if (token.startsWith('--qa-dev-server-timeout-ms=')) {\n const val = parseInt(token.split('=')[1] ?? '', 10);\n if (isNaN(val) || val <= 0) {\n die('--qa-dev-server-timeout-ms requires a positive integer (ms)');\n }\n args.qaDevServerTimeoutMs = val;\n break;\n }\n die(`Unknown option: ${token}\\nRun with --help for usage.`);\n }\n }\n\n return args;\n}\n\nexport function printHelp(): void {\n console.log(\n `\n🦦 @stackwright-pro/raft — Spawn raft-puppy (or code-puppy) in foreman mode\n\nUsage: launch-raft [options]\n\nOptions:\n --project-root <path> Project root directory (default: cwd)\n --verbose Enable verbose logging\n --pass-env <KEY> Forward an additional env var to code-puppy (repeatable)\n --non-interactive Skip all TUI prompts, use sane defaults for questions\n --dev-only Mock auth only — roles derived from use case, no real providers\n --use-case <file> Seed \"what to build\" from a file (plain text or markdown)\n --skip-qa Skip the visual QA gate (useful for CI or fast iterations)\n --qa-only Skip the otter pipeline; spawn dev server and run QA directly\n --qa-dev-server-timeout-ms=<n> Dev server startup timeout in ms for QA gate (default: 60000)\n --help, -h Show this help\n\nPrerequisites:\n pip install stackwright-puppy # provides raft-puppy + code-puppy alias\n Or: pip install code-puppy # fallback (MCP tools may not auto-start)\n`.trim()\n );\n}\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n\nexport function die(message: string): never {\n console.error(`❌ ${message}`);\n process.exit(1);\n}\n\nexport function log(message: string): void {\n console.log(`🦦 ${message}`);\n}\n\nexport function verbose(message: string, isVerbose: boolean): void {\n if (isVerbose) {\n console.log(` ${message}`);\n }\n}\n\n/**\n * Build a restricted environment for the code-puppy child process.\n *\n * Filters `process.env` to only keys in CODE_PUPPY_ENV_ALLOWLIST or matching\n * CODE_PUPPY_ENV_PREFIXES, then merges in `extraVars` (always wins) and any\n * keys explicitly requested via `--pass-env` (escape hatch).\n *\n * CWE-526 mitigation: credentials like AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN,\n * DATABASE_URL, NPM_TOKEN, etc. are silently excluded.\n *\n * @param extraVars Vars to force-inject (e.g. STACKWRIGHT_PROJECT_ROOT).\n * @param passEnvKeys Additional keys from --pass-env flags.\n */\nexport function buildCodePuppyEnv(\n extraVars: Record<string, string> = {},\n passEnvKeys: string[] = []\n): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {};\n\n for (const [key, value] of Object.entries(process.env)) {\n if (value === undefined) continue;\n\n const allowed =\n CODE_PUPPY_ENV_ALLOWLIST.has(key) ||\n CODE_PUPPY_ENV_PREFIXES.some((prefix) => key.startsWith(prefix)) ||\n passEnvKeys.includes(key);\n\n if (allowed) {\n env[key] = value;\n }\n }\n\n // extraVars always win — they're injected by the raft, not from the parent env\n for (const [key, value] of Object.entries(extraVars)) {\n env[key] = value;\n }\n\n return env;\n}\n\n// ─── Pipeline Lock ──────────────────────────────────────────────────────────\n\nconst LOCK_FILE = '.stackwright/.lock';\n\nexport function acquireLock(projectRoot: string): boolean {\n const lockPath = join(projectRoot, LOCK_FILE);\n\n // Symlink guard\n if (existsSync(lockPath) && lstatSync(lockPath).isSymbolicLink()) {\n die('.stackwright/.lock is a symlink — refusing to acquire lock. Check for tampering.');\n }\n\n const pid = process.pid;\n const host = hostname();\n const timestamp = new Date().toISOString();\n\n const lockContent = JSON.stringify({\n pid,\n hostname: host,\n acquiredAt: timestamp,\n version: '1.0',\n });\n\n mkdirSync(join(projectRoot, '.stackwright'), { recursive: true });\n\n try {\n // O_EXCL makes this atomic on POSIX — fails if file already exists\n writeFileSync(lockPath, lockContent, { flag: 'wx' });\n return true;\n } catch (err) {\n // EEXIST means lock already held by another process\n if (err instanceof Error && 'code' in err && (err as { code: string }).code === 'EEXIST') {\n // Lock exists — try to read it and check if the process is still alive\n try {\n const existing = JSON.parse(readFileSync(lockPath, 'utf8')) as Record<string, unknown>;\n const oldPid = existing['pid'] as number;\n\n // On Unix, check if process still exists via kill(0, pid)\n try {\n process.kill(oldPid, 0); // Signal 0 = check existence only\n // Process exists — lock is held by live process, cannot acquire\n return false;\n } catch {\n // Process is dead — stale lock, can take over\n writeFileSync(lockPath, lockContent, 'utf-8');\n return true;\n }\n } catch {\n // Can't read lock file or not JSON — treat as stale, take over\n writeFileSync(lockPath, lockContent, 'utf-8');\n return true;\n }\n }\n throw err;\n }\n}\n\nexport function releaseLock(projectRoot: string): void {\n const lockPath = join(projectRoot, LOCK_FILE);\n try {\n rmSync(lockPath);\n } catch {\n // Lock file may not exist — that's fine\n }\n}\n\n// ─── Workspace Directory Migration ────────────────────────────────────────────\n\n/**\n * Migrate the legacy `.code-puppy/` workspace directory to `.code_puppy/`.\n *\n * PR #290 renamed the workspace directory from `.code-puppy` (hyphen) to\n * `.code_puppy` (underscore) to match the canonical `PROJECT_WORKSPACE_DIR_NAME`\n * in code-puppy's `config.py`. Projects scaffolded before that change still\n * have the old directory; without migration, `get_project_workspace()` walks\n * past it and falls through to `$HOME/.code_puppy/` (global config), causing\n * `strict-local` profile and local MCP server loading to silently fail.\n *\n * Safe to call on every launch — no-ops when there's nothing to migrate.\n */\nexport function migrateWorkspaceDir(projectRoot: string): void {\n const oldDir = join(projectRoot, '.code-puppy');\n const newDir = join(projectRoot, '.code_puppy');\n\n const oldExists = existsSync(oldDir);\n const newExists = existsSync(newDir);\n\n if (!oldExists) return; // Nothing to migrate\n\n // Symlink guards — refuse to follow symlinks on either directory\n if (lstatSync(oldDir).isSymbolicLink()) {\n die('.code-puppy/ is a symlink — refusing to migrate. Check for tampering.');\n }\n if (newExists && lstatSync(newDir).isSymbolicLink()) {\n die('.code_puppy/ is a symlink — refusing to migrate. Check for tampering.');\n }\n\n if (!newExists) {\n // Clean migration: old exists, new doesn't → rename\n renameSync(oldDir, newDir);\n log('Migrated .code-puppy/ → .code_puppy/ (underscore convention)');\n } else {\n // Both exist — remove old to avoid confusion (underscore is canonical)\n rmSync(oldDir, { recursive: true });\n log('Removed stale .code-puppy/ (underscore version already exists)');\n }\n}\n\n// ─── Write Init Context ─────────────────────────────────────────────────────\n\nexport function writeInitContext(projectRoot: string): void {\n // Acquire pipeline lock — prevent concurrent launch-raft processes\n if (!acquireLock(projectRoot)) {\n let existingPid: string | number = 'unknown';\n try {\n const lockPath = join(projectRoot, LOCK_FILE);\n if (existsSync(lockPath)) {\n const lockData = JSON.parse(readFileSync(lockPath, 'utf8')) as Record<string, unknown>;\n const rawPid = lockData['pid'];\n existingPid = typeof rawPid === 'string' || typeof rawPid === 'number' ? rawPid : 'unknown';\n }\n } catch {\n // Couldn't read lock file\n }\n die(\n `Pipeline lock already held by PID ${existingPid}. Another launch-raft process is running.`\n );\n }\n\n const stackwrightDir = join(projectRoot, '.stackwright');\n const initContextPath = join(stackwrightDir, 'init-context.json');\n\n // Merge, don't clobber — respect anything the launcher already wrote\n const MAX_INIT_CONTEXT_BYTES = 1 * 1024 * 1024; // 1MB\n let existing: Record<string, unknown> = {};\n try {\n const raw = readFileSync(initContextPath, 'utf-8');\n\n // Size guard — reject oversized init-context.json before parsing\n if (raw.length > MAX_INIT_CONTEXT_BYTES) {\n die(\n `init-context.json exceeds ${MAX_INIT_CONTEXT_BYTES.toLocaleString()} bytes (got ${raw.length.toLocaleString()}). Refusing to parse. This may be an attack or a corrupted file.`\n );\n }\n\n existing = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n // Fresh project or malformed file — start from scratch\n }\n\n // Enrich: only fill in gaps\n existing['projectRoot'] = projectRoot;\n\n if (!existing['projectName']) {\n try {\n const pkgRaw = readFileSync(join(projectRoot, 'package.json'), 'utf-8');\n const pkg = JSON.parse(pkgRaw) as Record<string, unknown>;\n if (typeof pkg['name'] === 'string') {\n existing['projectName'] = pkg['name'];\n }\n } catch {\n // No package.json name — that's fine\n }\n }\n\n if (!existing['specPath']) {\n try {\n const specsDir = join(projectRoot, 'specs');\n if (existsSync(specsDir)) {\n const files = readdirSync(specsDir);\n const first = files[0];\n if (first) {\n existing['specPath'] = join('specs', first);\n }\n }\n } catch {\n // No specs dir — that's fine\n }\n }\n\n if (!existing['theme']) {\n try {\n const ymlPath = join(projectRoot, 'stackwright.yml');\n const ymlContent = readFileSync(ymlPath, 'utf-8');\n const match = /theme:\\s*\\n\\s+id:\\s*(.+)/.exec(ymlContent);\n if (match?.[1]) {\n existing['theme'] = match[1].trim();\n }\n } catch {\n // No stackwright.yml — that's fine\n }\n }\n\n existing['generatedBy'] = 'launch-raft';\n existing['version'] = '1.0';\n\n mkdirSync(stackwrightDir, { recursive: true });\n\n // Symlink guard — refuse to follow symlinks (prevents symlink-based overwrites)\n if (existsSync(stackwrightDir) && lstatSync(stackwrightDir).isSymbolicLink()) {\n die('.stackwright is a symlink — refusing to write. Check for tampering.');\n }\n if (existsSync(initContextPath) && lstatSync(initContextPath).isSymbolicLink()) {\n die('init-context.json is a symlink — refusing to write. Check for tampering.');\n }\n\n writeFileSync(initContextPath, JSON.stringify(existing, null, 2), 'utf-8');\n}\n\n// ─── Shutdown handler — release lock on exit ──────────────────────────────────\n\nprocess.on('exit', () => {\n try {\n releaseLock(process.cwd());\n } catch {\n // Lock release is best-effort on shutdown — don't block exit\n }\n});\nprocess.on('SIGINT', () => {\n try {\n releaseLock(process.cwd());\n } catch {\n // Lock release is best-effort on signal — don't block exit\n }\n process.exit(0);\n});\nprocess.on('SIGTERM', () => {\n try {\n releaseLock(process.cwd());\n } catch {\n // Lock release is best-effort on signal — don't block exit\n }\n process.exit(0);\n});\n\n// ─── Trusted Binary Directories ────────────────────────────────────────────\n\n/**\n * Returns the ordered list of trusted installation directories to probe\n * for raft-puppy / code-puppy before falling back to $PATH (`which`).\n *\n * These represent well-known, user-controlled install locations that a\n * malicious npm postinstall script cannot shadow by merely prepending to\n * $PATH — providing a meaningful defence against CWE-426 (Untrusted\n * Search Path). raft-puppy / code-puppy are Python tools, so pip user\n * install (~/.local/bin), brew, and system package managers cover all\n * supported install methods.\n *\n * Exported for unit testing.\n */\nexport function getTrustedBinaryDirs(): string[] {\n return [\n join(homedir(), '.local', 'bin'), // pip user install, uvx, pipx (Linux/macOS)\n '/opt/homebrew/bin', // brew Apple Silicon\n '/usr/local/bin', // brew Intel, pip system install\n '/usr/bin', // system package managers (apt, dnf)\n ];\n}\n\n// ─── Find code-puppy Executable ─────────────────────────────────────────────\n\n/**\n * Result of findCodePuppy() — includes the resolved real path AND an open\n * O_RDONLY file descriptor to close the TOCTOU window between validation\n * and spawn. Keep the fd alive until spawn() returns (Linux: exec via\n * /proc/self/fd/N pins the inode; macOS: fd is best-effort, residual\n * TOCTOU documented).\n */\nexport interface FindCodePuppyResult {\n /** Resolved real path (symlinks followed) — use for display/logging only on Linux */\n path: string;\n /** Open O_RDONLY fd — caller MUST closeSync(fd) after spawn() returns */\n fd: number;\n}\n\nexport function findCodePuppy(trustedDirsOverride?: string[]): FindCodePuppyResult {\n let candidate: string | null = null;\n\n // 1. Explicit env var override — highest priority\n const envPath = process.env['STACKWRIGHT_CODE_PUPPY_PATH'];\n if (envPath) {\n candidate = envPath;\n }\n\n // 2. Trusted installation paths — probe before falling back to $PATH.\n // Prevents CWE-426: a malicious npm postinstall script cannot shadow\n // these paths by merely prepending to $PATH.\n if (!candidate) {\n const trustedDirs = trustedDirsOverride ?? getTrustedBinaryDirs();\n outer: for (const bin of ['raft-puppy', 'code-puppy']) {\n for (const dir of trustedDirs) {\n const p = join(dir, bin);\n if (existsSync(p)) {\n candidate = p;\n break outer;\n }\n }\n }\n }\n\n // 3. Prefer raft-puppy (stackwright-puppy fork) over vanilla code-puppy.\n // raft-puppy ships the MCP auto-enable fix and local .code-puppy.json\n // loading — both required for the raft to work on a clean install.\n // Falls back to code-puppy so existing installs don't break.\n if (!candidate) {\n for (const bin of ['raft-puppy', 'code-puppy']) {\n // spawnSync avoids spawning a shell — args passed directly to execvp,\n // no shell interpolation, no injection surface (CWE-78 / detect-child-process).\n const whichResult = spawnSync('which', [bin], { encoding: 'utf-8' });\n if (whichResult.status === 0 && whichResult.stdout) {\n candidate = whichResult.stdout.trim();\n if (candidate) break;\n }\n }\n }\n\n if (!candidate) {\n die(\n 'raft-puppy (or code-puppy) not found.\\n' +\n '\\n' +\n 'Install the Stackwright-patched build (recommended):\\n' +\n ' pip install stackwright-puppy\\n' +\n '\\n' +\n 'Or install vanilla code-puppy (MCP tools may not auto-start):\\n' +\n ' pip install code-puppy\\n' +\n '\\n' +\n 'Or set STACKWRIGHT_CODE_PUPPY_PATH to the binary path.'\n );\n }\n\n // Resolve to absolute path — prevents bare-name or relative-path shenanigans\n const resolved = resolve(candidate);\n\n if (!existsSync(resolved)) {\n die(`raft-puppy/code-puppy not found at resolved path: ${resolved}`);\n }\n\n // Follow the full symlink chain to the real binary.\n // pip/pipx/uvx installs create a wrapper symlink in ~/.local/bin/ that points\n // to the actual binary inside a virtualenv — this is expected and safe.\n let realBinary: string;\n try {\n realBinary = realpathSync(resolved);\n } catch {\n die(\n `raft-puppy at ${resolved} has a broken symlink — cannot resolve to a real path. ` +\n `Try reinstalling stackwright-puppy or set STACKWRIGHT_CODE_PUPPY_PATH to the real binary.`\n );\n }\n\n if (!existsSync(realBinary)) {\n die(`raft-puppy symlink at ${resolved} points to a missing file: ${realBinary}`);\n }\n\n // Open an fd BEFORE permission checks — fstatSync(fd) operates on the already-open\n // inode, so the checks and the spawn target cannot diverge. CWE-367 mitigation.\n let fd: number;\n try {\n fd = openSync(realBinary, 'r');\n } catch (err) {\n die(\n `Cannot open raft-puppy at ${realBinary} for validation: ${String(err)}. ` +\n `Try reinstalling stackwright-puppy or set STACKWRIGHT_CODE_PUPPY_PATH.`\n );\n }\n\n // Use fstatSync(fd) — operates on the pinned inode, not the filename.\n const stat = fstatSync(fd!);\n\n // Refuse world-writable or group-writable binaries\n if (stat.mode & 0o022) {\n closeSync(fd!);\n die(\n `raft-puppy at ${realBinary} is group- or world-writable (mode: ${(stat.mode & 0o777).toString(8)}) — refusing to exec.`\n );\n }\n\n // Refuse setuid/setgid binaries\n if (stat.mode & 0o6000) {\n closeSync(fd!);\n die(`raft-puppy at ${realBinary} has setuid/setgid bits — refusing to exec.`);\n }\n\n return { path: realBinary, fd: fd! };\n}\n\n// ─── Pre-flight Environment Validation ──────────────────────────────────────\n\nfunction parseSemver(version: string): [number, number, number] {\n const parts = version.split('-')[0]!.split('.').map(Number);\n return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];\n}\n\nfunction semverGte(a: string, b: string): boolean {\n const [aMaj, aMin, aPatch] = parseSemver(a);\n const [bMaj, bMin, bPatch] = parseSemver(b);\n if (aMaj !== bMaj) return aMaj > bMaj;\n if (aMin !== bMin) return aMin > bMin;\n return aPatch >= bPatch;\n}\n\n/**\n * Validates the runtime environment before spawning code-puppy.\n * Calls die() (process.exit(1)) with a human-readable remediation message\n * on failure — never reaches the spawn if a check fails.\n *\n * Auth checking is intentionally NOT done here — code-puppy/raft-puppy owns\n * the auth layer and will surface a clear error at spawn time for any\n * missing/invalid credentials. This keeps the raft agnostic to model\n * providers (Anthropic API key, claude auth login OAuth, AWS Bedrock,\n * Google Vertex, Ollama, air-gapped inference, etc.).\n *\n * Checks:\n * 1. STACKWRIGHT_SKIP_PREFLIGHT — if 'true', skip all checks and return\n * 2. code-puppy --version reports >= MIN_SUPPORTED_CODE_PUPPY_VERSION\n */\nexport function validateBinaryVersion(binaryPath: string): void {\n // Escape hatch for air-gapped / custom model-provider deployments.\n // Set STACKWRIGHT_SKIP_PREFLIGHT=true to bypass pre-flight checks.\n if (process.env['STACKWRIGHT_SKIP_PREFLIGHT'] === 'true') {\n log('Pre-flight checks skipped via STACKWRIGHT_SKIP_PREFLIGHT');\n return;\n }\n\n // 1. Version check — run binary with --version, parse semver, compare\n // binaryPath is validated by findCodePuppy(): realpathSync'd, symlink-checked,\n // mode/setuid-checked, and confirmed to exist. spawnSync passes args directly\n // to execvp — no shell spawned, no quoting needed (CWE-78).\n // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process\n const versionStartTime = Date.now();\n const versionResult = spawnSync(binaryPath, ['--version'], {\n encoding: 'utf-8',\n // 15s: uv-installed Python tools cold-start (venv activation + interpreter\n // startup) can take 5-10s on first invocation; subsequent runs are ms.\n timeout: 15000,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const versionElapsed = Date.now() - versionStartTime;\n\n if (versionResult.error !== undefined || versionResult.status !== 0) {\n const errorDetail = versionResult.error\n ? `${(versionResult.error as NodeJS.ErrnoException).code ?? 'unknown'}: ${versionResult.error.message}`\n : `exit status ${String(versionResult.status)}`;\n const stdoutSnip = (versionResult.stdout ?? '').trim() || '<empty>';\n const stderrSnip = (versionResult.stderr ?? '').trim() || '<empty>';\n die(\n `Could not determine raft-puppy / code-puppy version.\\n` +\n ` Binary: ${binaryPath}\\n` +\n ` Failure: ${errorDetail}\\n` +\n ` Elapsed: ${String(versionElapsed)}ms (timeout: 15000ms)\\n` +\n ` Stdout: ${stdoutSnip}\\n` +\n ` Stderr: ${stderrSnip}\\n` +\n ` Required: ${MIN_SUPPORTED_CODE_PUPPY_VERSION}\\n` +\n `\\n` +\n ` Run: pip install --upgrade stackwright-puppy\\n` +\n ` Or: pip install --upgrade code-puppy\\n` +\n `\\n` +\n ` If the binary works manually but fails here, set\\n` +\n ` STACKWRIGHT_SKIP_PREFLIGHT=true to bypass this check.`\n );\n }\n\n // Some CLIs print --version to stderr instead of stdout. Accept either;\n // prefer stdout, fall back to stderr if stdout is empty.\n const versionStdout = versionResult.stdout.trim();\n const versionStderr = versionResult.stderr.trim();\n const versionOutput = versionStdout || versionStderr;\n\n const match = /(\\d+\\.\\d+\\.\\d+(?:-[^\\s]+)?)/.exec(versionOutput);\n if (!match || !match[1]) {\n die(\n `Could not parse version from raft-puppy / code-puppy output.\\n` +\n ` Binary: ${binaryPath}\\n` +\n ` Stdout: ${JSON.stringify(versionStdout) || '<empty>'}\\n` +\n ` Stderr: ${JSON.stringify(versionStderr) || '<empty>'}\\n` +\n ` Required: ${MIN_SUPPORTED_CODE_PUPPY_VERSION}\\n` +\n `\\n` +\n ` Run: pip install --upgrade stackwright-puppy\\n` +\n ` Or: pip install --upgrade code-puppy`\n );\n }\n\n const installedVersion = match[1];\n\n // Dev builds self-identify as 0.0.0-dev (or bare 0.0.0) — treat as a\n // local-source build and warn instead of hard-failing the version gate.\n // Any real published release should use a proper semver (e.g. 0.1.0-alpha.1).\n // NOTE: 0.0.0 bare is also treated as dev — it's never a valid published version.\n const isDevBuild = installedVersion === '0.0.0' || /^0\\.0\\.0-.+$/.test(installedVersion);\n if (isDevBuild) {\n console.warn(\n `⚠️ Dev build detected (${installedVersion}) — skipping minimum version check.\\n` +\n ` Minimum required for production: ${MIN_SUPPORTED_CODE_PUPPY_VERSION}\\n` +\n ` To suppress this warning in CI: set STACKWRIGHT_SKIP_PREFLIGHT=true`\n );\n return;\n }\n\n if (!semverGte(installedVersion, MIN_SUPPORTED_CODE_PUPPY_VERSION)) {\n die(\n `raft-puppy / code-puppy ${installedVersion} is below the minimum required version.\\n` +\n ` Installed: ${installedVersion}\\n` +\n ` Minimum required: ${MIN_SUPPORTED_CODE_PUPPY_VERSION}\\n` +\n `\\n` +\n ` Run: pip install --upgrade stackwright-puppy\\n` +\n ` Or: pip install --upgrade code-puppy`\n );\n }\n}\n\n// ─── Ensure MCP Config ─────────────────────────────────────────────────────\n\nexport function ensureMcpConfig(projectRoot: string): void {\n const workspaceDir = join(projectRoot, '.code_puppy');\n const configPath = join(workspaceDir, 'mcp_servers.json');\n\n // Symlink guard — consistent with other security patterns\n if (existsSync(configPath) && lstatSync(configPath).isSymbolicLink()) {\n die('.code_puppy/mcp_servers.json is a symlink — refusing to write. Check for tampering.');\n }\n\n mkdirSync(workspaceDir, { recursive: true });\n\n // Read existing → merge, don't clobber other registered servers\n let existing: { mcp_servers?: Record<string, unknown> } = {};\n if (existsSync(configPath)) {\n try {\n const raw = readFileSync(configPath, 'utf-8');\n existing = JSON.parse(raw) as { mcp_servers?: Record<string, unknown> };\n } catch {\n // Malformed file — start fresh\n }\n }\n\n // Resolve the pnpm-generated shim directly instead of going through `pnpm exec`.\n //\n // Why: the MCP Python SDK (mcp.client.stdio.get_default_environment) only inherits\n // 6 env vars by default — HOME, LOGNAME, PATH, SHELL, TERM, USER. Pnpm needs\n // PNPM_HOME, NODE_PATH, XDG_DATA_HOME, etc. to locate its store; without them it\n // exits in ~3-9ms without ever printing the \"running on stdio\" banner.\n //\n // The .bin/ shim is self-sufficient: it exports its own NODE_PATH and execs `node`\n // directly — only PATH is needed (already in the default 6). This also makes the\n // config package-manager agnostic: pnpm/npm/yarn all produce the same shim format.\n const shimName = platform() === 'win32' ? 'stackwright-pro-mcp.cmd' : 'stackwright-pro-mcp';\n const shimPath = join(projectRoot, 'node_modules', '.bin', shimName);\n\n if (!existsSync(shimPath)) {\n die(\n `MCP server shim not found: ${shimPath}\\n` +\n ` Run \\`pnpm install\\` (or your package manager equivalent) before \\`raft\\`.`\n );\n }\n\n const serverConfig = {\n type: 'stdio',\n command: shimPath,\n args: [] as string[],\n enabled: true,\n cwd: projectRoot,\n };\n\n const merged = {\n ...existing,\n mcp_servers: {\n ...(existing.mcp_servers ?? {}),\n 'stackwright-pro-mcp': serverConfig,\n },\n };\n\n // Atomic write via .tmp rename swap\n const tmpPath = `${configPath}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(merged, null, 2), 'utf-8');\n renameSync(tmpPath, configPath);\n\n log('MCP server registered → .code_puppy/mcp_servers.json');\n}\n\n// ─── Ensure Workspace Config ─────────────────────────────────────────────────\n\n/**\n * Write .code_puppy/config.json with profile: \"strict-local\".\n *\n * code-puppy ≥ 0.0.575 uses a profile-based workspace scoping system\n * (the `project_workspace` builtin plugin). `strict-local` scopes ALL\n * six extension surfaces — agents, skills, plugins, mcp, hooks,\n * file_permissions — to the project directory only. This ensures raft\n * runs are fully isolated from the user's global ~/.code_puppy/ config,\n * which is raft's core reproducibility guarantee.\n *\n * Merges with any existing config.json without clobbering other fields.\n * Uses atomic write (.tmp rename swap) and symlink guards throughout.\n */\nexport function ensureWorkspaceConfig(projectRoot: string): void {\n const workspaceDir = join(projectRoot, '.code_puppy');\n const configPath = join(workspaceDir, 'config.json');\n\n // Symlink guard on the .code_puppy/ dir itself\n if (existsSync(workspaceDir) && lstatSync(workspaceDir).isSymbolicLink()) {\n die('.code_puppy/ is a symlink — refusing to write. Check for tampering.');\n }\n\n mkdirSync(workspaceDir, { recursive: true });\n\n // Symlink guard on config.json\n if (existsSync(configPath) && lstatSync(configPath).isSymbolicLink()) {\n die('.code_puppy/config.json is a symlink — refusing to write. Check for tampering.');\n }\n\n // Merge — don't clobber existing fields\n let existing: Record<string, unknown> = {};\n if (existsSync(configPath)) {\n try {\n existing = JSON.parse(readFileSync(configPath, 'utf-8')) as Record<string, unknown>;\n } catch {\n // Malformed — start fresh\n }\n }\n\n const merged = {\n ...existing,\n $schema: 'https://stackwright.dev/schemas/workspace-config/v2.json',\n profile: 'strict-local',\n };\n\n // Atomic write via .tmp rename swap\n const tmpPath = `${configPath}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(merged, null, 2), 'utf-8');\n renameSync(tmpPath, configPath);\n\n log('Workspace config written → .code_puppy/config.json (profile: strict-local)');\n}\n\n// ─── Print Resume Status ────────────────────────────────────────────────────\n\nexport function printResumeStatus(projectRoot: string): void {\n const pipelineStatePath = join(projectRoot, '.stackwright', 'pipeline-state.json');\n\n try {\n const raw = readFileSync(pipelineStatePath, 'utf-8');\n const state = JSON.parse(raw) as Record<string, unknown>;\n const status = state['status'];\n const phases = state['phases'];\n\n if (typeof phases !== 'object' || phases === null || Array.isArray(phases)) {\n return;\n }\n\n const phaseEntries = Object.values(phases as Record<string, Record<string, unknown>>);\n const totalPhases = phaseEntries.length || 8;\n\n switch (status) {\n case 'setup':\n log('📍 Starting fresh');\n break;\n case 'questions': {\n const answered = phaseEntries.filter((p) => p['answered'] === true).length;\n log(`📍 Resuming: questions phase (${answered}/${totalPhases} phases answered)`);\n break;\n }\n case 'execution': {\n const executed = phaseEntries.filter((p) => p['executed'] === true).length;\n log(`📍 Resuming: execution phase (${executed}/${totalPhases} phases complete)`);\n break;\n }\n case 'done':\n log('📍 Pipeline complete — re-entering to review');\n break;\n default:\n break;\n }\n } catch {\n // No pipeline state yet — fresh project\n }\n}\n\n// ─── Pipeline Resumability Check ─────────────────────────────────────────────\n\n/**\n * Returns true if the pipeline is in a state that can be automatically resumed.\n *\n * Used by the retry/backoff logic in index.ts to decide whether a non-zero\n * code-puppy exit warrants a re-spawn. 'questions' and 'execution' statuses\n * mean the Foreman was mid-run; 'done' means it finished cleanly; no file\n * means it never started (no point retrying a blank slate).\n */\nexport function isPipelineResumable(projectRoot: string): boolean {\n const pipelineStatePath = join(projectRoot, '.stackwright', 'pipeline-state.json');\n try {\n const raw = readFileSync(pipelineStatePath, 'utf-8');\n const state = JSON.parse(raw) as Record<string, unknown>;\n const status = state['status'];\n return status === 'questions' || status === 'execution';\n } catch {\n return false;\n }\n}\n\n// ─── Resolve Otter Directory ────────────────────────────────────────────────\n\nexport function resolveOtterDir(projectRoot: string): string | null {\n const candidates = [\n join(projectRoot, 'node_modules', '@stackwright-pro', 'otters', 'src'),\n join(projectRoot, 'packages', 'otters', 'src'),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\n// ─── Sync Agents ────────────────────────────────────────────────────────────\n\n/**\n * Copy all *-otter.json files from the resolved otters package to\n * .code_puppy/agents/ on every raft startup.\n *\n * This mirrors the pattern used by ensureMcpConfig() for mcp_servers.json.\n * Relying solely on the postinstall hook in @stackwright-pro/otters is fragile:\n * pnpm/npm don't always re-run scripts on package updates, and postinstall\n * order is non-deterministic when multiple packages run scripts. Active sync\n * on every raft launch guarantees agents are always the installed version.\n */\nexport function syncAgents(projectRoot: string, isVerbose: boolean = false): void {\n const agentsDir = join(projectRoot, '.code_puppy', 'agents');\n\n const otterDir = resolveOtterDir(projectRoot);\n if (!otterDir) {\n verbose('No otters directory found — skipping agent sync', isVerbose);\n return;\n }\n\n // Symlink guard on agents dir\n if (existsSync(agentsDir) && lstatSync(agentsDir).isSymbolicLink()) {\n die('.code_puppy/agents is a symlink — refusing to write. Check for tampering.');\n }\n\n mkdirSync(agentsDir, { recursive: true });\n\n let synced = 0;\n let skipped = 0;\n\n try {\n const files = readdirSync(otterDir);\n for (const file of files) {\n if (!file.endsWith('-otter.json')) continue;\n\n const src = join(otterDir, file);\n const dest = join(agentsDir, file);\n\n // Symlink guard on individual file\n if (existsSync(dest) && lstatSync(dest).isSymbolicLink()) {\n verbose(`Skipping ${file} — dest is a symlink`, isVerbose);\n skipped++;\n continue;\n }\n\n // Atomic write: copy to .tmp then rename\n const tmp = `${dest}.tmp`;\n const content = readFileSync(src);\n writeFileSync(tmp, content);\n renameSync(tmp, dest);\n verbose(`Synced: ${file}`, isVerbose);\n synced++;\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n // Non-fatal: log and continue — a stale agent is better than a crash\n console.warn(`⚠️ Agent sync partial failure: ${msg}`);\n return;\n }\n\n if (synced > 0) {\n log(\n `Agents synced → .code_puppy/agents/ (${synced} otters${skipped > 0 ? `, ${skipped} skipped` : ''})`\n );\n }\n}\n\n// ─── Spawn Config Builder ───────────────────────────────────────────────────\n\n/**\n * Build the spawn target and stdio config for code-puppy/raft-puppy.\n *\n * On Linux: pass the validated binary fd through stdio at index 3 (dup2 clears\n * O_CLOEXEC), then exec via /proc/self/fd/3. This works for both ELF and\n * shebang (Python) scripts — the fd survives the interpreter's execve.\n *\n * On macOS/other: /proc does not exist — fall back to the validated path.\n * Residual TOCTOU on macOS is documented and accepted.\n *\n * Extracted from main() for testability.\n *\n * @param platform - process.platform value\n * @param executable - resolved binary path (from findCodePuppy)\n * @param binaryFd - open fd to the binary (from findCodePuppy)\n */\nexport function buildSpawnConfig(\n platform: string,\n executable: string,\n binaryFd: number\n): { spawnTarget: string; stdioConfig: import('child_process').StdioOptions } {\n const PINNED_CHILD_FD = 3;\n\n if (platform === 'linux') {\n return {\n spawnTarget: `/proc/self/fd/${PINNED_CHILD_FD}`,\n stdioConfig: ['inherit', 'inherit', 'inherit', binaryFd],\n };\n }\n\n return {\n spawnTarget: executable,\n stdioConfig: 'inherit',\n };\n}\n","/**\n * @stackwright-pro/raft — Dev-server lifecycle bracket\n *\n * Spawns `pnpm dev` in a project, probes until HTTP is ready, invokes a\n * callback body, then kills the server cleanly (SIGTERM → 5s grace → SIGKILL).\n *\n * swp-sc4x: Bead F — lifecycle module. QA otter invocation is Bead D (swp-wde8).\n */\n\nimport { spawn } from 'child_process';\nimport type { ChildProcess } from 'child_process';\nimport {\n existsSync,\n mkdirSync,\n openSync,\n closeSync,\n writeFileSync,\n unlinkSync,\n readFileSync,\n} from 'fs';\nimport { join } from 'path';\n\n// ─── Public Interfaces ───────────────────────────────────────────────────────\n\nexport interface DevServerInfo {\n pid: number;\n baseUrl: string;\n prismUrl?: string;\n startedAt: number;\n logPath: string;\n}\n\nexport interface DevServerOptions {\n /** Total budget to wait for the server to be ready. Default: 60000ms */\n timeoutMs?: number;\n /** Base URL to probe. Default: http://localhost:3000 */\n baseUrl?: string;\n /** Explicit prism URL override (skips auto-detection). Default: http://localhost:4010 */\n prismUrl?: string;\n /** Detect prism from package.json scripts.dev. Default: true */\n detectPrism?: boolean;\n}\n\nexport interface PrismConfig {\n hasPrism: boolean;\n prismUrl: string;\n}\n\nexport class DevServerStartupError extends Error {\n constructor(\n message: string,\n public readonly logTail: string[],\n public readonly elapsedMs: number\n ) {\n super(message);\n this.name = 'DevServerStartupError';\n }\n}\n\n// ─── Pure Utilities ──────────────────────────────────────────────────────────\n\n/**\n * Inspect a project's `package.json` dev script to determine if Prism is in use.\n * Pure function — exported for independent testability.\n */\nexport function detectPrismConfig(\n projectRoot: string,\n defaultPrismUrl = 'http://localhost:4010'\n): PrismConfig {\n try {\n const pkgPath = join(projectRoot, 'package.json');\n const raw = readFileSync(pkgPath, 'utf-8');\n const pkg = JSON.parse(raw) as Record<string, unknown>;\n const scripts = pkg['scripts'] as Record<string, unknown> | undefined;\n const devScript = typeof scripts?.['dev'] === 'string' ? scripts['dev'] : '';\n const hasConcurrently = devScript.includes('concurrently');\n const hasPrism = /prism/i.test(devScript);\n return { hasPrism: hasConcurrently && hasPrism, prismUrl: defaultPrismUrl };\n } catch {\n return { hasPrism: false, prismUrl: defaultPrismUrl };\n }\n}\n\n/** Read the last N non-empty lines from a file (best-effort). */\nfunction tailLog(logPath: string, maxLines: number): string[] {\n try {\n const content = readFileSync(logPath, 'utf-8');\n const lines = content.split('\\n').filter((l) => l.length > 0);\n return lines.slice(-maxLines);\n } catch {\n return [];\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// ─── HTTP Probing ────────────────────────────────────────────────────────────\n\n/**\n * Attempt a single fetch with per-request timeout.\n * Returns true on 2xx, false on any error/timeout. Never throws.\n */\nasync function singleFetch(url: string, timeoutMs: number): Promise<boolean> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await globalThis.fetch(url, { signal: controller.signal });\n clearTimeout(timer);\n return res.status >= 200 && res.status < 300;\n } catch {\n clearTimeout(timer);\n return false;\n }\n}\n\n/**\n * Poll a set of URLs every `pollIntervalMs` until ALL respond 2xx or `timeoutMs` elapses.\n * Returns true if all became ready within budget, false on timeout.\n */\nasync function pollUntilAllReady(\n urls: string[],\n timeoutMs: number,\n pollIntervalMs: number,\n perRequestTimeoutMs: number\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n\n while (Date.now() < deadline) {\n const remaining = deadline - Date.now();\n const perReq = Math.min(perRequestTimeoutMs, remaining);\n const results = await Promise.all(urls.map((url) => singleFetch(url, perReq)));\n if (results.every(Boolean)) return true;\n\n const sleepMs = Math.min(pollIntervalMs, deadline - Date.now());\n if (sleepMs <= 0) break;\n await sleep(sleepMs);\n }\n\n return false;\n}\n\n/**\n * Probe a URL until it responds 2xx or times out.\n * Exported for tests and for callers that need readiness checks without managing lifecycle.\n */\nexport async function probeUrl(\n url: string,\n options: { timeoutMs: number; pollIntervalMs?: number; perRequestTimeoutMs?: number }\n): Promise<boolean> {\n const { timeoutMs, pollIntervalMs = 2000, perRequestTimeoutMs = 1000 } = options;\n return pollUntilAllReady([url], timeoutMs, pollIntervalMs, perRequestTimeoutMs);\n}\n\n// ─── Process Cleanup ─────────────────────────────────────────────────────────\n\nasync function killChildProcess(child: ChildProcess, pid: number): Promise<void> {\n return new Promise((resolve) => {\n // Race: exit event vs 5s SIGKILL escalation\n let settled = false;\n const settle = (): void => {\n if (!settled) {\n settled = true;\n clearTimeout(escalateTimer);\n resolve();\n }\n };\n\n child.once('exit', settle);\n\n const escalateTimer = setTimeout(() => {\n try {\n process.kill(pid, 'SIGKILL');\n } catch {\n // Process already gone\n }\n settle();\n }, 5_000);\n\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // Process already gone — still resolve\n settle();\n }\n });\n}\n\n// ─── Dev Server Lifecycle ────────────────────────────────────────────────────\n\n/**\n * Spawn `pnpm dev` in the project, wait for it to be ready, invoke `body`,\n * then ALWAYS kill the server cleanly — even if `body` throws.\n *\n * Writes `.stackwright/dev-server-ready.json` on success.\n * Throws `DevServerStartupError` on timeout/failure with log tail.\n *\n * Critical (swp-e4y6 family): uses spawn (not exec) for pid, no zombie leaks.\n */\nexport async function withDevServer<T>(\n projectRoot: string,\n options: DevServerOptions,\n body: (info: DevServerInfo) => Promise<T>\n): Promise<T> {\n const {\n timeoutMs = 60_000,\n baseUrl: baseUrlOpt,\n prismUrl: prismUrlOpt,\n detectPrism: shouldDetectPrism = true,\n } = options;\n\n const baseUrl = baseUrlOpt ?? 'http://localhost:3000';\n\n // Prism detection: explicit override > auto-detect > disabled\n let prismUrl: string | undefined;\n if (prismUrlOpt !== undefined) {\n prismUrl = prismUrlOpt;\n } else if (shouldDetectPrism) {\n const cfg = detectPrismConfig(projectRoot);\n prismUrl = cfg.hasPrism ? cfg.prismUrl : undefined;\n }\n\n // Set up log file — truncate per run, no append\n const stackwrightDir = join(projectRoot, '.stackwright');\n mkdirSync(stackwrightDir, { recursive: true });\n const logPath = join(stackwrightDir, 'dev-server.log');\n const logFd = openSync(logPath, 'w');\n\n const readyFilePath = join(stackwrightDir, 'dev-server-ready.json');\n const startedAt = Date.now();\n\n let child: ChildProcess | undefined;\n let pid: number | undefined;\n let spawnErr: Error | undefined;\n\n try {\n // nosemgrep: javascript.lang.security.detect-child-process\n child = spawn('pnpm', ['dev'], {\n cwd: projectRoot,\n detached: false,\n stdio: ['ignore', logFd, logFd],\n });\n\n pid = child.pid;\n child.on('error', (err) => {\n spawnErr = err;\n });\n\n if (pid === undefined) {\n throw new DevServerStartupError(\n 'pnpm dev failed to spawn (no PID)',\n tailLog(logPath, 50),\n Date.now() - startedAt\n );\n }\n\n // Probe all required URLs\n const urlsToProbe = [baseUrl, ...(prismUrl ? [prismUrl] : [])];\n const ready = await pollUntilAllReady(urlsToProbe, timeoutMs, 2_000, 1_000);\n\n if (!ready) {\n const elapsedMs = Date.now() - startedAt;\n const tail = tailLog(logPath, 50);\n const spawnErrMsg = spawnErr ? ` Spawn error: ${spawnErr.message}.` : '';\n throw new DevServerStartupError(\n `Dev server did not become ready within ${timeoutMs}ms.${spawnErrMsg}`,\n tail,\n elapsedMs\n );\n }\n\n const info: DevServerInfo = {\n pid,\n baseUrl,\n ...(prismUrl ? { prismUrl } : {}),\n startedAt,\n logPath,\n };\n\n // Write ready file — Bead D / E consumers can read this for reference\n writeFileSync(\n readyFilePath,\n JSON.stringify({ ...info, readyAt: new Date().toISOString() }, null, 2) + '\\n'\n );\n\n return await body(info);\n } finally {\n // Cleanup always runs — even if probe threw or body threw\n if (child && pid !== undefined) {\n await killChildProcess(child, pid);\n }\n\n // Remove ready file (best-effort — ignore ENOENT)\n try {\n unlinkSync(readyFilePath);\n } catch {\n /* best-effort */\n }\n\n // Close log fd (best-effort)\n try {\n closeSync(logFd);\n } catch {\n /* best-effort */\n }\n }\n}\n"],"mappings":"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","names":["import_fs","import_path","import_child_process","platform","import_child_process","import_fs","import_path","resolve","resolve"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackwright-pro/raft",
3
- "version": "1.0.0-alpha.112",
3
+ "version": "1.0.0-alpha.114",
4
4
  "description": "Launch the Pro Otter Raft — verifies integrity, writes init context, spawns code-puppy in foreman mode",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {