@stackwright-pro/raft 1.0.0-alpha.6 → 1.0.0-alpha.60

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ PROPRIETARY SOFTWARE LICENSE
2
+
3
+ Copyright (c) 2024-2026 Per Aspera LLC. All Rights Reserved.
4
+
5
+ This software and associated documentation files (the "Software") are the
6
+ proprietary and confidential property of Per Aspera LLC ("Company").
7
+
8
+ RESTRICTIONS: You may not use, copy, modify, merge, publish, distribute,
9
+ sublicense, sell, or otherwise exploit this Software or any portion thereof
10
+ without the express prior written consent of the Company.
11
+
12
+ GOVERNMENT USE: Use, duplication, or disclosure by the U.S. Government is
13
+ subject to restrictions as set forth in FAR 52.227-19 (Commercial Computer
14
+ Software - Restricted Rights) and DFARS 252.227-7013 (Rights in Technical
15
+ Data and Computer Software), as applicable.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED. IN NO EVENT SHALL THE COMPANY BE LIABLE FOR ANY CLAIM, DAMAGES, OR
19
+ OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
20
+
21
+ For licensing inquiries: legal@peraspera.com
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # @stackwright-pro/raft
2
+
3
+ Launch the Pro Otter Raft — verifies otter integrity, writes init context, and spawns `raft-puppy` (or `code-puppy`) in foreman mode.
4
+
5
+ ## Prerequisites
6
+
7
+ Install the Stackwright-patched build of code-puppy:
8
+
9
+ ```bash
10
+ pip install stackwright-puppy
11
+ ```
12
+
13
+ This provides the `raft-puppy` binary (and `code-puppy` alias) with two fixes required for clean-install compatibility:
14
+
15
+ - MCP servers auto-enable on startup (no manual `/mcp start` needed)
16
+ - Local `.code-puppy.json` project config loading
17
+
18
+ Source: https://github.com/Per-Aspera-LLC/stackwright-puppy
19
+ Upstream: https://github.com/mpfaffenberger/code_puppy (MIT)
20
+
21
+ No specific credentials or API keys are required by the raft itself — see [Authentication](#authentication) below.
22
+
23
+ ## Usage
24
+
25
+ ```bash
26
+ npx @stackwright-pro/raft [options]
27
+ ```
28
+
29
+ ### Options
30
+
31
+ | Flag | Description |
32
+ | ----------------------- | ------------------------------------- |
33
+ | `--project-root <path>` | Project root directory (default: cwd) |
34
+ | `--verbose` | Enable verbose logging |
35
+ | `--help`, `-h` | Show help |
36
+
37
+ ## Authentication
38
+
39
+ The raft does **not** check for API keys or credentials — that is `raft-puppy`/`code-puppy`'s responsibility. Any auth mechanism supported by your binary works:
40
+
41
+ - `claude auth login` — OAuth browser login (interactive sessions)
42
+ - `ANTHROPIC_API_KEY` — Direct Anthropic API key
43
+ - AWS Bedrock, Google Vertex, Ollama, custom inference — configured via your `raft-puppy`/`code-puppy` setup
44
+
45
+ If your binary can talk to a model, the raft will launch successfully.
46
+
47
+ ## Air-gapped & custom deployments
48
+
49
+ To skip the binary version pre-flight check entirely (useful when running a custom or pinned binary that reports a non-standard version string):
50
+
51
+ ```bash
52
+ STACKWRIGHT_SKIP_PREFLIGHT=true npx @stackwright-pro/raft
53
+ ```
54
+
55
+ This bypasses all raft-level validation and spawns your binary directly. The binary itself is still responsible for validating its own environment.
56
+
57
+ ## Binary resolution
58
+
59
+ `raft` searches for the Python binary in this order:
60
+
61
+ 1. `STACKWRIGHT_CODE_PUPPY_PATH` env var (explicit override)
62
+ 2. `raft-puppy` on `PATH` (stackwright-puppy fork — preferred)
63
+ 3. `code-puppy` on `PATH` (vanilla upstream — fallback)
64
+
65
+ If neither is found, the error message will guide you to install `stackwright-puppy`.
66
+
67
+ ## What it does
68
+
69
+ 1. Acquires a pipeline lock (`.stackwright/.lock`) to prevent concurrent sessions
70
+ 2. Writes/enriches `.stackwright/init-context.json` (project name, spec path, theme)
71
+ 3. Registers the `@stackwright-pro/mcp` server in `~/.code_puppy/mcp_servers.json`
72
+ 4. Verifies otter file integrity
73
+ 5. Spawns `raft-puppy` / `code-puppy` in foreman mode with `STACKWRIGHT_PROJECT_ROOT` set
74
+
75
+ ## Version
76
+
77
+ See [CHANGELOG.md](./CHANGELOG.md) for release history.
package/dist/index.js CHANGED
@@ -6,17 +6,61 @@ var import_fs2 = require("fs");
6
6
  var import_path2 = require("path");
7
7
  var import_child_process2 = require("child_process");
8
8
  var import_integrity = require("@stackwright-pro/mcp/integrity");
9
+ var import_type_schemas = require("@stackwright-pro/mcp/type-schemas");
9
10
 
10
11
  // src/lib.ts
11
12
  var import_child_process = require("child_process");
12
13
  var import_fs = require("fs");
13
14
  var import_path = require("path");
14
15
  var import_os = require("os");
16
+ var CODE_PUPPY_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
17
+ // Shell / execution environment
18
+ "PATH",
19
+ "HOME",
20
+ "USER",
21
+ "USERNAME",
22
+ "SHELL",
23
+ "TMPDIR",
24
+ "TMP",
25
+ "TEMP",
26
+ // Terminal
27
+ "TERM",
28
+ "COLORTERM",
29
+ "COLUMNS",
30
+ "LINES",
31
+ "NO_COLOR",
32
+ "FORCE_COLOR",
33
+ // Language / locale
34
+ "LANG",
35
+ "LC_ALL",
36
+ "LC_CTYPE",
37
+ "LC_MESSAGES",
38
+ // Network proxy — needed for outbound LLM API calls behind corporate proxies
39
+ "HTTP_PROXY",
40
+ "HTTPS_PROXY",
41
+ "NO_PROXY",
42
+ "http_proxy",
43
+ "https_proxy",
44
+ "no_proxy",
45
+ // LLM API keys — the only credentials code-puppy should have
46
+ "ANTHROPIC_API_KEY",
47
+ "ANTHROPIC_BASE_URL",
48
+ "OPENAI_API_KEY",
49
+ // Node runtime
50
+ "NODE_ENV",
51
+ // Stackwright-specific
52
+ "STACKWRIGHT_PROJECT_ROOT",
53
+ "STACKWRIGHT_SKIP_PREFLIGHT",
54
+ "STACKWRIGHT_CODE_PUPPY_PATH"
55
+ ]);
56
+ var CODE_PUPPY_ENV_PREFIXES = ["PYTHON", "LC_"];
57
+ var MIN_SUPPORTED_CODE_PUPPY_VERSION = "0.0.513";
15
58
  function parseArgs(argv) {
16
59
  const args = {
17
60
  projectRoot: process.cwd(),
18
61
  verbose: false,
19
- help: false
62
+ help: false,
63
+ passEnvKeys: []
20
64
  };
21
65
  const raw = argv.slice(2);
22
66
  for (let i = 0; i < raw.length; i++) {
@@ -32,6 +76,9 @@ function parseArgs(argv) {
32
76
  case "-h":
33
77
  args.help = true;
34
78
  break;
79
+ case "--pass-env":
80
+ args.passEnvKeys.push(raw[++i] ?? die("--pass-env requires a KEY argument"));
81
+ break;
35
82
  default:
36
83
  die(`Unknown option: ${token}
37
84
  Run with --help for usage.`);
@@ -42,14 +89,19 @@ Run with --help for usage.`);
42
89
  function printHelp() {
43
90
  console.log(
44
91
  `
45
- \u{1F9A6} launch-raft \u2014 Spawn code-puppy in foreman mode
92
+ \u{1F9A6} @stackwright-pro/raft \u2014 Spawn raft-puppy (or code-puppy) in foreman mode
46
93
 
47
94
  Usage: launch-raft [options]
48
95
 
49
96
  Options:
50
97
  --project-root <path> Project root directory (default: cwd)
51
98
  --verbose Enable verbose logging
99
+ --pass-env <KEY> Forward an additional env var to code-puppy (repeatable)
52
100
  --help, -h Show this help
101
+
102
+ Prerequisites:
103
+ pip install stackwright-puppy # provides raft-puppy + code-puppy alias
104
+ Or: pip install code-puppy # fallback (MCP tools may not auto-start)
53
105
  `.trim()
54
106
  );
55
107
  }
@@ -65,6 +117,20 @@ function verbose(message, isVerbose) {
65
117
  console.log(` ${message}`);
66
118
  }
67
119
  }
120
+ function buildCodePuppyEnv(extraVars = {}, passEnvKeys = []) {
121
+ const env = {};
122
+ for (const [key, value] of Object.entries(process.env)) {
123
+ if (value === void 0) continue;
124
+ const allowed = CODE_PUPPY_ENV_ALLOWLIST.has(key) || CODE_PUPPY_ENV_PREFIXES.some((prefix) => key.startsWith(prefix)) || passEnvKeys.includes(key);
125
+ if (allowed) {
126
+ env[key] = value;
127
+ }
128
+ }
129
+ for (const [key, value] of Object.entries(extraVars)) {
130
+ env[key] = value;
131
+ }
132
+ return env;
133
+ }
68
134
  var LOCK_FILE = ".stackwright/.lock";
69
135
  function acquireLock(projectRoot) {
70
136
  const lockPath = (0, import_path.join)(projectRoot, LOCK_FILE);
@@ -206,66 +272,157 @@ process.on("SIGTERM", () => {
206
272
  }
207
273
  process.exit(0);
208
274
  });
209
- function findCodePuppy() {
275
+ function getTrustedBinaryDirs() {
276
+ return [
277
+ (0, import_path.join)((0, import_os.homedir)(), ".local", "bin"),
278
+ // pip user install, uvx, pipx (Linux/macOS)
279
+ "/opt/homebrew/bin",
280
+ // brew Apple Silicon
281
+ "/usr/local/bin",
282
+ // brew Intel, pip system install
283
+ "/usr/bin"
284
+ // system package managers (apt, dnf)
285
+ ];
286
+ }
287
+ function findCodePuppy(trustedDirsOverride) {
210
288
  let candidate = null;
211
289
  const envPath = process.env["STACKWRIGHT_CODE_PUPPY_PATH"];
212
290
  if (envPath) {
213
291
  candidate = envPath;
214
292
  }
215
293
  if (!candidate) {
216
- try {
217
- candidate = (0, import_child_process.execSync)("which code-puppy", { encoding: "utf-8" }).trim();
218
- } catch {
294
+ const trustedDirs = trustedDirsOverride ?? getTrustedBinaryDirs();
295
+ outer: for (const bin of ["raft-puppy", "code-puppy"]) {
296
+ for (const dir of trustedDirs) {
297
+ const p = (0, import_path.join)(dir, bin);
298
+ if ((0, import_fs.existsSync)(p)) {
299
+ candidate = p;
300
+ break outer;
301
+ }
302
+ }
303
+ }
304
+ }
305
+ if (!candidate) {
306
+ for (const bin of ["raft-puppy", "code-puppy"]) {
307
+ const whichResult = (0, import_child_process.spawnSync)("which", [bin], { encoding: "utf-8" });
308
+ if (whichResult.status === 0 && whichResult.stdout) {
309
+ candidate = whichResult.stdout.trim();
310
+ if (candidate) break;
311
+ }
219
312
  }
220
313
  }
221
314
  if (!candidate) {
222
315
  die(
223
- "code-puppy not found. Install with:\n pip install code-puppy\n or: uvx code-puppy\nOr set STACKWRIGHT_CODE_PUPPY_PATH."
316
+ "raft-puppy (or code-puppy) not found.\n\nInstall the Stackwright-patched build (recommended):\n pip install stackwright-puppy\n\nOr install vanilla code-puppy (MCP tools may not auto-start):\n pip install code-puppy\n\nOr set STACKWRIGHT_CODE_PUPPY_PATH to the binary path."
224
317
  );
225
318
  }
226
319
  const resolved = (0, import_path.resolve)(candidate);
227
320
  if (!(0, import_fs.existsSync)(resolved)) {
228
- die(`code-puppy not found at resolved path: ${resolved}`);
321
+ die(`raft-puppy/code-puppy not found at resolved path: ${resolved}`);
229
322
  }
230
323
  let realBinary;
231
324
  try {
232
325
  realBinary = (0, import_fs.realpathSync)(resolved);
233
326
  } catch {
234
327
  die(
235
- `code-puppy at ${resolved} has a broken symlink \u2014 cannot resolve to a real path. Try reinstalling code-puppy or set STACKWRIGHT_CODE_PUPPY_PATH to the real binary.`
328
+ `raft-puppy at ${resolved} has a broken symlink \u2014 cannot resolve to a real path. Try reinstalling stackwright-puppy or set STACKWRIGHT_CODE_PUPPY_PATH to the real binary.`
236
329
  );
237
330
  }
238
331
  if (!(0, import_fs.existsSync)(realBinary)) {
239
- die(`code-puppy symlink at ${resolved} points to a missing file: ${realBinary}`);
332
+ die(`raft-puppy symlink at ${resolved} points to a missing file: ${realBinary}`);
333
+ }
334
+ let fd;
335
+ try {
336
+ fd = (0, import_fs.openSync)(realBinary, "r");
337
+ } catch (err) {
338
+ die(
339
+ `Cannot open raft-puppy at ${realBinary} for validation: ${String(err)}. Try reinstalling stackwright-puppy or set STACKWRIGHT_CODE_PUPPY_PATH.`
340
+ );
240
341
  }
241
- const stat = (0, import_fs.lstatSync)(realBinary);
342
+ const stat = (0, import_fs.fstatSync)(fd);
242
343
  if (stat.mode & 18) {
344
+ (0, import_fs.closeSync)(fd);
243
345
  die(
244
- `code-puppy at ${realBinary} is group- or world-writable (mode: ${(stat.mode & 511).toString(8)}) \u2014 refusing to exec.`
346
+ `raft-puppy at ${realBinary} is group- or world-writable (mode: ${(stat.mode & 511).toString(8)}) \u2014 refusing to exec.`
245
347
  );
246
348
  }
247
349
  if (stat.mode & 3072) {
248
- die(`code-puppy at ${realBinary} has setuid/setgid bits \u2014 refusing to exec.`);
350
+ (0, import_fs.closeSync)(fd);
351
+ die(`raft-puppy at ${realBinary} has setuid/setgid bits \u2014 refusing to exec.`);
352
+ }
353
+ return { path: realBinary, fd };
354
+ }
355
+ function parseSemver(version) {
356
+ const parts = version.split("-")[0].split(".").map(Number);
357
+ return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
358
+ }
359
+ function semverGte(a, b) {
360
+ const [aMaj, aMin, aPatch] = parseSemver(a);
361
+ const [bMaj, bMin, bPatch] = parseSemver(b);
362
+ if (aMaj !== bMaj) return aMaj > bMaj;
363
+ if (aMin !== bMin) return aMin > bMin;
364
+ return aPatch >= bPatch;
365
+ }
366
+ function validateBinaryVersion(binaryPath) {
367
+ if (process.env["STACKWRIGHT_SKIP_PREFLIGHT"] === "true") {
368
+ log("Pre-flight checks skipped via STACKWRIGHT_SKIP_PREFLIGHT");
369
+ return;
370
+ }
371
+ let versionOutput;
372
+ const versionResult = (0, import_child_process.spawnSync)(binaryPath, ["--version"], {
373
+ encoding: "utf-8",
374
+ timeout: 5e3,
375
+ stdio: ["ignore", "pipe", "pipe"]
376
+ });
377
+ if (versionResult.error !== void 0 || versionResult.status !== 0) {
378
+ die(
379
+ `Could not determine raft-puppy / code-puppy version.
380
+ Binary: ${binaryPath}
381
+ Minimum required: ${MIN_SUPPORTED_CODE_PUPPY_VERSION}
382
+
383
+ Run: pip install --upgrade stackwright-puppy
384
+ Or: pip install --upgrade code-puppy`
385
+ );
386
+ }
387
+ versionOutput = versionResult.stdout.trim();
388
+ const match = /(\d+\.\d+\.\d+(?:-[^\s]+)?)/.exec(versionOutput);
389
+ if (!match || !match[1]) {
390
+ die(
391
+ `Could not parse version from raft-puppy / code-puppy output: ${JSON.stringify(versionOutput)}
392
+ Minimum required: ${MIN_SUPPORTED_CODE_PUPPY_VERSION}
393
+
394
+ Run: pip install --upgrade stackwright-puppy
395
+ Or: pip install --upgrade code-puppy`
396
+ );
397
+ }
398
+ const installedVersion = match[1];
399
+ const isDevBuild = installedVersion === "0.0.0" || /^0\.0\.0-.+$/.test(installedVersion);
400
+ if (isDevBuild) {
401
+ console.warn(
402
+ `\u26A0\uFE0F Dev build detected (${installedVersion}) \u2014 skipping minimum version check.
403
+ Minimum required for production: ${MIN_SUPPORTED_CODE_PUPPY_VERSION}
404
+ To suppress this warning in CI: set STACKWRIGHT_SKIP_PREFLIGHT=true`
405
+ );
406
+ return;
407
+ }
408
+ if (!semverGte(installedVersion, MIN_SUPPORTED_CODE_PUPPY_VERSION)) {
409
+ die(
410
+ `raft-puppy / code-puppy ${installedVersion} is below the minimum required version.
411
+ Installed: ${installedVersion}
412
+ Minimum required: ${MIN_SUPPORTED_CODE_PUPPY_VERSION}
413
+
414
+ Run: pip install --upgrade stackwright-puppy
415
+ Or: pip install --upgrade code-puppy`
416
+ );
249
417
  }
250
- return realBinary;
251
418
  }
252
419
  function ensureMcpConfig(projectRoot) {
253
- const xdgConfigHome = process.env["XDG_CONFIG_HOME"];
254
- const configDir = xdgConfigHome ? (0, import_path.join)(xdgConfigHome, "code_puppy") : (0, import_path.join)((0, import_os.homedir)(), ".code_puppy");
255
- const configPath = (0, import_path.join)(configDir, "mcp_servers.json");
420
+ const workspaceDir = (0, import_path.join)(projectRoot, ".code-puppy");
421
+ const configPath = (0, import_path.join)(workspaceDir, "mcp_servers.json");
256
422
  if ((0, import_fs.existsSync)(configPath) && (0, import_fs.lstatSync)(configPath).isSymbolicLink()) {
257
- die("`~/.code_puppy/mcp_servers.json` is a symlink \u2014 refusing to write. Check for tampering.");
258
- }
259
- (0, import_fs.mkdirSync)(configDir, { recursive: true });
260
- const localServerPath = (0, import_path.join)(
261
- projectRoot,
262
- "node_modules",
263
- "@stackwright-pro",
264
- "mcp",
265
- "dist",
266
- "server.js"
267
- );
268
- const serverConfig = (0, import_fs.existsSync)(localServerPath) ? { command: "node", args: ["node_modules/@stackwright-pro/mcp/dist/server.js"] } : { command: "npx", args: ["--yes", "@stackwright-pro/mcp"] };
423
+ die(".code-puppy/mcp_servers.json is a symlink \u2014 refusing to write. Check for tampering.");
424
+ }
425
+ (0, import_fs.mkdirSync)(workspaceDir, { recursive: true });
269
426
  let existing = {};
270
427
  if ((0, import_fs.existsSync)(configPath)) {
271
428
  try {
@@ -274,17 +431,47 @@ function ensureMcpConfig(projectRoot) {
274
431
  } catch {
275
432
  }
276
433
  }
434
+ const serverConfig = {
435
+ type: "stdio",
436
+ command: "pnpm",
437
+ args: ["exec", "stackwright-pro-mcp"],
438
+ enabled: true,
439
+ cwd: "${PROJECT_ROOT}"
440
+ };
277
441
  const merged = {
278
442
  ...existing,
279
443
  mcp_servers: {
280
444
  ...existing.mcp_servers ?? {},
281
- "stackwright-pro": serverConfig
445
+ "stackwright-pro-mcp": serverConfig
282
446
  }
283
447
  };
284
448
  const tmpPath = `${configPath}.tmp`;
285
449
  (0, import_fs.writeFileSync)(tmpPath, JSON.stringify(merged, null, 2), "utf-8");
286
450
  (0, import_fs.renameSync)(tmpPath, configPath);
287
- log("MCP server registered \u2192 ~/.code_puppy/mcp_servers.json");
451
+ log("MCP server registered \u2192 .code-puppy/mcp_servers.json");
452
+ }
453
+ function ensureWorkspaceConfig(projectRoot) {
454
+ const workspaceDir = (0, import_path.join)(projectRoot, ".code-puppy");
455
+ const configPath = (0, import_path.join)(workspaceDir, "config.json");
456
+ if ((0, import_fs.existsSync)(workspaceDir) && (0, import_fs.lstatSync)(workspaceDir).isSymbolicLink()) {
457
+ die(".code-puppy/ is a symlink \u2014 refusing to write. Check for tampering.");
458
+ }
459
+ (0, import_fs.mkdirSync)(workspaceDir, { recursive: true });
460
+ if ((0, import_fs.existsSync)(configPath) && (0, import_fs.lstatSync)(configPath).isSymbolicLink()) {
461
+ die(".code-puppy/config.json is a symlink \u2014 refusing to write. Check for tampering.");
462
+ }
463
+ let existing = {};
464
+ if ((0, import_fs.existsSync)(configPath)) {
465
+ try {
466
+ existing = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
467
+ } catch {
468
+ }
469
+ }
470
+ const merged = { ...existing, projectOnly: true };
471
+ const tmpPath = `${configPath}.tmp`;
472
+ (0, import_fs.writeFileSync)(tmpPath, JSON.stringify(merged, null, 2), "utf-8");
473
+ (0, import_fs.renameSync)(tmpPath, configPath);
474
+ log("Workspace config written \u2192 .code-puppy/config.json (projectOnly: true)");
288
475
  }
289
476
  function printResumeStatus(projectRoot) {
290
477
  const pipelineStatePath = (0, import_path.join)(projectRoot, ".stackwright", "pipeline-state.json");
@@ -333,6 +520,48 @@ function resolveOtterDir(projectRoot) {
333
520
  }
334
521
  return null;
335
522
  }
523
+ function syncAgents(projectRoot, isVerbose = false) {
524
+ const agentsDir = (0, import_path.join)(projectRoot, ".code-puppy", "agents");
525
+ const otterDir = resolveOtterDir(projectRoot);
526
+ if (!otterDir) {
527
+ verbose("No otters directory found \u2014 skipping agent sync", isVerbose);
528
+ return;
529
+ }
530
+ if ((0, import_fs.existsSync)(agentsDir) && (0, import_fs.lstatSync)(agentsDir).isSymbolicLink()) {
531
+ die(".code-puppy/agents is a symlink \u2014 refusing to write. Check for tampering.");
532
+ }
533
+ (0, import_fs.mkdirSync)(agentsDir, { recursive: true });
534
+ let synced = 0;
535
+ let skipped = 0;
536
+ try {
537
+ const files = (0, import_fs.readdirSync)(otterDir);
538
+ for (const file of files) {
539
+ if (!file.endsWith("-otter.json")) continue;
540
+ const src = (0, import_path.join)(otterDir, file);
541
+ const dest = (0, import_path.join)(agentsDir, file);
542
+ if ((0, import_fs.existsSync)(dest) && (0, import_fs.lstatSync)(dest).isSymbolicLink()) {
543
+ verbose(`Skipping ${file} \u2014 dest is a symlink`, isVerbose);
544
+ skipped++;
545
+ continue;
546
+ }
547
+ const tmp = `${dest}.tmp`;
548
+ const content = (0, import_fs.readFileSync)(src);
549
+ (0, import_fs.writeFileSync)(tmp, content);
550
+ (0, import_fs.renameSync)(tmp, dest);
551
+ verbose(`Synced: ${file}`, isVerbose);
552
+ synced++;
553
+ }
554
+ } catch (err) {
555
+ const msg = err instanceof Error ? err.message : String(err);
556
+ console.warn(`\u26A0\uFE0F Agent sync partial failure: ${msg}`);
557
+ return;
558
+ }
559
+ if (synced > 0) {
560
+ log(
561
+ `Agents synced \u2192 .code-puppy/agents/ (${synced} otters${skipped > 0 ? `, ${skipped} skipped` : ""})`
562
+ );
563
+ }
564
+ }
336
565
 
337
566
  // src/index.ts
338
567
  function main() {
@@ -351,6 +580,21 @@ function main() {
351
580
  log("Launching Pro Otter Raft...");
352
581
  writeInitContext(projectRoot);
353
582
  verbose("Init context written", args.verbose);
583
+ try {
584
+ const stackwrightDir = (0, import_path2.join)(projectRoot, ".stackwright");
585
+ (0, import_fs2.mkdirSync)(stackwrightDir, { recursive: true });
586
+ const schemaSummary = (0, import_type_schemas.buildTypeSchemaSummary)();
587
+ (0, import_fs2.writeFileSync)(
588
+ (0, import_path2.join)(stackwrightDir, "type-schemas.json"),
589
+ JSON.stringify(schemaSummary, null, 2) + "\n"
590
+ );
591
+ verbose("Type schemas sink written", args.verbose);
592
+ } catch (err) {
593
+ console.warn("\u26A0\uFE0F Could not write type-schemas.json:", String(err));
594
+ }
595
+ ensureWorkspaceConfig(projectRoot);
596
+ ensureMcpConfig(projectRoot);
597
+ syncAgents(projectRoot, args.verbose);
354
598
  const otterDir = resolveOtterDir(projectRoot);
355
599
  if (!otterDir) {
356
600
  die(
@@ -359,27 +603,43 @@ function main() {
359
603
  }
360
604
  const result = (0, import_integrity.verifyAllOtters)(otterDir);
361
605
  if (result.failed.length > 0) {
362
- console.error("\u274C Otter integrity check failed:");
606
+ console.warn("\u26A0\uFE0F Otter integrity check warnings (non-blocking):");
363
607
  for (const f of result.failed) {
364
- console.error(` ${f.filename}: ${f.error}`);
608
+ console.warn(` ${f.filename}: ${f.error}`);
365
609
  }
366
- die("Fix the above integrity failures before launching the raft.");
610
+ console.warn(" Note: SHA-256 pinning will be replaced by PKI-signed deployment manifests.");
611
+ console.warn(
612
+ " See: https://github.com/Per-Aspera-LLC/stackwright-pro/issues (signing model issue)"
613
+ );
614
+ } else {
615
+ log(`\u2705 All ${result.verified.length} otters verified`);
367
616
  }
368
- log(`\u2705 All ${result.verified.length} otters verified`);
369
617
  printResumeStatus(projectRoot);
370
- const executable = findCodePuppy();
371
- verbose(`Resolved code-puppy: ${executable}`, args.verbose);
372
- ensureMcpConfig(projectRoot);
373
- log("Spawning code-puppy raft session...");
618
+ const { path: executable, fd: binaryFd } = findCodePuppy();
619
+ verbose(`Resolved raft-puppy / code-puppy: ${executable}`, args.verbose);
620
+ validateBinaryVersion(executable);
621
+ log("Spawning raft-puppy / code-puppy raft session...");
374
622
  const spawnArgs = ["Begin", "--interactive", "--agent", "stackwright-pro-foreman-otter"];
375
- verbose(`cmd: ${executable} ${spawnArgs.join(" ")}`, args.verbose);
376
- const child = (0, import_child_process2.spawn)(executable, spawnArgs, {
623
+ const spawnTarget = process.platform === "linux" ? `/proc/self/fd/${binaryFd}` : executable;
624
+ if (process.platform !== "linux") {
625
+ verbose(
626
+ `macOS/other: spawning via path (residual TOCTOU \u2014 /proc unavailable). See GH#128 for threat model documentation.`,
627
+ args.verbose
628
+ );
629
+ }
630
+ verbose(
631
+ `cmd: ${spawnTarget} ${spawnArgs.join(" ")}${spawnTarget !== executable ? ` (real: ${executable})` : ""}`,
632
+ args.verbose
633
+ );
634
+ const child = (0, import_child_process2.spawn)(spawnTarget, spawnArgs, {
377
635
  stdio: "inherit",
378
- env: {
379
- ...process.env,
380
- STACKWRIGHT_PROJECT_ROOT: projectRoot
381
- }
636
+ cwd: projectRoot,
637
+ env: buildCodePuppyEnv({ STACKWRIGHT_PROJECT_ROOT: projectRoot }, args.passEnvKeys)
382
638
  });
639
+ try {
640
+ (0, import_fs2.closeSync)(binaryFd);
641
+ } catch {
642
+ }
383
643
  const forward = (signal) => {
384
644
  if (child.pid) child.kill(signal);
385
645
  };
@@ -387,7 +647,7 @@ function main() {
387
647
  const onSigterm = () => forward("SIGTERM");
388
648
  process.on("SIGINT", onSigint);
389
649
  process.on("SIGTERM", onSigterm);
390
- child.on("error", (err) => die(`Failed to spawn code-puppy: ${err.message}`));
650
+ child.on("error", (err) => die(`Failed to spawn raft-puppy / code-puppy: ${err.message}`));
391
651
  child.on("close", (code, signal) => {
392
652
  process.off("SIGINT", onSigint);
393
653
  process.off("SIGTERM", onSigterm);
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 } from 'fs';\nimport { join, resolve } from 'path';\nimport { spawn } from 'child_process';\nimport { verifyAllOtters } from '@stackwright-pro/mcp/integrity';\nimport {\n parseArgs,\n printHelp,\n writeInitContext,\n findCodePuppy,\n ensureMcpConfig,\n printResumeStatus,\n resolveOtterDir,\n die,\n log,\n verbose,\n} from './lib.js';\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 // 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.error('❌ Otter integrity check failed:');\n for (const f of result.failed) {\n console.error(` ${f.filename}: ${f.error}`);\n }\n die('Fix the above integrity failures before launching the raft.');\n }\n log(`✅ All ${result.verified.length} otters verified`);\n\n // 3. Print resume status\n printResumeStatus(projectRoot);\n\n // 4. Resolve code-puppy\n const executable = findCodePuppy();\n verbose(`Resolved code-puppy: ${executable}`, args.verbose);\n\n // 4b. Register MCP server into ~/.code_puppy/mcp_servers.json\n ensureMcpConfig(projectRoot);\n\n // 5. Spawn code-puppy\n log('Spawning code-puppy raft session...');\n\n const spawnArgs = ['Begin', '--interactive', '--agent', 'stackwright-pro-foreman-otter'];\n\n verbose(`cmd: ${executable} ${spawnArgs.join(' ')}`, args.verbose);\n\n const child = spawn(executable, spawnArgs, {\n stdio: 'inherit',\n env: {\n ...process.env,\n STACKWRIGHT_PROJECT_ROOT: projectRoot,\n },\n });\n\n // Forward signals to child — let it clean up gracefully\n const forward = (signal: NodeJS.Signals) => {\n if (child.pid) child.kill(signal);\n };\n const onSigint = () => forward('SIGINT');\n const onSigterm = () => forward('SIGTERM');\n process.on('SIGINT', onSigint);\n process.on('SIGTERM', onSigterm);\n\n child.on('error', (err) => die(`Failed to spawn code-puppy: ${err.message}`));\n child.on('close', (code, signal) => {\n process.off('SIGINT', onSigint);\n process.off('SIGTERM', onSigterm);\n if (signal) {\n process.kill(process.pid, signal);\n } else {\n process.exit(code ?? 1);\n }\n });\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 { execSync } from 'child_process';\nimport {\n existsSync,\n lstatSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n realpathSync,\n renameSync,\n writeFileSync,\n rmSync,\n} from 'fs';\nimport { join, resolve } from 'path';\nimport { homedir, hostname } from 'os';\n\n// ─── CLI Argument Parsing ────────────────────────────────────────────────────\n\nexport interface ParsedArgs {\n projectRoot: string;\n verbose: boolean;\n help: boolean;\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const args: ParsedArgs = {\n projectRoot: process.cwd(),\n verbose: false,\n help: false,\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 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🦦 launch-raft — Spawn 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 --help, -h Show this help\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// ─── 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// ─── 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 existingPid = lockData['pid'] ?? '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// ─── Find code-puppy Executable ─────────────────────────────────────────────\n\nexport function findCodePuppy(): string {\n let candidate: string | null = null;\n\n // 1. Explicit env var override\n const envPath = process.env['STACKWRIGHT_CODE_PUPPY_PATH'];\n if (envPath) {\n candidate = envPath;\n }\n\n // 2. PATH lookup via `which`\n if (!candidate) {\n try {\n candidate = execSync('which code-puppy', { encoding: 'utf-8' }).trim();\n } catch {\n // Not on PATH\n }\n }\n\n if (!candidate) {\n die(\n 'code-puppy not found. Install with:\\n' +\n ' pip install code-puppy\\n' +\n ' or: uvx code-puppy\\n' +\n 'Or set STACKWRIGHT_CODE_PUPPY_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(`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 // We resolve to the real target so security checks apply to the binary that\n // will actually be executed, not to an intermediate symlink.\n let realBinary: string;\n try {\n realBinary = realpathSync(resolved);\n } catch {\n die(\n `code-puppy at ${resolved} has a broken symlink — cannot resolve to a real path. ` +\n `Try reinstalling code-puppy or set STACKWRIGHT_CODE_PUPPY_PATH to the real binary.`\n );\n }\n\n if (!existsSync(realBinary)) {\n die(`code-puppy symlink at ${resolved} points to a missing file: ${realBinary}`);\n }\n\n // Security checks — applied to the real binary, not the symlink entry point\n const stat = lstatSync(realBinary);\n\n // Refuse world-writable or group-writable binaries\n if (stat.mode & 0o022) {\n die(\n `code-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 die(`code-puppy at ${realBinary} has setuid/setgid bits — refusing to exec.`);\n }\n\n return realBinary;\n}\n\n// ─── Ensure MCP Config ─────────────────────────────────────────────────────\n\nexport function ensureMcpConfig(projectRoot: string): void {\n const xdgConfigHome = process.env['XDG_CONFIG_HOME'];\n const configDir = xdgConfigHome\n ? join(xdgConfigHome, 'code_puppy')\n : join(homedir(), '.code_puppy');\n const configPath = join(configDir, '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(configDir, { recursive: true });\n\n const localServerPath = join(\n projectRoot,\n 'node_modules',\n '@stackwright-pro',\n 'mcp',\n 'dist',\n 'server.js'\n );\n\n const serverConfig = existsSync(localServerPath)\n ? { command: 'node', args: ['node_modules/@stackwright-pro/mcp/dist/server.js'] }\n : { command: 'npx', args: ['--yes', '@stackwright-pro/mcp'] };\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 const merged = {\n ...existing,\n mcp_servers: {\n ...(existing.mcp_servers ?? {}),\n 'stackwright-pro': 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// ─── 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// ─── 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"],"mappings":"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","names":["import_fs","import_path","import_child_process"]}
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, 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 ensureWorkspaceConfig,\n ensureMcpConfig,\n syncAgents,\n printResumeStatus,\n resolveOtterDir,\n buildCodePuppyEnv,\n die,\n log,\n verbose,\n} from './lib.js';\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 // 1aa. Ensure workspace config — write .code-puppy/config.json (projectOnly: true)\n // Must come before ensureMcpConfig so the workspace dir exists.\n ensureWorkspaceConfig(projectRoot);\n\n // 1b. 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 // 1c. 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\n const { path: executable, fd: binaryFd } = findCodePuppy();\n verbose(`Resolved raft-puppy / code-puppy: ${executable}`, args.verbose);\n\n // 4a. Pre-flight: validate binary version before spawning\n validateBinaryVersion(executable);\n\n // 5. Spawn raft-puppy / code-puppy\n log('Spawning raft-puppy / code-puppy raft session...');\n\n const spawnArgs = ['Begin', '--interactive', '--agent', 'stackwright-pro-foreman-otter'];\n\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 // 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 spawnTarget = process.platform === 'linux' ? `/proc/self/fd/${binaryFd}` : executable;\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 verbose(\n `cmd: ${spawnTarget} ${spawnArgs.join(' ')}${spawnTarget !== executable ? ` (real: ${executable})` : ''}`,\n args.verbose\n );\n\n const child = spawn(spawnTarget, spawnArgs, {\n stdio: 'inherit',\n cwd: projectRoot,\n env: buildCodePuppyEnv({ STACKWRIGHT_PROJECT_ROOT: projectRoot }, args.passEnvKeys),\n });\n\n // Close the fd — the process image is loaded, the pinned inode is no longer needed.\n try {\n closeSync(binaryFd);\n } catch {\n // Non-critical — fd cleanup is best-effort\n }\n\n // Forward signals to child — let it clean up gracefully\n const forward = (signal: NodeJS.Signals) => {\n if (child.pid) child.kill(signal);\n };\n const onSigint = () => forward('SIGINT');\n const onSigterm = () => forward('SIGTERM');\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 if (signal) {\n process.kill(process.pid, signal);\n } else {\n process.exit(code ?? 1);\n }\n });\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.513).\n * This floor is set explicitly to the minimum feature set the raft requires —\n * bump this when a new raft-puppy feature becomes a hard dependency.\n * Dev builds (0.0.0 / 0.0.0-*) are handled separately by the isDevBuild bypass below.\n */\nexport const MIN_SUPPORTED_CODE_PUPPY_VERSION = '0.0.513';\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}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const args: ParsedArgs = {\n projectRoot: process.cwd(),\n verbose: false,\n help: false,\n passEnvKeys: [],\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 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 --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// ─── 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 existingPid = lockData['pid'] ?? '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 let versionOutput: string;\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 versionResult = spawnSync(binaryPath, ['--version'], {\n encoding: 'utf-8',\n timeout: 5000,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n if (versionResult.error !== undefined || versionResult.status !== 0) {\n die(\n `Could not determine raft-puppy / code-puppy version.\\n` +\n ` Binary: ${binaryPath}\\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 versionOutput = versionResult.stdout.trim();\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: ${JSON.stringify(versionOutput)}\\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 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 // ${PROJECT_ROOT} is a literal — raft-puppy expands it at runtime to the\n // directory containing .code-puppy/ (walk-up discovery root).\n const serverConfig = {\n type: 'stdio',\n command: 'pnpm',\n args: ['exec', 'stackwright-pro-mcp'],\n enabled: true,\n cwd: '${PROJECT_ROOT}',\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 projectOnly: true.\n *\n * This gates the workspace so that global ~/.code_puppy/ is completely\n * ignored when raft-puppy / code-puppy starts inside this project.\n * Only .code-puppy/agents/ otters + the base code-puppy agent will be\n * visible — global plugins and user agents are hidden.\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 = { ...existing, projectOnly: true };\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 (projectOnly: true)');\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// ─── 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"],"mappings":"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","names":["import_fs","import_path","import_child_process"]}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@stackwright-pro/raft",
3
- "version": "1.0.0-alpha.6",
3
+ "version": "1.0.0-alpha.60",
4
4
  "description": "Launch the Pro Otter Raft — verifies integrity, writes init context, spawns code-puppy in foreman mode",
5
- "license": "MIT",
5
+ "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/Per-Aspera-LLC/stackwright-pro"
@@ -13,20 +13,25 @@
13
13
  "files": [
14
14
  "dist"
15
15
  ],
16
+ "engines": {
17
+ "node": ">=22",
18
+ "pnpm": ">=10"
19
+ },
16
20
  "publishConfig": {
17
- "access": "public"
21
+ "access": "public",
22
+ "tag": "alpha"
18
23
  },
19
24
  "dependencies": {
20
- "@stackwright-pro/mcp": "0.2.0-alpha.7"
25
+ "@stackwright-pro/mcp": "0.2.0-alpha.48"
21
26
  },
22
27
  "peerDependencies": {
23
- "@stackwright-pro/otters": ">=1.0.0-alpha.18"
28
+ "@stackwright-pro/otters": ">=1.0.0-alpha.37"
24
29
  },
25
30
  "devDependencies": {
26
- "@types/node": "^24.0.0",
27
- "typescript": "^5.0",
28
- "tsup": "^8.5",
29
- "vitest": "^4.0.18"
31
+ "@types/node": "^25.9.1",
32
+ "typescript": "^6.0.3",
33
+ "tsup": "^8.5.1",
34
+ "vitest": "^4.1.7"
30
35
  },
31
36
  "scripts": {
32
37
  "build": "tsup",