/** * mission_scaffold tool * * First-time setup tool - copies bundled worker/auditor agents and skills * into project .pi dirs and creates mission-control directory structure. * * This is a command-only tool (not exposed to agents). */ import * as path from "path"; import * as fs from "fs"; import { fileURLToPath } from "url"; import { getProjectRoot, getMissionControlDir, getMemoryDir, getRunsDir, ensureDir, copyDir, fileExists, writeJson, defaultState, isScaffolded } from "../state.js"; export interface ScaffoldResult { success: boolean; message: string; created: string[]; skipped: string[]; errors: string[]; } const packageRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", ".."); /** * Get the bundled agents directory path (within the npm package) */ function getBundledAgentsDir(): string { return path.join(packageRoot, "agents"); } /** * Get the bundled skills directory path (within the npm package) */ function getBundledSkillsDir(): string { return path.join(packageRoot, "skills"); } /** * Get the bundled templates directory path (within the npm package) */ function getBundledTemplatesDir(): string { return path.join(packageRoot, "templates"); } /** * Get target paths in the project */ function getTargetPaths() { const projectRoot = getProjectRoot(); return { agents: path.join(projectRoot, ".pi", "agents"), skills: path.join(projectRoot, ".pi", "skills"), missionControl: getMissionControlDir(), memory: getMemoryDir(), runs: getRunsDir() }; } /** * Scaffold the mission-control environment * * Copies bundled worker/auditor agents and skills to project .pi dirs and creates * the mission-control directory structure. Non-destructive - * skips existing files rather than overwriting. */ export function missionScaffold(): ScaffoldResult { const result: ScaffoldResult = { success: true, message: "", created: [], skipped: [], errors: [] }; try { const bundledDirs = { agents: getBundledAgentsDir(), skills: getBundledSkillsDir(), templates: getBundledTemplatesDir() }; const targetPaths = getTargetPaths(); // Verify bundled directories exist for (const [name, dirPath] of Object.entries(bundledDirs)) { if (!fileExists(dirPath)) { result.errors.push(`Bundled ${name} directory not found: ${dirPath}`); } } if (result.errors.length > 0) { result.success = false; result.message = "Scaffolding failed: bundled resources not found"; return result; } // Create base directory structure ensureDir(targetPaths.missionControl); ensureDir(targetPaths.memory); ensureDir(targetPaths.runs); result.created.push(".pi/mission-control/"); result.created.push(".pi/mission-control/memory/"); result.created.push(".pi/mission-control/runs/"); // Create state.json if it doesn't exist const statePath = path.join(targetPaths.missionControl, "state.json"); if (!fileExists(statePath)) { writeJson(statePath, defaultState); result.created.push(".pi/mission-control/state.json"); } else { result.skipped.push(".pi/mission-control/state.json (already exists)"); } // Copy worker/auditor agents (skip existing files) const agentsCopied = copyDir(bundledDirs.agents, targetPaths.agents, true); if (agentsCopied > 0) { result.created.push(`.pi/agents/ (${agentsCopied} files copied)`); } else { result.skipped.push(".pi/agents/ (all files already exist)"); } // Copy skills (skip existing files) const skillsCopied = copyDir(bundledDirs.skills, targetPaths.skills, true); if (skillsCopied > 0) { result.created.push(`.pi/skills/ (${skillsCopied} files copied)`); } else { result.skipped.push(".pi/skills/ (all files already exist)"); } // Create long_term.md if it doesn't exist const longTermPath = path.join(targetPaths.memory, "long_term.md"); if (!fileExists(longTermPath)) { fs.writeFileSync( longTermPath, "# Long-term Memory\n\nDistilled knowledge from past missions.\n\n## Repo Conventions\n\n## Tooling Quirks\n\n## Architecture Decisions\n\n## Common Failures\n\n## Performance Notes\n", "utf-8" ); result.created.push(".pi/mission-control/memory/long_term.md"); } else { result.skipped.push(".pi/mission-control/memory/long_term.md (already exists)"); } result.success = true; result.message = "Mission Control scaffolding complete"; } catch (error) { result.success = false; result.message = `Scaffolding error: ${error instanceof Error ? error.message : String(error)}`; result.errors.push(String(error)); } return result; }