@stackwright-pro/raft 0.1.0-alpha.4 → 1.0.0-alpha.10

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
@@ -1,5 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
3
25
 
4
26
  // src/index.ts
5
27
  var import_fs2 = require("fs");
@@ -15,7 +37,6 @@ var import_os = require("os");
15
37
  function parseArgs(argv) {
16
38
  const args = {
17
39
  projectRoot: process.cwd(),
18
- mcpConfig: null,
19
40
  verbose: false,
20
41
  help: false
21
42
  };
@@ -26,9 +47,6 @@ function parseArgs(argv) {
26
47
  case "--project-root":
27
48
  args.projectRoot = raw[++i] ?? die("--project-root requires a path argument");
28
49
  break;
29
- case "--mcp-config":
30
- args.mcpConfig = raw[++i] ?? die("--mcp-config requires a path argument");
31
- break;
32
50
  case "--verbose":
33
51
  args.verbose = true;
34
52
  break;
@@ -52,7 +70,6 @@ Usage: launch-raft [options]
52
70
 
53
71
  Options:
54
72
  --project-root <path> Project root directory (default: cwd)
55
- --mcp-config <path> Path to MCP configuration file
56
73
  --verbose Enable verbose logging
57
74
  --help, -h Show this help
58
75
  `.trim()
@@ -254,45 +271,14 @@ function findCodePuppy() {
254
271
  }
255
272
  return realBinary;
256
273
  }
257
- function findMcpConfig(projectRoot, explicit) {
258
- if (explicit) {
259
- const resolved = (0, import_path.resolve)(explicit);
260
- if (!(0, import_fs.existsSync)(resolved)) {
261
- die(`MCP config not found: ${resolved}`);
262
- }
263
- const configStat = (0, import_fs.lstatSync)(resolved);
264
- if (configStat.isSymbolicLink()) {
265
- die(`MCP config is a symlink \u2014 refusing to load: ${resolved}`);
266
- }
267
- if (configStat.size > 1024 * 1024) {
268
- die(`MCP config exceeds 1MB \u2014 suspicious: ${resolved}`);
269
- }
270
- try {
271
- JSON.parse((0, import_fs.readFileSync)(resolved, "utf-8"));
272
- } catch {
273
- die(`MCP config is not valid JSON: ${resolved}`);
274
- }
275
- return resolved;
276
- }
277
- const projectLocal = (0, import_path.join)(projectRoot, ".code_puppy", "mcp.json");
278
- if ((0, import_fs.existsSync)(projectLocal) && !(0, import_fs.lstatSync)(projectLocal).isSymbolicLink()) {
279
- return projectLocal;
280
- }
281
- const userLevel = (0, import_path.join)((0, import_os.homedir)(), ".code_puppy", "mcp.json");
282
- if ((0, import_fs.existsSync)(userLevel) && !(0, import_fs.lstatSync)(userLevel).isSymbolicLink()) {
283
- return userLevel;
284
- }
285
- return null;
286
- }
287
274
  function ensureMcpConfig(projectRoot) {
288
- const configPath = (0, import_path.join)(projectRoot, ".code_puppy", "mcp.json");
289
- if ((0, import_fs.existsSync)(configPath)) {
290
- if ((0, import_fs.lstatSync)(configPath).isSymbolicLink()) {
291
- die("`.code_puppy/mcp.json` is a symlink \u2014 refusing to write. Check for tampering.");
292
- }
293
- return configPath;
275
+ const xdgConfigHome = process.env["XDG_CONFIG_HOME"];
276
+ const configDir = xdgConfigHome ? (0, import_path.join)(xdgConfigHome, "code_puppy") : (0, import_path.join)((0, import_os.homedir)(), ".code_puppy");
277
+ const configPath = (0, import_path.join)(configDir, "mcp_servers.json");
278
+ if ((0, import_fs.existsSync)(configPath) && (0, import_fs.lstatSync)(configPath).isSymbolicLink()) {
279
+ die("`~/.code_puppy/mcp_servers.json` is a symlink \u2014 refusing to write. Check for tampering.");
294
280
  }
295
- (0, import_fs.mkdirSync)((0, import_path.join)(projectRoot, ".code_puppy"), { recursive: true });
281
+ (0, import_fs.mkdirSync)(configDir, { recursive: true });
296
282
  const localServerPath = (0, import_path.join)(
297
283
  projectRoot,
298
284
  "node_modules",
@@ -301,15 +287,31 @@ function ensureMcpConfig(projectRoot) {
301
287
  "dist",
302
288
  "server.js"
303
289
  );
304
- 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"] };
305
- const config = {
306
- mcpServers: {
290
+ let raftOwnServerPath = null;
291
+ try {
292
+ raftOwnServerPath = require.resolve("@stackwright-pro/mcp");
293
+ } catch {
294
+ }
295
+ const serverConfig = (0, import_fs.existsSync)(localServerPath) ? { type: "stdio", command: "node", args: [localServerPath] } : raftOwnServerPath !== null ? { type: "stdio", command: "node", args: [raftOwnServerPath] } : { type: "stdio", command: "npx", args: ["--yes", "@stackwright-pro/mcp@latest"] };
296
+ let existing = {};
297
+ if ((0, import_fs.existsSync)(configPath)) {
298
+ try {
299
+ const raw = (0, import_fs.readFileSync)(configPath, "utf-8");
300
+ existing = JSON.parse(raw);
301
+ } catch {
302
+ }
303
+ }
304
+ const merged = {
305
+ ...existing,
306
+ mcp_servers: {
307
+ ...existing.mcp_servers ?? {},
307
308
  "stackwright-pro": serverConfig
308
309
  }
309
310
  };
310
- (0, import_fs.writeFileSync)(configPath, JSON.stringify(config, null, 2), "utf-8");
311
- log("MCP config written \u2192 .code_puppy/mcp.json");
312
- return configPath;
311
+ const tmpPath = `${configPath}.tmp`;
312
+ (0, import_fs.writeFileSync)(tmpPath, JSON.stringify(merged, null, 2), "utf-8");
313
+ (0, import_fs.renameSync)(tmpPath, configPath);
314
+ log("MCP server registered \u2192 ~/.code_puppy/mcp_servers.json");
313
315
  }
314
316
  function printResumeStatus(projectRoot) {
315
317
  const pipelineStatePath = (0, import_path.join)(projectRoot, ".stackwright", "pipeline-state.json");
@@ -376,6 +378,7 @@ function main() {
376
378
  log("Launching Pro Otter Raft...");
377
379
  writeInitContext(projectRoot);
378
380
  verbose("Init context written", args.verbose);
381
+ ensureMcpConfig(projectRoot);
379
382
  const otterDir = resolveOtterDir(projectRoot);
380
383
  if (!otterDir) {
381
384
  die(
@@ -384,33 +387,26 @@ function main() {
384
387
  }
385
388
  const result = (0, import_integrity.verifyAllOtters)(otterDir);
386
389
  if (result.failed.length > 0) {
387
- console.error("\u274C Otter integrity check failed:");
390
+ console.warn("\u26A0\uFE0F Otter integrity check warnings (non-blocking):");
388
391
  for (const f of result.failed) {
389
- console.error(` ${f.filename}: ${f.error}`);
392
+ console.warn(` ${f.filename}: ${f.error}`);
390
393
  }
391
- die("Fix the above integrity failures before launching the raft.");
394
+ console.warn(" Note: SHA-256 pinning will be replaced by PKI-signed deployment manifests.");
395
+ console.warn(
396
+ " See: https://github.com/Per-Aspera-LLC/stackwright-pro/issues (signing model issue)"
397
+ );
398
+ } else {
399
+ log(`\u2705 All ${result.verified.length} otters verified`);
392
400
  }
393
- log(`\u2705 All ${result.verified.length} otters verified`);
394
401
  printResumeStatus(projectRoot);
395
402
  const executable = findCodePuppy();
396
403
  verbose(`Resolved code-puppy: ${executable}`, args.verbose);
397
- const autoMcpConfig = ensureMcpConfig(projectRoot);
398
- verbose(`MCP config ensured: ${autoMcpConfig}`, args.verbose);
399
- const mcpConfig = findMcpConfig(projectRoot, args.mcpConfig ?? void 0);
400
- if (mcpConfig) {
401
- verbose(`Using MCP config: ${mcpConfig}`, args.verbose);
402
- }
403
404
  log("Spawning code-puppy raft session...");
404
- const spawnArgs = [
405
- "Begin",
406
- "--interactive",
407
- "--agent",
408
- "stackwright-pro-foreman-otter",
409
- ...mcpConfig ? ["--mcp-config", mcpConfig] : []
410
- ];
405
+ const spawnArgs = ["Begin", "--interactive", "--agent", "stackwright-pro-foreman-otter"];
411
406
  verbose(`cmd: ${executable} ${spawnArgs.join(" ")}`, args.verbose);
412
407
  const child = (0, import_child_process2.spawn)(executable, spawnArgs, {
413
408
  stdio: "inherit",
409
+ cwd: projectRoot,
414
410
  env: {
415
411
  ...process.env,
416
412
  STACKWRIGHT_PROJECT_ROOT: projectRoot
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 findMcpConfig,\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. Ensure MCP config exists (writes .code_puppy/mcp.json if absent)\n const autoMcpConfig = ensureMcpConfig(projectRoot);\n verbose(`MCP config ensured: ${autoMcpConfig}`, args.verbose);\n\n // 5. Find MCP config\n const mcpConfig = findMcpConfig(projectRoot, args.mcpConfig ?? undefined);\n if (mcpConfig) {\n verbose(`Using MCP config: ${mcpConfig}`, args.verbose);\n }\n\n // 6. Spawn code-puppy\n log('Spawning code-puppy raft session...');\n\n const spawnArgs = [\n 'Begin',\n '--interactive',\n '--agent',\n 'stackwright-pro-foreman-otter',\n ...(mcpConfig ? ['--mcp-config', mcpConfig] : []),\n ];\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 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 mcpConfig: string | null;\n verbose: boolean;\n help: boolean;\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const args: ParsedArgs = {\n projectRoot: process.cwd(),\n mcpConfig: null,\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 '--mcp-config':\n args.mcpConfig = raw[++i] ?? die('--mcp-config 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 --mcp-config <path> Path to MCP configuration file\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// ─── Find MCP Config ────────────────────────────────────────────────────────\n\nexport function findMcpConfig(projectRoot: string, explicit?: string): string | null {\n // 1. Explicit path — validate it\n if (explicit) {\n const resolved = resolve(explicit);\n if (!existsSync(resolved)) {\n die(`MCP config not found: ${resolved}`);\n }\n const configStat = lstatSync(resolved);\n if (configStat.isSymbolicLink()) {\n die(`MCP config is a symlink — refusing to load: ${resolved}`);\n }\n if (configStat.size > 1024 * 1024) {\n die(`MCP config exceeds 1MB — suspicious: ${resolved}`);\n }\n try {\n JSON.parse(readFileSync(resolved, 'utf-8'));\n } catch {\n die(`MCP config is not valid JSON: ${resolved}`);\n }\n return resolved;\n }\n\n // 2. Project-local config\n const projectLocal = join(projectRoot, '.code_puppy', 'mcp.json');\n if (existsSync(projectLocal) && !lstatSync(projectLocal).isSymbolicLink()) {\n return projectLocal;\n }\n\n // 3. User-level config\n const userLevel = join(homedir(), '.code_puppy', 'mcp.json');\n if (existsSync(userLevel) && !lstatSync(userLevel).isSymbolicLink()) {\n return userLevel;\n }\n\n // 4. Let code-puppy discover on its own\n return null;\n}\n\n// ─── Ensure MCP Config ─────────────────────────────────────────────────────\n\nexport function ensureMcpConfig(projectRoot: string): string {\n const configPath = join(projectRoot, '.code_puppy', 'mcp.json');\n\n if (existsSync(configPath)) {\n if (lstatSync(configPath).isSymbolicLink()) {\n die('`.code_puppy/mcp.json` is a symlink — refusing to write. Check for tampering.');\n }\n return configPath;\n }\n\n mkdirSync(join(projectRoot, '.code_puppy'), { 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 const config = {\n mcpServers: {\n 'stackwright-pro': serverConfig,\n },\n };\n\n writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');\n log('MCP config written → .code_puppy/mcp.json');\n\n return configPath;\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 } 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 // 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 // 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 code-puppy\n const executable = findCodePuppy();\n verbose(`Resolved code-puppy: ${executable}`, args.verbose);\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 cwd: projectRoot,\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 // Resolve from raft's own node_modules — raft has @stackwright-pro/mcp as a\n // direct dep, so this is always available when running via `npx @stackwright-pro/raft`.\n // This is the reliable middle fallback: works even if the scaffolded project\n // never had mcp added to its own deps (e.g. old published launch-stackwright-pro).\n //\n // Note: require.resolve('@stackwright-pro/mcp') resolves via the `main` field\n // → dist/server.js. We cannot use the subpath './dist/server.js' directly\n // because it's not listed in the package's `exports` map.\n let raftOwnServerPath: string | null = null;\n try {\n raftOwnServerPath = require.resolve('@stackwright-pro/mcp');\n } catch {\n // mcp not resolvable from raft's own node_modules — unusual, fall through to npx\n }\n\n const serverConfig = existsSync(localServerPath)\n ? { type: 'stdio', command: 'node', args: [localServerPath] }\n : raftOwnServerPath !== null\n ? { type: 'stdio', command: 'node', args: [raftOwnServerPath] }\n : { type: 'stdio', command: 'npx', args: ['--yes', '@stackwright-pro/mcp@latest'] };\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackwright-pro/raft",
3
- "version": "0.1.0-alpha.4",
3
+ "version": "1.0.0-alpha.10",
4
4
  "description": "Launch the Pro Otter Raft — verifies integrity, writes init context, spawns code-puppy in foreman mode",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -17,10 +17,10 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@stackwright-pro/mcp": "0.2.0-alpha.6"
20
+ "@stackwright-pro/mcp": "0.2.0-alpha.10"
21
21
  },
22
22
  "peerDependencies": {
23
- "@stackwright-pro/otters": ">=1.0.0-alpha.17"
23
+ "@stackwright-pro/otters": ">=1.0.0-alpha.19"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/node": "^24.0.0",