@isaacriehm/cairn-core 0.9.8 → 0.10.1
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/.tsbuildinfo +1 -1
- package/dist/hooks/runners/context-threshold.d.ts +33 -4
- package/dist/hooks/runners/context-threshold.js +90 -25
- package/dist/hooks/runners/context-threshold.js.map +1 -1
- package/dist/hooks/runners/index.d.ts +1 -0
- package/dist/hooks/runners/index.js +1 -0
- package/dist/hooks/runners/index.js.map +1 -1
- package/dist/hooks/runners/session-start.js +36 -2
- package/dist/hooks/runners/session-start.js.map +1 -1
- package/dist/hooks/runners/stop.js +105 -0
- package/dist/hooks/runners/stop.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/errors.d.ts +1 -1
- package/dist/mcp/errors.js.map +1 -1
- package/dist/mcp/schemas.d.ts +101 -0
- package/dist/mcp/schemas.js +92 -0
- package/dist/mcp/schemas.js.map +1 -1
- package/dist/mcp/tools/index.js +19 -0
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/mission-accept-draft.d.ts +16 -0
- package/dist/mcp/tools/mission-accept-draft.js +102 -0
- package/dist/mcp/tools/mission-accept-draft.js.map +1 -0
- package/dist/mcp/tools/mission-advance.d.ts +8 -0
- package/dist/mcp/tools/mission-advance.js +124 -0
- package/dist/mcp/tools/mission-advance.js.map +1 -0
- package/dist/mcp/tools/mission-close.d.ts +8 -0
- package/dist/mcp/tools/mission-close.js +48 -0
- package/dist/mcp/tools/mission-close.js.map +1 -0
- package/dist/mcp/tools/mission-get.d.ts +6 -0
- package/dist/mcp/tools/mission-get.js +64 -0
- package/dist/mcp/tools/mission-get.js.map +1 -0
- package/dist/mcp/tools/mission-reopen.d.ts +13 -0
- package/dist/mcp/tools/mission-reopen.js +56 -0
- package/dist/mcp/tools/mission-reopen.js.map +1 -0
- package/dist/mcp/tools/mission-resume.d.ts +15 -0
- package/dist/mcp/tools/mission-resume.js +219 -0
- package/dist/mcp/tools/mission-resume.js.map +1 -0
- package/dist/mcp/tools/mission-resync-accept.d.ts +19 -0
- package/dist/mcp/tools/mission-resync-accept.js +192 -0
- package/dist/mcp/tools/mission-resync-accept.js.map +1 -0
- package/dist/mcp/tools/mission-resync.d.ts +20 -0
- package/dist/mcp/tools/mission-resync.js +113 -0
- package/dist/mcp/tools/mission-resync.js.map +1 -0
- package/dist/mcp/tools/mission-start.d.ts +8 -0
- package/dist/mcp/tools/mission-start.js +76 -0
- package/dist/mcp/tools/mission-start.js.map +1 -0
- package/dist/mcp/tools/resume.js +40 -4
- package/dist/mcp/tools/resume.js.map +1 -1
- package/dist/mcp/tools/task-create.d.ts +2 -0
- package/dist/mcp/tools/task-create.js +40 -2
- package/dist/mcp/tools/task-create.js.map +1 -1
- package/dist/missions/cli.d.ts +106 -0
- package/dist/missions/cli.js +279 -0
- package/dist/missions/cli.js.map +1 -0
- package/dist/missions/cursor.d.ts +40 -0
- package/dist/missions/cursor.js +99 -0
- package/dist/missions/cursor.js.map +1 -0
- package/dist/missions/draft.d.ts +28 -0
- package/dist/missions/draft.js +112 -0
- package/dist/missions/draft.js.map +1 -0
- package/dist/missions/index.d.ts +9 -0
- package/dist/missions/index.js +10 -0
- package/dist/missions/index.js.map +1 -0
- package/dist/missions/task-link.d.ts +51 -0
- package/dist/missions/task-link.js +155 -0
- package/dist/missions/task-link.js.map +1 -0
- package/dist/session-start/build.d.ts +1 -1
- package/dist/session-start/build.js +58 -1
- package/dist/session-start/build.js.map +1 -1
- package/dist/status-line/format.d.ts +15 -1
- package/dist/status-line/format.js +12 -1
- package/dist/status-line/format.js.map +1 -1
- package/dist/status-line/index.d.ts +1 -1
- package/dist/status-line/index.js +1 -1
- package/dist/status-line/index.js.map +1 -1
- package/dist/status-line/reader.js +33 -2
- package/dist/status-line/reader.js.map +1 -1
- package/dist/tasks/lifecycle.js +11 -0
- package/dist/tasks/lifecycle.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI wrappers around the mission MCP tools — `cairn mission ...`
|
|
3
|
+
* subcommands. Reuses the helper modules (cursor, draft, task-link)
|
|
4
|
+
* directly so the CLI stays in lock-step with the MCP surface.
|
|
5
|
+
*/
|
|
6
|
+
import { type MissionExitGate, type MissionPhase } from "@isaacriehm/cairn-state";
|
|
7
|
+
export interface MissionStartArgs {
|
|
8
|
+
repoRoot: string;
|
|
9
|
+
specPath: string;
|
|
10
|
+
exitGate: MissionExitGate;
|
|
11
|
+
noLlm?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface MissionStartResult {
|
|
14
|
+
proposed_title: string;
|
|
15
|
+
spec_path: string;
|
|
16
|
+
exit_gate: MissionExitGate;
|
|
17
|
+
phases: MissionPhase[];
|
|
18
|
+
llm_used: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function runMissionStart(args: MissionStartArgs): Promise<MissionStartResult>;
|
|
21
|
+
export interface MissionAcceptArgs {
|
|
22
|
+
repoRoot: string;
|
|
23
|
+
title: string;
|
|
24
|
+
specPath: string;
|
|
25
|
+
exitGate: MissionExitGate;
|
|
26
|
+
phases: MissionPhase[];
|
|
27
|
+
}
|
|
28
|
+
export interface MissionAcceptResult {
|
|
29
|
+
mission_id: string;
|
|
30
|
+
cursor: {
|
|
31
|
+
active_phase: string | null;
|
|
32
|
+
};
|
|
33
|
+
total_phases: number;
|
|
34
|
+
}
|
|
35
|
+
export declare function runMissionAccept(args: MissionAcceptArgs): MissionAcceptResult;
|
|
36
|
+
export interface MissionGetResult {
|
|
37
|
+
active: boolean;
|
|
38
|
+
mission_id?: string;
|
|
39
|
+
title?: string;
|
|
40
|
+
cursor?: {
|
|
41
|
+
active_phase: string | null;
|
|
42
|
+
};
|
|
43
|
+
progress?: {
|
|
44
|
+
done: number;
|
|
45
|
+
total: number;
|
|
46
|
+
};
|
|
47
|
+
drift_phase_ids?: string[];
|
|
48
|
+
}
|
|
49
|
+
export declare function runMissionGet(repoRoot: string): MissionGetResult;
|
|
50
|
+
export interface MissionAdvanceArgs {
|
|
51
|
+
repoRoot: string;
|
|
52
|
+
phaseId: string;
|
|
53
|
+
force?: boolean;
|
|
54
|
+
drop?: boolean;
|
|
55
|
+
}
|
|
56
|
+
export declare function runMissionAdvance(args: MissionAdvanceArgs): {
|
|
57
|
+
kind: "advanced";
|
|
58
|
+
phase_advanced: string;
|
|
59
|
+
next_phase: string | null;
|
|
60
|
+
closed: boolean;
|
|
61
|
+
} | {
|
|
62
|
+
kind: "dropped";
|
|
63
|
+
phase_id: string;
|
|
64
|
+
orphaned_task_ids: string[];
|
|
65
|
+
};
|
|
66
|
+
export declare function runMissionClose(repoRoot: string, missionId: string, outcome: "done" | "aborted", reason?: string): {
|
|
67
|
+
mission_id: string;
|
|
68
|
+
outcome: string;
|
|
69
|
+
closed_at: string;
|
|
70
|
+
};
|
|
71
|
+
export declare function runMissionReopen(repoRoot: string, missionId: string): {
|
|
72
|
+
mission_id: string;
|
|
73
|
+
cursor: {
|
|
74
|
+
active_phase: string | null;
|
|
75
|
+
} | null;
|
|
76
|
+
reopened_at: string;
|
|
77
|
+
};
|
|
78
|
+
export declare function listMissions(repoRoot: string): {
|
|
79
|
+
active: string[];
|
|
80
|
+
done: string[];
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Pretty-print a roadmap to a string for `cairn mission start --print`
|
|
84
|
+
* preview output. Matches the YAML-frontmatter shape Cairn writes to
|
|
85
|
+
* disk so the operator can copy + edit it before approval.
|
|
86
|
+
*/
|
|
87
|
+
export declare function previewRoadmap(args: {
|
|
88
|
+
title: string;
|
|
89
|
+
specPath: string;
|
|
90
|
+
exitGate: MissionExitGate;
|
|
91
|
+
phases: MissionPhase[];
|
|
92
|
+
}): string;
|
|
93
|
+
/** Used by `cairn mission accept --from <file>` to load a hand-edited roadmap draft. */
|
|
94
|
+
export declare function loadDraftFromFile(path: string): {
|
|
95
|
+
title: string;
|
|
96
|
+
spec_path: string;
|
|
97
|
+
exit_gate: MissionExitGate;
|
|
98
|
+
phases: MissionPhase[];
|
|
99
|
+
};
|
|
100
|
+
/** Persist the proposed roadmap draft to a temp JSON file for hand-editing. */
|
|
101
|
+
export declare function writeDraftToFile(path: string, draft: {
|
|
102
|
+
title: string;
|
|
103
|
+
spec_path: string;
|
|
104
|
+
exit_gate: MissionExitGate;
|
|
105
|
+
phases: MissionPhase[];
|
|
106
|
+
}): void;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI wrappers around the mission MCP tools — `cairn mission ...`
|
|
3
|
+
* subcommands. Reuses the helper modules (cursor, draft, task-link)
|
|
4
|
+
* directly so the CLI stays in lock-step with the MCP surface.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { isAbsolute, resolve as pathResolve } from "node:path";
|
|
8
|
+
import { appendMissionJournal, archiveMission, countDonePhases, deriveMissionId, detectRoadmapDrift, findActiveMission, initialPhaseProgress, listActiveMissionIds, listDoneMissionIds, locateMission, nextPendingPhase, readMissionState, readRoadmap, restoreMission, writeMissionSpec, writeMissionState, writeRoadmap, } from "@isaacriehm/cairn-state";
|
|
9
|
+
import { advancePhase } from "./cursor.js";
|
|
10
|
+
import { draftRoadmapFromSpec, stubRoadmap } from "./draft.js";
|
|
11
|
+
export async function runMissionStart(args) {
|
|
12
|
+
if (findActiveMission(args.repoRoot) !== null) {
|
|
13
|
+
throw new Error("An active mission already exists; close or abort it before starting another.");
|
|
14
|
+
}
|
|
15
|
+
const abs = isAbsolute(args.specPath) ? args.specPath : pathResolve(args.repoRoot, args.specPath);
|
|
16
|
+
if (!abs.startsWith(args.repoRoot)) {
|
|
17
|
+
throw new Error(`${args.specPath} resolves outside the repo`);
|
|
18
|
+
}
|
|
19
|
+
if (!existsSync(abs)) {
|
|
20
|
+
throw new Error(`Spec doc not found: ${args.specPath}`);
|
|
21
|
+
}
|
|
22
|
+
const source = readFileSync(abs, "utf8");
|
|
23
|
+
const proposedTitle = deriveTitleFromSpec(source, args.specPath);
|
|
24
|
+
if (args.noLlm === true) {
|
|
25
|
+
return {
|
|
26
|
+
proposed_title: proposedTitle,
|
|
27
|
+
spec_path: args.specPath,
|
|
28
|
+
exit_gate: args.exitGate,
|
|
29
|
+
phases: stubRoadmap(),
|
|
30
|
+
llm_used: false,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const draft = await draftRoadmapFromSpec({
|
|
34
|
+
repoRoot: args.repoRoot,
|
|
35
|
+
spec: source,
|
|
36
|
+
specPath: args.specPath,
|
|
37
|
+
});
|
|
38
|
+
if (draft === null) {
|
|
39
|
+
throw new Error("Haiku failed to parse the spec doc; retry or pass --no-llm");
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
proposed_title: proposedTitle,
|
|
43
|
+
spec_path: args.specPath,
|
|
44
|
+
exit_gate: args.exitGate,
|
|
45
|
+
phases: draft.phases,
|
|
46
|
+
llm_used: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function runMissionAccept(args) {
|
|
50
|
+
if (findActiveMission(args.repoRoot) !== null) {
|
|
51
|
+
throw new Error("An active mission already exists; close or abort it before starting another.");
|
|
52
|
+
}
|
|
53
|
+
const abs = isAbsolute(args.specPath) ? args.specPath : pathResolve(args.repoRoot, args.specPath);
|
|
54
|
+
if (!existsSync(abs)) {
|
|
55
|
+
throw new Error(`Spec doc not found: ${args.specPath}`);
|
|
56
|
+
}
|
|
57
|
+
const startedAt = new Date().toISOString();
|
|
58
|
+
const missionId = deriveMissionId({ title: args.title, spec_path: args.specPath, started_at: startedAt });
|
|
59
|
+
const frontmatter = {
|
|
60
|
+
mission_id: missionId,
|
|
61
|
+
title: args.title,
|
|
62
|
+
spec_path: args.specPath,
|
|
63
|
+
created_at: startedAt,
|
|
64
|
+
exit_gate: args.exitGate,
|
|
65
|
+
phases: args.phases,
|
|
66
|
+
};
|
|
67
|
+
writeRoadmap(args.repoRoot, missionId, frontmatter, `# Mission — ${args.title}\n\nSpec: \`${args.specPath}\`.\n`);
|
|
68
|
+
writeMissionSpec(args.repoRoot, missionId, readFileSync(abs, "utf8"));
|
|
69
|
+
const phaseProgress = initialPhaseProgress(frontmatter);
|
|
70
|
+
const firstPhase = nextPendingPhase(frontmatter, {
|
|
71
|
+
mission_id: missionId,
|
|
72
|
+
started_at: startedAt,
|
|
73
|
+
cursor: { active_phase: null, active_phase_started_at: null },
|
|
74
|
+
phase_progress: phaseProgress,
|
|
75
|
+
outcome: "active",
|
|
76
|
+
});
|
|
77
|
+
if (firstPhase !== null) {
|
|
78
|
+
phaseProgress[firstPhase.id] = { state: "in_progress", task_ids: [] };
|
|
79
|
+
}
|
|
80
|
+
writeMissionState(args.repoRoot, missionId, {
|
|
81
|
+
mission_id: missionId,
|
|
82
|
+
started_at: startedAt,
|
|
83
|
+
cursor: {
|
|
84
|
+
active_phase: firstPhase?.id ?? null,
|
|
85
|
+
active_phase_started_at: firstPhase !== null ? startedAt : null,
|
|
86
|
+
},
|
|
87
|
+
phase_progress: phaseProgress,
|
|
88
|
+
outcome: "active",
|
|
89
|
+
});
|
|
90
|
+
appendMissionJournal(args.repoRoot, missionId, {
|
|
91
|
+
ts: startedAt,
|
|
92
|
+
kind: "started",
|
|
93
|
+
detail: `${args.phases.length} phases, exit_gate=${args.exitGate}`,
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
mission_id: missionId,
|
|
97
|
+
cursor: { active_phase: firstPhase?.id ?? null },
|
|
98
|
+
total_phases: args.phases.length,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function runMissionGet(repoRoot) {
|
|
102
|
+
const id = findActiveMission(repoRoot);
|
|
103
|
+
if (id === null)
|
|
104
|
+
return { active: false };
|
|
105
|
+
const roadmap = readRoadmap(repoRoot, id);
|
|
106
|
+
const state = readMissionState(repoRoot, id);
|
|
107
|
+
if (roadmap === null || state === null)
|
|
108
|
+
return { active: false };
|
|
109
|
+
return {
|
|
110
|
+
active: true,
|
|
111
|
+
mission_id: id,
|
|
112
|
+
title: roadmap.frontmatter.title,
|
|
113
|
+
cursor: { active_phase: state.cursor.active_phase },
|
|
114
|
+
progress: { done: countDonePhases(state), total: roadmap.frontmatter.phases.length },
|
|
115
|
+
drift_phase_ids: detectRoadmapDrift(roadmap.frontmatter, state),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export function runMissionAdvance(args) {
|
|
119
|
+
const missionId = findActiveMission(args.repoRoot);
|
|
120
|
+
if (missionId === null)
|
|
121
|
+
throw new Error("No active mission");
|
|
122
|
+
const state = readMissionState(args.repoRoot, missionId);
|
|
123
|
+
if (state === null)
|
|
124
|
+
throw new Error(`state.json missing for ${missionId}`);
|
|
125
|
+
const roadmap = readRoadmap(args.repoRoot, missionId);
|
|
126
|
+
if (roadmap === null)
|
|
127
|
+
throw new Error(`roadmap.md missing for ${missionId}`);
|
|
128
|
+
if (args.drop === true) {
|
|
129
|
+
if (roadmap.frontmatter.phases.some((p) => p.id === args.phaseId)) {
|
|
130
|
+
throw new Error(`phase ${args.phaseId} is still in roadmap.md; --drop only resolves drifted ids`);
|
|
131
|
+
}
|
|
132
|
+
const progress = state.phase_progress[args.phaseId];
|
|
133
|
+
if (progress === undefined) {
|
|
134
|
+
throw new Error(`phase ${args.phaseId} not present in phase_progress`);
|
|
135
|
+
}
|
|
136
|
+
const taskIds = progress.task_ids;
|
|
137
|
+
delete state.phase_progress[args.phaseId];
|
|
138
|
+
writeMissionState(args.repoRoot, missionId, state);
|
|
139
|
+
appendMissionJournal(args.repoRoot, missionId, {
|
|
140
|
+
ts: new Date().toISOString(),
|
|
141
|
+
kind: "drift-detected",
|
|
142
|
+
phase_id: args.phaseId,
|
|
143
|
+
detail: `dropped — ${taskIds.length} graduated task(s) orphaned`,
|
|
144
|
+
});
|
|
145
|
+
return { kind: "dropped", phase_id: args.phaseId, orphaned_task_ids: taskIds };
|
|
146
|
+
}
|
|
147
|
+
if (args.force !== true) {
|
|
148
|
+
const progress = state.phase_progress[args.phaseId];
|
|
149
|
+
if (progress === undefined || progress.task_ids.length === 0) {
|
|
150
|
+
throw new Error(`phase ${args.phaseId} has no linked tasks; pass --force to advance`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const r = advancePhase(args.repoRoot, missionId, args.phaseId);
|
|
154
|
+
if (!r.ok)
|
|
155
|
+
throw new Error(r.message);
|
|
156
|
+
return {
|
|
157
|
+
kind: "advanced",
|
|
158
|
+
phase_advanced: r.phase_advanced,
|
|
159
|
+
next_phase: r.next_phase?.id ?? null,
|
|
160
|
+
closed: r.closed,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
export function runMissionClose(repoRoot, missionId, outcome, reason) {
|
|
164
|
+
if (locateMission(repoRoot, missionId) !== "active") {
|
|
165
|
+
throw new Error(`Mission ${missionId} is not active`);
|
|
166
|
+
}
|
|
167
|
+
const state = readMissionState(repoRoot, missionId);
|
|
168
|
+
if (state === null)
|
|
169
|
+
throw new Error(`state.json missing for ${missionId}`);
|
|
170
|
+
const closedAt = new Date().toISOString();
|
|
171
|
+
state.outcome = outcome;
|
|
172
|
+
state.cursor.active_phase = null;
|
|
173
|
+
state.cursor.active_phase_started_at = null;
|
|
174
|
+
state.closed_at = closedAt;
|
|
175
|
+
if (outcome === "aborted" && reason !== undefined)
|
|
176
|
+
state.abort_reason = reason;
|
|
177
|
+
writeMissionState(repoRoot, missionId, state);
|
|
178
|
+
appendMissionJournal(repoRoot, missionId, {
|
|
179
|
+
ts: closedAt,
|
|
180
|
+
kind: "closed",
|
|
181
|
+
detail: outcome === "aborted" ? `aborted${reason !== undefined ? `: ${reason}` : ""}` : "manual close",
|
|
182
|
+
});
|
|
183
|
+
archiveMission(repoRoot, missionId);
|
|
184
|
+
return { mission_id: missionId, outcome, closed_at: closedAt };
|
|
185
|
+
}
|
|
186
|
+
export function runMissionReopen(repoRoot, missionId) {
|
|
187
|
+
if (locateMission(repoRoot, missionId) !== "done") {
|
|
188
|
+
throw new Error(`Mission ${missionId} is not archived`);
|
|
189
|
+
}
|
|
190
|
+
if (findActiveMission(repoRoot) !== null) {
|
|
191
|
+
throw new Error("Another mission is already active");
|
|
192
|
+
}
|
|
193
|
+
if (!restoreMission(repoRoot, missionId)) {
|
|
194
|
+
throw new Error(`Could not restore archived dirs for ${missionId}`);
|
|
195
|
+
}
|
|
196
|
+
const state = readMissionState(repoRoot, missionId);
|
|
197
|
+
const reopenedAt = new Date().toISOString();
|
|
198
|
+
if (state !== null) {
|
|
199
|
+
state.outcome = "active";
|
|
200
|
+
delete state.closed_at;
|
|
201
|
+
delete state.abort_reason;
|
|
202
|
+
writeMissionState(repoRoot, missionId, state);
|
|
203
|
+
}
|
|
204
|
+
appendMissionJournal(repoRoot, missionId, {
|
|
205
|
+
ts: reopenedAt,
|
|
206
|
+
kind: "reopened",
|
|
207
|
+
detail: `cursor: ${state?.cursor.active_phase ?? "(none)"}`,
|
|
208
|
+
});
|
|
209
|
+
return {
|
|
210
|
+
mission_id: missionId,
|
|
211
|
+
cursor: state?.cursor ?? null,
|
|
212
|
+
reopened_at: reopenedAt,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
export function listMissions(repoRoot) {
|
|
216
|
+
return {
|
|
217
|
+
active: listActiveMissionIds(repoRoot),
|
|
218
|
+
done: listDoneMissionIds(repoRoot),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function deriveTitleFromSpec(source, fallback) {
|
|
222
|
+
const m = source.match(/^#\s+(.+?)\s*$/m);
|
|
223
|
+
const raw = m?.[1] ?? fallback.replace(/^.*\//, "").replace(/\.[a-z]+$/i, "");
|
|
224
|
+
return raw.slice(0, 60);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Pretty-print a roadmap to a string for `cairn mission start --print`
|
|
228
|
+
* preview output. Matches the YAML-frontmatter shape Cairn writes to
|
|
229
|
+
* disk so the operator can copy + edit it before approval.
|
|
230
|
+
*/
|
|
231
|
+
export function previewRoadmap(args) {
|
|
232
|
+
const lines = [];
|
|
233
|
+
lines.push(`# Mission preview — ${args.title}`);
|
|
234
|
+
lines.push("");
|
|
235
|
+
lines.push(`spec_path: ${args.specPath}`);
|
|
236
|
+
lines.push(`exit_gate: ${args.exitGate}`);
|
|
237
|
+
lines.push("");
|
|
238
|
+
lines.push("Phases:");
|
|
239
|
+
for (const p of args.phases) {
|
|
240
|
+
lines.push(` - ${p.id}: ${p.title}`);
|
|
241
|
+
if (p.depends_on.length > 0)
|
|
242
|
+
lines.push(` depends_on: ${p.depends_on.join(", ")}`);
|
|
243
|
+
lines.push(` exit_criteria: ${p.exit_criteria}`);
|
|
244
|
+
}
|
|
245
|
+
return lines.join("\n");
|
|
246
|
+
}
|
|
247
|
+
/** Used by `cairn mission accept --from <file>` to load a hand-edited roadmap draft. */
|
|
248
|
+
export function loadDraftFromFile(path) {
|
|
249
|
+
if (!existsSync(path))
|
|
250
|
+
throw new Error(`Draft file not found: ${path}`);
|
|
251
|
+
const raw = readFileSync(path, "utf8");
|
|
252
|
+
const parsed = JSON.parse(raw);
|
|
253
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
254
|
+
throw new Error("Draft file must be a JSON object");
|
|
255
|
+
const obj = parsed;
|
|
256
|
+
if (typeof obj["title"] !== "string")
|
|
257
|
+
throw new Error("Draft missing title");
|
|
258
|
+
if (typeof obj["spec_path"] !== "string")
|
|
259
|
+
throw new Error("Draft missing spec_path");
|
|
260
|
+
if (typeof obj["exit_gate"] !== "string")
|
|
261
|
+
throw new Error("Draft missing exit_gate");
|
|
262
|
+
if (!Array.isArray(obj["phases"]))
|
|
263
|
+
throw new Error("Draft missing phases array");
|
|
264
|
+
const exitGate = obj["exit_gate"];
|
|
265
|
+
if (exitGate !== "prompt" && exitGate !== "auto" && exitGate !== "manual") {
|
|
266
|
+
throw new Error(`Invalid exit_gate: ${exitGate}`);
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
title: obj["title"],
|
|
270
|
+
spec_path: obj["spec_path"],
|
|
271
|
+
exit_gate: exitGate,
|
|
272
|
+
phases: obj["phases"],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/** Persist the proposed roadmap draft to a temp JSON file for hand-editing. */
|
|
276
|
+
export function writeDraftToFile(path, draft) {
|
|
277
|
+
writeFileSync(path, JSON.stringify(draft, null, 2) + "\n", "utf8");
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/missions/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mission cursor advance helpers — shared logic between the explicit
|
|
3
|
+
* `cairn_mission_advance` MCP tool, the Stop hook auto-advance path
|
|
4
|
+
* (exit_gate=auto), and the Stop hook prompt path (exit_gate=prompt
|
|
5
|
+
* after operator picks `a`).
|
|
6
|
+
*/
|
|
7
|
+
import { type MissionPhase, type MissionRoadmapFrontmatter, type MissionState } from "@isaacriehm/cairn-state";
|
|
8
|
+
export interface AdvanceResult {
|
|
9
|
+
ok: true;
|
|
10
|
+
phase_advanced: string;
|
|
11
|
+
next_phase: MissionPhase | null;
|
|
12
|
+
/** True when this advance closed the mission. */
|
|
13
|
+
closed: boolean;
|
|
14
|
+
donePhases: number;
|
|
15
|
+
totalPhases: number;
|
|
16
|
+
}
|
|
17
|
+
export interface AdvanceError {
|
|
18
|
+
ok: false;
|
|
19
|
+
code: "MISSION_NOT_FOUND" | "PHASE_NOT_FOUND" | "PHASE_ALREADY_DONE" | "ROADMAP_MISSING" | "STATE_MISSING";
|
|
20
|
+
message: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Mark `phaseId` as done in `phase_progress` and pick the next pending
|
|
24
|
+
* phase whose `depends_on` set is satisfied. When no eligible phase
|
|
25
|
+
* remains, set `cursor.active_phase = null`, mark `outcome: done`, and
|
|
26
|
+
* archive the mission dirs.
|
|
27
|
+
*/
|
|
28
|
+
export declare function advancePhase(repoRoot: string, missionId: string, phaseId: string, now?: Date): AdvanceResult | AdvanceError;
|
|
29
|
+
/**
|
|
30
|
+
* Determine whether every linked task on a phase has graduated.
|
|
31
|
+
* Returns `false` when the phase has zero tasks (the operator has to
|
|
32
|
+
* `mission_advance --force` for empty phases — never auto-advance an
|
|
33
|
+
* empty phase silently).
|
|
34
|
+
*/
|
|
35
|
+
export declare function allPhaseTasksDone(state: MissionState, phaseId: string, taskIsDone: (taskId: string) => boolean): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve a roadmap phase by id from a mission's roadmap. Returns null
|
|
38
|
+
* when the id was deleted in a mid-mission roadmap edit (drift case).
|
|
39
|
+
*/
|
|
40
|
+
export declare function lookupPhase(roadmap: MissionRoadmapFrontmatter, phaseId: string): MissionPhase | null;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mission cursor advance helpers — shared logic between the explicit
|
|
3
|
+
* `cairn_mission_advance` MCP tool, the Stop hook auto-advance path
|
|
4
|
+
* (exit_gate=auto), and the Stop hook prompt path (exit_gate=prompt
|
|
5
|
+
* after operator picks `a`).
|
|
6
|
+
*/
|
|
7
|
+
import { appendMissionJournal, archiveMission, countDonePhases, nextPendingPhase, readMissionState, readRoadmap, writeMissionState, } from "@isaacriehm/cairn-state";
|
|
8
|
+
/**
|
|
9
|
+
* Mark `phaseId` as done in `phase_progress` and pick the next pending
|
|
10
|
+
* phase whose `depends_on` set is satisfied. When no eligible phase
|
|
11
|
+
* remains, set `cursor.active_phase = null`, mark `outcome: done`, and
|
|
12
|
+
* archive the mission dirs.
|
|
13
|
+
*/
|
|
14
|
+
export function advancePhase(repoRoot, missionId, phaseId, now = new Date()) {
|
|
15
|
+
const roadmap = readRoadmap(repoRoot, missionId);
|
|
16
|
+
if (roadmap === null) {
|
|
17
|
+
return { ok: false, code: "ROADMAP_MISSING", message: `roadmap.md missing for ${missionId}` };
|
|
18
|
+
}
|
|
19
|
+
const state = readMissionState(repoRoot, missionId);
|
|
20
|
+
if (state === null) {
|
|
21
|
+
return { ok: false, code: "STATE_MISSING", message: `state.json missing for ${missionId}` };
|
|
22
|
+
}
|
|
23
|
+
const phaseDef = roadmap.frontmatter.phases.find((p) => p.id === phaseId);
|
|
24
|
+
if (phaseDef === undefined) {
|
|
25
|
+
return { ok: false, code: "PHASE_NOT_FOUND", message: `phase ${phaseId} not in roadmap` };
|
|
26
|
+
}
|
|
27
|
+
const progress = state.phase_progress[phaseId];
|
|
28
|
+
if (progress?.state === "done") {
|
|
29
|
+
return { ok: false, code: "PHASE_ALREADY_DONE", message: `phase ${phaseId} already done` };
|
|
30
|
+
}
|
|
31
|
+
const ts = now.toISOString();
|
|
32
|
+
state.phase_progress[phaseId] = {
|
|
33
|
+
state: "done",
|
|
34
|
+
task_ids: progress?.task_ids ?? [],
|
|
35
|
+
graduated_at: ts,
|
|
36
|
+
};
|
|
37
|
+
const next = nextPendingPhase(roadmap.frontmatter, state);
|
|
38
|
+
if (next !== null) {
|
|
39
|
+
state.cursor.active_phase = next.id;
|
|
40
|
+
state.cursor.active_phase_started_at = ts;
|
|
41
|
+
state.phase_progress[next.id] = {
|
|
42
|
+
state: "in_progress",
|
|
43
|
+
task_ids: state.phase_progress[next.id]?.task_ids ?? [],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
state.cursor.active_phase = null;
|
|
48
|
+
state.cursor.active_phase_started_at = null;
|
|
49
|
+
state.outcome = "done";
|
|
50
|
+
state.closed_at = ts;
|
|
51
|
+
}
|
|
52
|
+
writeMissionState(repoRoot, missionId, state);
|
|
53
|
+
appendMissionJournal(repoRoot, missionId, {
|
|
54
|
+
ts,
|
|
55
|
+
kind: "phase-advanced",
|
|
56
|
+
phase_id: phaseId,
|
|
57
|
+
detail: next !== null ? `next: ${next.id}` : "mission complete",
|
|
58
|
+
});
|
|
59
|
+
let closed = false;
|
|
60
|
+
if (next === null) {
|
|
61
|
+
appendMissionJournal(repoRoot, missionId, {
|
|
62
|
+
ts,
|
|
63
|
+
kind: "closed",
|
|
64
|
+
detail: "auto-close on last phase complete",
|
|
65
|
+
});
|
|
66
|
+
archiveMission(repoRoot, missionId);
|
|
67
|
+
closed = true;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
ok: true,
|
|
71
|
+
phase_advanced: phaseId,
|
|
72
|
+
next_phase: next,
|
|
73
|
+
closed,
|
|
74
|
+
donePhases: countDonePhases(state),
|
|
75
|
+
totalPhases: roadmap.frontmatter.phases.length,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Determine whether every linked task on a phase has graduated.
|
|
80
|
+
* Returns `false` when the phase has zero tasks (the operator has to
|
|
81
|
+
* `mission_advance --force` for empty phases — never auto-advance an
|
|
82
|
+
* empty phase silently).
|
|
83
|
+
*/
|
|
84
|
+
export function allPhaseTasksDone(state, phaseId, taskIsDone) {
|
|
85
|
+
const progress = state.phase_progress[phaseId];
|
|
86
|
+
if (progress === undefined)
|
|
87
|
+
return false;
|
|
88
|
+
if (progress.task_ids.length === 0)
|
|
89
|
+
return false;
|
|
90
|
+
return progress.task_ids.every(taskIsDone);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolve a roadmap phase by id from a mission's roadmap. Returns null
|
|
94
|
+
* when the id was deleted in a mid-mission roadmap edit (drift case).
|
|
95
|
+
*/
|
|
96
|
+
export function lookupPhase(roadmap, phaseId) {
|
|
97
|
+
return roadmap.phases.find((p) => p.id === phaseId) ?? null;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/missions/cursor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mission roadmap drafting — Haiku call that turns a planning doc into
|
|
3
|
+
* an ordered phase array. One LLM-cost touchpoint per mission
|
|
4
|
+
* lifecycle. All subsequent phase advance / linking is deterministic.
|
|
5
|
+
*
|
|
6
|
+
* The model receives ONLY the spec doc + a tight system prompt; no
|
|
7
|
+
* project ground state, no MCP tools, no ambient CLAUDE.md hierarchy.
|
|
8
|
+
* `isolateAmbientContext: true` enforces that.
|
|
9
|
+
*/
|
|
10
|
+
import type { MissionPhase } from "@isaacriehm/cairn-state";
|
|
11
|
+
export interface DraftRoadmapArgs {
|
|
12
|
+
repoRoot: string;
|
|
13
|
+
spec: string;
|
|
14
|
+
specPath: string;
|
|
15
|
+
}
|
|
16
|
+
export interface DraftRoadmapResult {
|
|
17
|
+
phases: MissionPhase[];
|
|
18
|
+
/** Char count of the spec slice actually fed to Haiku. */
|
|
19
|
+
spec_chars_used: number;
|
|
20
|
+
/** True when the spec was truncated to fit MAX_SPEC_CHARS. */
|
|
21
|
+
truncated: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function draftRoadmapFromSpec(args: DraftRoadmapArgs): Promise<DraftRoadmapResult | null>;
|
|
24
|
+
/**
|
|
25
|
+
* Single-phase fallback used when `no_llm: true` or Haiku is offline.
|
|
26
|
+
* Lets the operator hand-edit roadmap.md before approving.
|
|
27
|
+
*/
|
|
28
|
+
export declare function stubRoadmap(): MissionPhase[];
|