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

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
@@ -562,6 +562,19 @@ function syncAgents(projectRoot, isVerbose = false) {
562
562
  );
563
563
  }
564
564
  }
565
+ function buildSpawnConfig(platform, executable, binaryFd) {
566
+ const PINNED_CHILD_FD = 3;
567
+ if (platform === "linux") {
568
+ return {
569
+ spawnTarget: `/proc/self/fd/${PINNED_CHILD_FD}`,
570
+ stdioConfig: ["inherit", "inherit", "inherit", binaryFd]
571
+ };
572
+ }
573
+ return {
574
+ spawnTarget: executable,
575
+ stdioConfig: "inherit"
576
+ };
577
+ }
565
578
 
566
579
  // src/index.ts
567
580
  function main() {
@@ -620,7 +633,7 @@ function main() {
620
633
  validateBinaryVersion(executable);
621
634
  log("Spawning raft-puppy / code-puppy raft session...");
622
635
  const spawnArgs = ["Begin", "--interactive", "--agent", "stackwright-pro-foreman-otter"];
623
- const spawnTarget = process.platform === "linux" ? `/proc/self/fd/${binaryFd}` : executable;
636
+ const { spawnTarget, stdioConfig } = buildSpawnConfig(process.platform, executable, binaryFd);
624
637
  if (process.platform !== "linux") {
625
638
  verbose(
626
639
  `macOS/other: spawning via path (residual TOCTOU \u2014 /proc unavailable). See GH#128 for threat model documentation.`,
@@ -632,7 +645,7 @@ function main() {
632
645
  args.verbose
633
646
  );
634
647
  const child = (0, import_child_process2.spawn)(spawnTarget, spawnArgs, {
635
- stdio: "inherit",
648
+ stdio: stdioConfig,
636
649
  cwd: projectRoot,
637
650
  env: buildCodePuppyEnv({ STACKWRIGHT_PROJECT_ROOT: projectRoot }, args.passEnvKeys)
638
651
  });
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, 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"]}
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 buildSpawnConfig,\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 //\n // Shebang-aware: raft-puppy is a Python script, not an ELF binary. The kernel's\n // shebang handler re-execs the interpreter (python) with the fd path as a string\n // argument. O_CLOEXEC (set by Node's openSync) would close the fd during that exec,\n // causing Python to get ENOENT on /proc/self/fd/N.\n //\n // Fix: pass the fd through the stdio array at index 3. dup2() clears O_CLOEXEC,\n // so fd 3 survives the interpreter exec. We then spawn via /proc/self/fd/3.\n //\n // On macOS: /proc does not exist — we fall back to the validated path.\n // Residual TOCTOU on macOS is documented and accepted for the current threat model.\n // See: CWE-367, GH#128, beads issue stackwright-pro-s1g.\n const { spawnTarget, stdioConfig } = buildSpawnConfig(process.platform, executable, binaryFd);\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: stdioConfig,\n cwd: projectRoot,\n env: buildCodePuppyEnv({ STACKWRIGHT_PROJECT_ROOT: projectRoot }, args.passEnvKeys),\n });\n\n // Close the parent's copy of the fd. On Linux the child inherited it via dup2\n // to fd 3 (without O_CLOEXEC) so the child's copy survives exec independently.\n try {\n closeSync(binaryFd);\n } catch {\n // 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\n// ─── Spawn Config Builder ───────────────────────────────────────────────────\n\n/**\n * Build the spawn target and stdio config for code-puppy/raft-puppy.\n *\n * On Linux: pass the validated binary fd through stdio at index 3 (dup2 clears\n * O_CLOEXEC), then exec via /proc/self/fd/3. This works for both ELF and\n * shebang (Python) scripts — the fd survives the interpreter's execve.\n *\n * On macOS/other: /proc does not exist — fall back to the validated path.\n * Residual TOCTOU on macOS is documented and accepted.\n *\n * Extracted from main() for testability.\n *\n * @param platform - process.platform value\n * @param executable - resolved binary path (from findCodePuppy)\n * @param binaryFd - open fd to the binary (from findCodePuppy)\n */\nexport function buildSpawnConfig(\n platform: string,\n executable: string,\n binaryFd: number\n): { spawnTarget: string; stdioConfig: import('child_process').StdioOptions } {\n const PINNED_CHILD_FD = 3;\n\n if (platform === 'linux') {\n return {\n spawnTarget: `/proc/self/fd/${PINNED_CHILD_FD}`,\n stdioConfig: ['inherit', 'inherit', 'inherit', binaryFd],\n };\n }\n\n return {\n spawnTarget: executable,\n stdioConfig: 'inherit',\n };\n}\n"],"mappings":"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","names":["import_fs","import_path","import_child_process"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackwright-pro/raft",
3
- "version": "1.0.0-alpha.60",
3
+ "version": "1.0.0-alpha.61",
4
4
  "description": "Launch the Pro Otter Raft — verifies integrity, writes init context, spawns code-puppy in foreman mode",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
@@ -22,10 +22,10 @@
22
22
  "tag": "alpha"
23
23
  },
24
24
  "dependencies": {
25
- "@stackwright-pro/mcp": "0.2.0-alpha.48"
25
+ "@stackwright-pro/mcp": "0.2.0-alpha.49"
26
26
  },
27
27
  "peerDependencies": {
28
- "@stackwright-pro/otters": ">=1.0.0-alpha.37"
28
+ "@stackwright-pro/otters": ">=1.0.0-alpha.38"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^25.9.1",