pi-mission-control 0.0.0-dev

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.
Files changed (110) hide show
  1. package/README.md +205 -0
  2. package/agents/auditor.md +45 -0
  3. package/agents/worker.md +44 -0
  4. package/dist/index.d.ts +9 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +526 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/state.d.ts +265 -0
  9. package/dist/state.d.ts.map +1 -0
  10. package/dist/state.js +474 -0
  11. package/dist/state.js.map +1 -0
  12. package/dist/tools/add-phase.d.ts +28 -0
  13. package/dist/tools/add-phase.d.ts.map +1 -0
  14. package/dist/tools/add-phase.js +69 -0
  15. package/dist/tools/add-phase.js.map +1 -0
  16. package/dist/tools/add-task.d.ts +30 -0
  17. package/dist/tools/add-task.d.ts.map +1 -0
  18. package/dist/tools/add-task.js +85 -0
  19. package/dist/tools/add-task.js.map +1 -0
  20. package/dist/tools/index.d.ts +13 -0
  21. package/dist/tools/index.d.ts.map +1 -0
  22. package/dist/tools/index.js +16 -0
  23. package/dist/tools/index.js.map +1 -0
  24. package/dist/tools/init.d.ts +34 -0
  25. package/dist/tools/init.d.ts.map +1 -0
  26. package/dist/tools/init.js +75 -0
  27. package/dist/tools/init.js.map +1 -0
  28. package/dist/tools/mission-complete.d.ts +30 -0
  29. package/dist/tools/mission-complete.d.ts.map +1 -0
  30. package/dist/tools/mission-complete.js +85 -0
  31. package/dist/tools/mission-complete.js.map +1 -0
  32. package/dist/tools/mission-resume.d.ts +35 -0
  33. package/dist/tools/mission-resume.d.ts.map +1 -0
  34. package/dist/tools/mission-resume.js +87 -0
  35. package/dist/tools/mission-resume.js.map +1 -0
  36. package/dist/tools/scaffold.d.ts +24 -0
  37. package/dist/tools/scaffold.d.ts.map +1 -0
  38. package/dist/tools/scaffold.js +129 -0
  39. package/dist/tools/scaffold.js.map +1 -0
  40. package/dist/tools/update-phase.d.ts +33 -0
  41. package/dist/tools/update-phase.d.ts.map +1 -0
  42. package/dist/tools/update-phase.js +101 -0
  43. package/dist/tools/update-phase.js.map +1 -0
  44. package/dist/tools/update-task.d.ts +34 -0
  45. package/dist/tools/update-task.d.ts.map +1 -0
  46. package/dist/tools/update-task.js +104 -0
  47. package/dist/tools/update-task.js.map +1 -0
  48. package/dist/tui/dashboard.d.ts +146 -0
  49. package/dist/tui/dashboard.d.ts.map +1 -0
  50. package/dist/tui/dashboard.js +381 -0
  51. package/dist/tui/dashboard.js.map +1 -0
  52. package/dist/tui/header.d.ts +39 -0
  53. package/dist/tui/header.d.ts.map +1 -0
  54. package/dist/tui/header.js +62 -0
  55. package/dist/tui/header.js.map +1 -0
  56. package/dist/tui/idle-view.d.ts +44 -0
  57. package/dist/tui/idle-view.d.ts.map +1 -0
  58. package/dist/tui/idle-view.js +87 -0
  59. package/dist/tui/idle-view.js.map +1 -0
  60. package/dist/tui/index.d.ts +13 -0
  61. package/dist/tui/index.d.ts.map +1 -0
  62. package/dist/tui/index.js +15 -0
  63. package/dist/tui/index.js.map +1 -0
  64. package/dist/tui/past-runs.d.ts +49 -0
  65. package/dist/tui/past-runs.d.ts.map +1 -0
  66. package/dist/tui/past-runs.js +207 -0
  67. package/dist/tui/past-runs.js.map +1 -0
  68. package/dist/tui/phases-panel.d.ts +46 -0
  69. package/dist/tui/phases-panel.d.ts.map +1 -0
  70. package/dist/tui/phases-panel.js +161 -0
  71. package/dist/tui/phases-panel.js.map +1 -0
  72. package/dist/tui/progress-bar.d.ts +37 -0
  73. package/dist/tui/progress-bar.d.ts.map +1 -0
  74. package/dist/tui/progress-bar.js +123 -0
  75. package/dist/tui/progress-bar.js.map +1 -0
  76. package/dist/tui/styles.d.ts +8 -0
  77. package/dist/tui/styles.d.ts.map +1 -0
  78. package/dist/tui/styles.js +22 -0
  79. package/dist/tui/styles.js.map +1 -0
  80. package/dist/tui/tasks-panel.d.ts +48 -0
  81. package/dist/tui/tasks-panel.d.ts.map +1 -0
  82. package/dist/tui/tasks-panel.js +191 -0
  83. package/dist/tui/tasks-panel.js.map +1 -0
  84. package/package.json +42 -0
  85. package/skills/mission-memory/SKILL.md +88 -0
  86. package/skills/mission-orchestrator/SKILL.md +167 -0
  87. package/skills/mission-pm/SKILL.md +83 -0
  88. package/skills/mission-research/SKILL.md +66 -0
  89. package/skills/mission-tech-lead/SKILL.md +68 -0
  90. package/src/index.ts +659 -0
  91. package/src/state.ts +623 -0
  92. package/src/tools/add-phase.ts +98 -0
  93. package/src/tools/add-task.ts +121 -0
  94. package/src/tools/index.ts +18 -0
  95. package/src/tools/init.ts +109 -0
  96. package/src/tools/mission-complete.ts +118 -0
  97. package/src/tools/mission-resume.ts +119 -0
  98. package/src/tools/scaffold.ts +167 -0
  99. package/src/tools/update-phase.ts +140 -0
  100. package/src/tools/update-task.ts +145 -0
  101. package/src/tui/dashboard.ts +441 -0
  102. package/src/tui/header.ts +85 -0
  103. package/src/tui/idle-view.ts +114 -0
  104. package/src/tui/index.ts +20 -0
  105. package/src/tui/past-runs.ts +261 -0
  106. package/src/tui/phases-panel.ts +199 -0
  107. package/src/tui/progress-bar.ts +152 -0
  108. package/src/tui/styles.ts +27 -0
  109. package/src/tui/tasks-panel.ts +228 -0
  110. package/templates/state.json +5 -0
@@ -0,0 +1,121 @@
1
+ /**
2
+ * add_task tool
3
+ *
4
+ * Agent tool to add a new task to a phase.
5
+ * Auto-generates task_id from phase prefix ("phase1-task1", "phase1-task2", ...).
6
+ * Sets status to "pending".
7
+ *
8
+ * Used by: mission-pm skill
9
+ */
10
+
11
+ import {
12
+ readState,
13
+ readRun,
14
+ writeRun,
15
+ findPhase,
16
+ generateTaskId,
17
+ createTask,
18
+ addTaskToRun,
19
+ ensureDir,
20
+ getTaskDir
21
+ } from "../state.js";
22
+
23
+ export interface AddTaskParams {
24
+ phase_id: string;
25
+ name: string;
26
+ file: string;
27
+ }
28
+
29
+ export interface AddTaskResult {
30
+ success: boolean;
31
+ message: string;
32
+ taskId: string | null;
33
+ errors: string[];
34
+ }
35
+
36
+ /**
37
+ * Add a task to a phase in the active mission run
38
+ *
39
+ * @param params.phase_id - ID of the phase to add task to (e.g., "phase1")
40
+ * @param params.name - Human-readable task name (e.g., "Setup DB Schema")
41
+ * @param params.file - Path to task contract file
42
+ * @returns Result with generated task_id
43
+ */
44
+ export function addTask(params: AddTaskParams): AddTaskResult {
45
+ const result: AddTaskResult = {
46
+ success: false,
47
+ message: "",
48
+ taskId: null,
49
+ errors: []
50
+ };
51
+
52
+ try {
53
+ // Validate parameters
54
+ if (!params.phase_id || params.phase_id.trim() === "") {
55
+ result.errors.push("Phase ID is required");
56
+ result.message = "Failed to add task: phase_id is required";
57
+ return result;
58
+ }
59
+
60
+ if (!params.name || params.name.trim() === "") {
61
+ result.errors.push("Task name is required");
62
+ result.message = "Failed to add task: name is required";
63
+ return result;
64
+ }
65
+
66
+ if (!params.file || params.file.trim() === "") {
67
+ result.errors.push("Task file is required");
68
+ result.message = "Failed to add task: file is required";
69
+ return result;
70
+ }
71
+
72
+ // Get active run
73
+ const state = readState();
74
+ if (!state.active_run_id) {
75
+ result.errors.push("No active run");
76
+ result.message = "Failed to add task: no active mission run";
77
+ return result;
78
+ }
79
+
80
+ // Read run.json
81
+ const run = readRun(state.active_run_id);
82
+ if (!run) {
83
+ result.errors.push(`Run not found: ${state.active_run_id}`);
84
+ result.message = "Failed to add task: run not found";
85
+ return result;
86
+ }
87
+
88
+ // Find phase
89
+ const phase = findPhase(run, params.phase_id.trim());
90
+ if (!phase) {
91
+ result.errors.push(`Phase not found: ${params.phase_id}`);
92
+ result.message = "Failed to add task: phase not found";
93
+ return result;
94
+ }
95
+
96
+ // Generate task ID
97
+ const taskId = generateTaskId(phase);
98
+
99
+ // Create task
100
+ const task = createTask(taskId, params.name.trim(), params.file.trim());
101
+
102
+ // Add to run
103
+ const updatedRun = addTaskToRun(run, params.phase_id.trim(), task);
104
+ writeRun(updatedRun);
105
+
106
+ // Create task directory
107
+ const taskDir = getTaskDir(state.active_run_id, taskId);
108
+ ensureDir(taskDir);
109
+
110
+ result.success = true;
111
+ result.taskId = taskId;
112
+ result.message = `Task added: ${taskId} - ${params.name}`;
113
+
114
+ } catch (error) {
115
+ result.success = false;
116
+ result.message = `Error adding task: ${error instanceof Error ? error.message : String(error)}`;
117
+ result.errors.push(String(error));
118
+ }
119
+
120
+ return result;
121
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Tools index - exports all mission-control tools
3
+ */
4
+
5
+ // Re-export isScaffolded from state
6
+ export { isScaffolded } from "../state.js";
7
+
8
+ // Command-only tools (called by /mission, not exposed to agents)
9
+ export { missionScaffold, type ScaffoldResult } from "./scaffold.js";
10
+ export { missionInit, type InitOptions, type InitResult } from "./init.js";
11
+
12
+ // Agent tools (registered for LLM use)
13
+ export { addPhase, type AddPhaseParams, type AddPhaseResult } from "./add-phase.js";
14
+ export { addTask, type AddTaskParams, type AddTaskResult } from "./add-task.js";
15
+ export { updatePhase, type UpdatePhaseParams, type UpdatePhaseResult } from "./update-phase.js";
16
+ export { updateTask, type UpdateTaskParams, type UpdateTaskResult } from "./update-task.js";
17
+ export { missionComplete, type MissionCompleteParams, type MissionCompleteResult } from "./mission-complete.js";
18
+ export { missionResume, type MissionResumeParams, type MissionResumeResult } from "./mission-resume.js";
@@ -0,0 +1,109 @@
1
+ /**
2
+ * mission_init tool
3
+ *
4
+ * Creates a new mission run directory with run.json and updates state.json.
5
+ * Called when user clicks "Init new mission" button.
6
+ *
7
+ * This is a command-only tool (not exposed to agents).
8
+ */
9
+
10
+ import {
11
+ generateRunId,
12
+ createInitialRun,
13
+ createRunStructure,
14
+ writeRun,
15
+ updateState,
16
+ readState,
17
+ writeText,
18
+ getRunDir,
19
+ isScaffolded
20
+ } from "../state.js";
21
+
22
+ export interface InitOptions {
23
+ runId?: string;
24
+ phase?: string;
25
+ statusMessage?: string;
26
+ }
27
+
28
+ export interface InitResult {
29
+ success: boolean;
30
+ message: string;
31
+ runId: string | null;
32
+ runDir: string | null;
33
+ errors: string[];
34
+ }
35
+
36
+ /**
37
+ * Initialize a new mission run
38
+ *
39
+ * Creates:
40
+ * - run directory: .pi/mission-control/runs/<run-id>/
41
+ * - run.json with initial structure
42
+ * - empty artifact files (00-requirements.md, etc.)
43
+ * - tasks directory
44
+ *
45
+ * Updates:
46
+ * - state.json with active_run_id
47
+ */
48
+ export function missionInit(options: InitOptions = {}): InitResult {
49
+ const result: InitResult = {
50
+ success: false,
51
+ message: "",
52
+ runId: null,
53
+ runDir: null,
54
+ errors: []
55
+ };
56
+
57
+ try {
58
+ // Check if scaffolding exists
59
+ if (!isScaffolded()) {
60
+ result.errors.push("Mission Control not scaffolded. Run scaffold first.");
61
+ result.message = "Initialization failed: scaffolding required";
62
+ return result;
63
+ }
64
+
65
+ // Check if there's already an active run
66
+ const currentState = readState();
67
+ if (currentState.active_run_id) {
68
+ result.errors.push(`Active run exists: ${currentState.active_run_id}. Complete or resume it first.`);
69
+ result.message = "Initialization failed: another run is active";
70
+ return result;
71
+ }
72
+
73
+ // Generate run ID
74
+ const runId = options.runId || generateRunId();
75
+ result.runId = runId;
76
+
77
+ // Create run structure
78
+ createRunStructure(runId);
79
+ result.runDir = getRunDir(runId);
80
+
81
+ // Create initial run.json
82
+ const run = createInitialRun(runId);
83
+ writeRun(run);
84
+
85
+ // Create empty artifact files
86
+ const runDir = getRunDir(runId);
87
+ writeText(`${runDir}/00-requirements.md`, `# Requirements\n\nRun ID: ${runId}\nCreated: ${run.started_at}\n\n<!-- Requirements will be written here by mission-orchestrator skill -->\n`);
88
+ writeText(`${runDir}/01-architecture.md`, `# Architecture\n\nRun ID: ${runId}\n\n<!-- Architecture will be written here by mission-tech-lead skill -->\n`);
89
+ writeText(`${runDir}/02-validation.md`, `# Validation Contract\n\nRun ID: ${runId}\n\n<!-- Validation criteria will be written here by mission-tech-lead skill -->\n`);
90
+ writeText(`${runDir}/short_term_memory.md`, `# Short-term Memory\n\nRun ID: ${runId}\n\n<!-- Learnings from this run will be accumulated here -->\n`);
91
+
92
+ // Update state.json
93
+ updateState({
94
+ active_run_id: runId,
95
+ current_phase: options.phase || "research",
96
+ current_status_message: options.statusMessage || "Mission initialized - waiting for requirements"
97
+ });
98
+
99
+ result.success = true;
100
+ result.message = `Mission initialized: ${runId}`;
101
+
102
+ } catch (error) {
103
+ result.success = false;
104
+ result.message = `Initialization error: ${error instanceof Error ? error.message : String(error)}`;
105
+ result.errors.push(String(error));
106
+ }
107
+
108
+ return result;
109
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * mission_complete tool
3
+ *
4
+ * Agent tool to mark a mission run as complete.
5
+ * Sets run.json status to "done" and finish_at timestamp.
6
+ * Clears active_run_id from state.json.
7
+ *
8
+ * Used by: mission-orchestrator skill
9
+ */
10
+
11
+ import {
12
+ readState,
13
+ readRun,
14
+ writeRun,
15
+ updateState,
16
+ getTimestamp,
17
+ RunStatus
18
+ } from "../state.js";
19
+
20
+ export interface MissionCompleteParams {
21
+ run_id: string;
22
+ }
23
+
24
+ export interface MissionCompleteResult {
25
+ success: boolean;
26
+ message: string;
27
+ runId: string | null;
28
+ previousStatus: RunStatus | null;
29
+ newStatus: RunStatus;
30
+ finishAt: string;
31
+ errors: string[];
32
+ }
33
+
34
+ /**
35
+ * Mark a mission run as complete
36
+ *
37
+ * @param params.run_id - ID of the run to complete
38
+ * @returns Result with completion info
39
+ */
40
+ export function missionComplete(params: MissionCompleteParams): MissionCompleteResult {
41
+ const result: MissionCompleteResult = {
42
+ success: false,
43
+ message: "",
44
+ runId: null,
45
+ previousStatus: null,
46
+ newStatus: "done",
47
+ finishAt: "",
48
+ errors: []
49
+ };
50
+
51
+ try {
52
+ // Validate parameters
53
+ if (!params.run_id || params.run_id.trim() === "") {
54
+ result.errors.push("Run ID is required");
55
+ result.message = "Failed to complete mission: run_id is required";
56
+ return result;
57
+ }
58
+
59
+ const runId = params.run_id.trim();
60
+ result.runId = runId;
61
+
62
+ // Read the run
63
+ const run = readRun(runId);
64
+ if (!run) {
65
+ result.errors.push(`Run not found: ${runId}`);
66
+ result.message = "Failed to complete mission: run not found";
67
+ return result;
68
+ }
69
+
70
+ result.previousStatus = run.status;
71
+
72
+ // Check if run is already completed
73
+ if (run.status === "done") {
74
+ result.success = true;
75
+ result.message = `Mission ${runId} is already complete`;
76
+ result.finishAt = run.finish_at || getTimestamp();
77
+ return result;
78
+ }
79
+
80
+ // Check if run is in a state that can be completed
81
+ if (run.status === "failed") {
82
+ result.errors.push(`Run ${runId} has failed status and cannot be marked complete`);
83
+ result.message = "Failed to complete mission: run has failed status";
84
+ return result;
85
+ }
86
+
87
+ const now = getTimestamp();
88
+ result.finishAt = now;
89
+
90
+ // Update run.json
91
+ const updatedRun = {
92
+ ...run,
93
+ status: "done" as RunStatus,
94
+ finish_at: now
95
+ };
96
+ writeRun(updatedRun);
97
+
98
+ // Clear active_run_id from state.json if it matches
99
+ const state = readState();
100
+ if (state.active_run_id === runId) {
101
+ updateState({
102
+ active_run_id: null,
103
+ current_phase: "idle",
104
+ current_status_message: "Mission complete"
105
+ });
106
+ }
107
+
108
+ result.success = true;
109
+ result.message = `Mission ${runId} marked as complete`;
110
+
111
+ } catch (error) {
112
+ result.success = false;
113
+ result.message = `Error completing mission: ${error instanceof Error ? error.message : String(error)}`;
114
+ result.errors.push(String(error));
115
+ }
116
+
117
+ return result;
118
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * mission_resume tool
3
+ *
4
+ * Agent tool to resume a previously created mission run.
5
+ * Sets state.json active_run_id to the specified run_id.
6
+ *
7
+ * Used by: mission-orchestrator skill (when user asks to resume)
8
+ */
9
+
10
+ import {
11
+ readState,
12
+ readRun,
13
+ updateState,
14
+ RunStatus
15
+ } from "../state.js";
16
+
17
+ export interface MissionResumeParams {
18
+ run_id: string;
19
+ phase?: string;
20
+ statusMessage?: string;
21
+ }
22
+
23
+ export interface MissionResumeResult {
24
+ success: boolean;
25
+ message: string;
26
+ runId: string | null;
27
+ runStatus: RunStatus | null;
28
+ previousActiveRunId: string | null;
29
+ errors: string[];
30
+ }
31
+
32
+ /**
33
+ * Resume a mission run
34
+ *
35
+ * Sets the specified run as the active run in state.json.
36
+ * Fails if there's already a different active run.
37
+ *
38
+ * @param params.run_id - ID of the run to resume
39
+ * @param params.phase - Optional phase to set as current (defaults to run's current state)
40
+ * @param params.statusMessage - Optional status message
41
+ * @returns Result with resume info
42
+ */
43
+ export function missionResume(params: MissionResumeParams): MissionResumeResult {
44
+ const result: MissionResumeResult = {
45
+ success: false,
46
+ message: "",
47
+ runId: null,
48
+ runStatus: null,
49
+ previousActiveRunId: null,
50
+ errors: []
51
+ };
52
+
53
+ try {
54
+ // Validate parameters
55
+ if (!params.run_id || params.run_id.trim() === "") {
56
+ result.errors.push("Run ID is required");
57
+ result.message = "Failed to resume mission: run_id is required";
58
+ return result;
59
+ }
60
+
61
+ const runId = params.run_id.trim();
62
+ result.runId = runId;
63
+
64
+ // Read current state
65
+ const state = readState();
66
+ result.previousActiveRunId = state.active_run_id;
67
+
68
+ // Check if there's already a different active run
69
+ if (state.active_run_id && state.active_run_id !== runId) {
70
+ result.errors.push(`Another run is active: ${state.active_run_id}. Complete or pause it first.`);
71
+ result.message = "Failed to resume mission: another run is active";
72
+ return result;
73
+ }
74
+
75
+ // Read the run to verify it exists
76
+ const run = readRun(runId);
77
+ if (!run) {
78
+ result.errors.push(`Run not found: ${runId}`);
79
+ result.message = "Failed to resume mission: run not found";
80
+ return result;
81
+ }
82
+
83
+ result.runStatus = run.status;
84
+
85
+ // Check if run is in a resumable state
86
+ if (run.status === "done") {
87
+ result.errors.push(`Run ${runId} is already complete`);
88
+ result.message = "Failed to resume mission: run is already complete";
89
+ return result;
90
+ }
91
+
92
+ // Determine current phase
93
+ let currentPhase = params.phase || "execution";
94
+ if (!params.phase && run.phases.length > 0) {
95
+ // Find the first non-done phase
96
+ const activePhase = run.phases.find(p => p.status !== "done" && p.status !== "removed");
97
+ if (activePhase) {
98
+ currentPhase = activePhase.id;
99
+ }
100
+ }
101
+
102
+ // Update state.json
103
+ updateState({
104
+ active_run_id: runId,
105
+ current_phase: currentPhase,
106
+ current_status_message: params.statusMessage || `Resumed mission: ${runId}`
107
+ });
108
+
109
+ result.success = true;
110
+ result.message = `Mission resumed: ${runId}`;
111
+
112
+ } catch (error) {
113
+ result.success = false;
114
+ result.message = `Error resuming mission: ${error instanceof Error ? error.message : String(error)}`;
115
+ result.errors.push(String(error));
116
+ }
117
+
118
+ return result;
119
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * mission_scaffold tool
3
+ *
4
+ * First-time setup tool - copies bundled worker/auditor agents and skills
5
+ * into project .pi dirs and creates mission-control directory structure.
6
+ *
7
+ * This is a command-only tool (not exposed to agents).
8
+ */
9
+
10
+ import * as path from "path";
11
+ import * as fs from "fs";
12
+ import { fileURLToPath } from "url";
13
+ import {
14
+ getProjectRoot,
15
+ getMissionControlDir,
16
+ getMemoryDir,
17
+ getRunsDir,
18
+ ensureDir,
19
+ copyDir,
20
+ fileExists,
21
+ writeJson,
22
+ defaultState,
23
+ isScaffolded
24
+ } from "../state.js";
25
+
26
+ export interface ScaffoldResult {
27
+ success: boolean;
28
+ message: string;
29
+ created: string[];
30
+ skipped: string[];
31
+ errors: string[];
32
+ }
33
+
34
+ const packageRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
35
+
36
+ /**
37
+ * Get the bundled agents directory path (within the npm package)
38
+ */
39
+ function getBundledAgentsDir(): string {
40
+ return path.join(packageRoot, "agents");
41
+ }
42
+
43
+ /**
44
+ * Get the bundled skills directory path (within the npm package)
45
+ */
46
+ function getBundledSkillsDir(): string {
47
+ return path.join(packageRoot, "skills");
48
+ }
49
+
50
+ /**
51
+ * Get the bundled templates directory path (within the npm package)
52
+ */
53
+ function getBundledTemplatesDir(): string {
54
+ return path.join(packageRoot, "templates");
55
+ }
56
+
57
+ /**
58
+ * Get target paths in the project
59
+ */
60
+ function getTargetPaths() {
61
+ const projectRoot = getProjectRoot();
62
+ return {
63
+ agents: path.join(projectRoot, ".pi", "agents"),
64
+ skills: path.join(projectRoot, ".pi", "skills"),
65
+ missionControl: getMissionControlDir(),
66
+ memory: getMemoryDir(),
67
+ runs: getRunsDir()
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Scaffold the mission-control environment
73
+ *
74
+ * Copies bundled worker/auditor agents and skills to project .pi dirs and creates
75
+ * the mission-control directory structure. Non-destructive -
76
+ * skips existing files rather than overwriting.
77
+ */
78
+ export function missionScaffold(): ScaffoldResult {
79
+ const result: ScaffoldResult = {
80
+ success: true,
81
+ message: "",
82
+ created: [],
83
+ skipped: [],
84
+ errors: []
85
+ };
86
+
87
+ try {
88
+ const bundledDirs = {
89
+ agents: getBundledAgentsDir(),
90
+ skills: getBundledSkillsDir(),
91
+ templates: getBundledTemplatesDir()
92
+ };
93
+
94
+ const targetPaths = getTargetPaths();
95
+
96
+ // Verify bundled directories exist
97
+ for (const [name, dirPath] of Object.entries(bundledDirs)) {
98
+ if (!fileExists(dirPath)) {
99
+ result.errors.push(`Bundled ${name} directory not found: ${dirPath}`);
100
+ }
101
+ }
102
+
103
+ if (result.errors.length > 0) {
104
+ result.success = false;
105
+ result.message = "Scaffolding failed: bundled resources not found";
106
+ return result;
107
+ }
108
+
109
+ // Create base directory structure
110
+ ensureDir(targetPaths.missionControl);
111
+ ensureDir(targetPaths.memory);
112
+ ensureDir(targetPaths.runs);
113
+ result.created.push(".pi/mission-control/");
114
+ result.created.push(".pi/mission-control/memory/");
115
+ result.created.push(".pi/mission-control/runs/");
116
+
117
+ // Create state.json if it doesn't exist
118
+ const statePath = path.join(targetPaths.missionControl, "state.json");
119
+ if (!fileExists(statePath)) {
120
+ writeJson(statePath, defaultState);
121
+ result.created.push(".pi/mission-control/state.json");
122
+ } else {
123
+ result.skipped.push(".pi/mission-control/state.json (already exists)");
124
+ }
125
+
126
+ // Copy worker/auditor agents (skip existing files)
127
+ const agentsCopied = copyDir(bundledDirs.agents, targetPaths.agents, true);
128
+ if (agentsCopied > 0) {
129
+ result.created.push(`.pi/agents/ (${agentsCopied} files copied)`);
130
+ } else {
131
+ result.skipped.push(".pi/agents/ (all files already exist)");
132
+ }
133
+
134
+ // Copy skills (skip existing files)
135
+ const skillsCopied = copyDir(bundledDirs.skills, targetPaths.skills, true);
136
+ if (skillsCopied > 0) {
137
+ result.created.push(`.pi/skills/ (${skillsCopied} files copied)`);
138
+ } else {
139
+ result.skipped.push(".pi/skills/ (all files already exist)");
140
+ }
141
+
142
+ // Create long_term.md if it doesn't exist
143
+ const longTermPath = path.join(targetPaths.memory, "long_term.md");
144
+ if (!fileExists(longTermPath)) {
145
+ fs.writeFileSync(
146
+ longTermPath,
147
+ "# 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",
148
+ "utf-8"
149
+ );
150
+ result.created.push(".pi/mission-control/memory/long_term.md");
151
+ } else {
152
+ result.skipped.push(".pi/mission-control/memory/long_term.md (already exists)");
153
+ }
154
+
155
+ result.success = true;
156
+ result.message = "Mission Control scaffolding complete";
157
+
158
+ } catch (error) {
159
+ result.success = false;
160
+ result.message = `Scaffolding error: ${error instanceof Error ? error.message : String(error)}`;
161
+ result.errors.push(String(error));
162
+ }
163
+
164
+ return result;
165
+ }
166
+
167
+