@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,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn_mission_resync_accept` — apply or reject the pending resync
|
|
3
|
+
* marker (`.cairn/missions/<id>/_resync.json`) written by
|
|
4
|
+
* `cairn_mission_resync`. On accept: rewrite roadmap.md with the
|
|
5
|
+
* proposed phases (preserving mission_id, title, exit_gate,
|
|
6
|
+
* created_at), refresh spec.md from the live spec doc, reconcile
|
|
7
|
+
* `phase_progress` (added phases → pending entries; removed phases →
|
|
8
|
+
* dropped with journal note), and reposition the cursor when the
|
|
9
|
+
* active phase was deleted.
|
|
10
|
+
*
|
|
11
|
+
* Reject just deletes the marker; roadmap.md and state.json stay
|
|
12
|
+
* untouched.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
15
|
+
import { isAbsolute, join, resolve as pathResolve } from "node:path";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import { appendMissionJournal, findActiveMission, missionRuntimeDir, nextPendingPhase, readMissionState, readRoadmap, writeMissionSpec, writeMissionState, writeRoadmap, } from "@isaacriehm/cairn-state";
|
|
18
|
+
import { requireBootstrap } from "../bootstrap-guard.js";
|
|
19
|
+
import { mcpError } from "../errors.js";
|
|
20
|
+
import { missionResyncAcceptInput } from "../schemas.js";
|
|
21
|
+
const ResyncMarkerSchema = z.object({
|
|
22
|
+
generated_at: z.string(),
|
|
23
|
+
spec_path: z.string(),
|
|
24
|
+
proposed_phases: z.array(z.object({
|
|
25
|
+
id: z.string(),
|
|
26
|
+
title: z.string(),
|
|
27
|
+
depends_on: z.array(z.string()).default([]),
|
|
28
|
+
exit_criteria: z.string(),
|
|
29
|
+
exit_gate: z.enum(["prompt", "auto", "manual"]).optional(),
|
|
30
|
+
})),
|
|
31
|
+
diff: z
|
|
32
|
+
.object({
|
|
33
|
+
added: z.array(z.string()),
|
|
34
|
+
removed: z.array(z.string()),
|
|
35
|
+
renamed: z.array(z.object({ from: z.string(), to: z.string() })),
|
|
36
|
+
exit_criteria_changed: z.array(z.object({ id: z.string(), before: z.string(), after: z.string() })),
|
|
37
|
+
})
|
|
38
|
+
.passthrough(),
|
|
39
|
+
});
|
|
40
|
+
async function handler(ctx, input) {
|
|
41
|
+
const block = requireBootstrap(ctx.repoRoot);
|
|
42
|
+
if (block !== null)
|
|
43
|
+
return block;
|
|
44
|
+
const missionId = findActiveMission(ctx.repoRoot);
|
|
45
|
+
if (missionId === null) {
|
|
46
|
+
return mcpError("MISSION_NOT_FOUND", "No active mission");
|
|
47
|
+
}
|
|
48
|
+
const markerPath = join(missionRuntimeDir(ctx.repoRoot, missionId), "_resync.json");
|
|
49
|
+
if (!existsSync(markerPath)) {
|
|
50
|
+
return mcpError("FILE_NOT_FOUND", `No pending resync for ${missionId} (_resync.json not present)`);
|
|
51
|
+
}
|
|
52
|
+
let raw;
|
|
53
|
+
try {
|
|
54
|
+
raw = readFileSync(markerPath, "utf8");
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
return mcpError("INTERNAL_ERROR", `Failed to read resync marker: ${err instanceof Error ? err.message : String(err)}`);
|
|
58
|
+
}
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = JSON.parse(raw);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
return mcpError("INTERNAL_ERROR", `Resync marker malformed: ${err instanceof Error ? err.message : String(err)}`);
|
|
65
|
+
}
|
|
66
|
+
const markerResult = ResyncMarkerSchema.safeParse(parsed);
|
|
67
|
+
if (!markerResult.success) {
|
|
68
|
+
return mcpError("VALIDATION_FAILED", `Resync marker invalid: ${markerResult.error.message}`);
|
|
69
|
+
}
|
|
70
|
+
const marker = markerResult.data;
|
|
71
|
+
const ts = new Date().toISOString();
|
|
72
|
+
if (input.outcome === "reject") {
|
|
73
|
+
rmSync(markerPath, { force: true });
|
|
74
|
+
appendMissionJournal(ctx.repoRoot, missionId, {
|
|
75
|
+
ts,
|
|
76
|
+
kind: "resync-applied",
|
|
77
|
+
detail: "rejected by operator",
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
ok: true,
|
|
81
|
+
mission_id: missionId,
|
|
82
|
+
outcome: "rejected",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// outcome === "accept"
|
|
86
|
+
const roadmap = readRoadmap(ctx.repoRoot, missionId);
|
|
87
|
+
const state = readMissionState(ctx.repoRoot, missionId);
|
|
88
|
+
if (roadmap === null || state === null) {
|
|
89
|
+
return mcpError("MISSION_NOT_FOUND", `Mission ${missionId} state or roadmap unreadable`);
|
|
90
|
+
}
|
|
91
|
+
// Build new roadmap frontmatter — preserve mission_id, title,
|
|
92
|
+
// exit_gate, created_at, spec_path. Replace phases with the
|
|
93
|
+
// operator-approved proposed_phases.
|
|
94
|
+
const newPhases = marker.proposed_phases.map((p) => ({
|
|
95
|
+
id: p.id,
|
|
96
|
+
title: p.title,
|
|
97
|
+
depends_on: p.depends_on,
|
|
98
|
+
exit_criteria: p.exit_criteria,
|
|
99
|
+
...(p.exit_gate !== undefined ? { exit_gate: p.exit_gate } : {}),
|
|
100
|
+
}));
|
|
101
|
+
const newFrontmatter = {
|
|
102
|
+
mission_id: roadmap.frontmatter.mission_id,
|
|
103
|
+
title: roadmap.frontmatter.title,
|
|
104
|
+
spec_path: marker.spec_path,
|
|
105
|
+
created_at: roadmap.frontmatter.created_at,
|
|
106
|
+
exit_gate: roadmap.frontmatter.exit_gate,
|
|
107
|
+
phases: newPhases,
|
|
108
|
+
};
|
|
109
|
+
writeRoadmap(ctx.repoRoot, missionId, newFrontmatter, roadmap.prose);
|
|
110
|
+
// Refresh spec.md from the live spec doc.
|
|
111
|
+
const absSpec = isAbsolute(marker.spec_path)
|
|
112
|
+
? marker.spec_path
|
|
113
|
+
: pathResolve(ctx.repoRoot, marker.spec_path);
|
|
114
|
+
if (existsSync(absSpec)) {
|
|
115
|
+
try {
|
|
116
|
+
const liveSpec = readFileSync(absSpec, "utf8");
|
|
117
|
+
writeMissionSpec(ctx.repoRoot, missionId, liveSpec);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// best-effort spec refresh; resync still applies even if spec read fails
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Reconcile phase_progress.
|
|
124
|
+
// - Added phases: insert a `pending` entry with no task_ids.
|
|
125
|
+
// - Removed phases (in marker.diff.removed): drop from phase_progress;
|
|
126
|
+
// journal each drop with the orphaned task ids.
|
|
127
|
+
// - Surviving phases keep their state + task_ids.
|
|
128
|
+
const orphanedByPhase = {};
|
|
129
|
+
for (const removed of marker.diff.removed) {
|
|
130
|
+
const taskIds = state.phase_progress[removed]?.task_ids ?? [];
|
|
131
|
+
if (taskIds.length > 0)
|
|
132
|
+
orphanedByPhase[removed] = taskIds;
|
|
133
|
+
delete state.phase_progress[removed];
|
|
134
|
+
}
|
|
135
|
+
for (const added of marker.diff.added) {
|
|
136
|
+
if (!(added in state.phase_progress)) {
|
|
137
|
+
state.phase_progress[added] = { state: "pending", task_ids: [] };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Cursor: if the current active phase was removed, recompute via
|
|
141
|
+
// nextPendingPhase against the new roadmap. If no phase remains
|
|
142
|
+
// pending, leave cursor null (mission may auto-close on next
|
|
143
|
+
// advance).
|
|
144
|
+
const cursorPhaseId = state.cursor.active_phase;
|
|
145
|
+
if (cursorPhaseId !== null && !newPhases.some((p) => p.id === cursorPhaseId)) {
|
|
146
|
+
const nextPhase = nextPendingPhase(newFrontmatter, state);
|
|
147
|
+
if (nextPhase !== null) {
|
|
148
|
+
state.cursor.active_phase = nextPhase.id;
|
|
149
|
+
state.cursor.active_phase_started_at = ts;
|
|
150
|
+
const existing = state.phase_progress[nextPhase.id];
|
|
151
|
+
state.phase_progress[nextPhase.id] = {
|
|
152
|
+
state: "in_progress",
|
|
153
|
+
task_ids: existing?.task_ids ?? [],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
state.cursor.active_phase = null;
|
|
158
|
+
state.cursor.active_phase_started_at = null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
writeMissionState(ctx.repoRoot, missionId, state);
|
|
162
|
+
appendMissionJournal(ctx.repoRoot, missionId, {
|
|
163
|
+
ts,
|
|
164
|
+
kind: "resync-applied",
|
|
165
|
+
detail: `+${marker.diff.added.length} −${marker.diff.removed.length} ↻${marker.diff.renamed.length} criteria${marker.diff.exit_criteria_changed.length}`,
|
|
166
|
+
});
|
|
167
|
+
for (const [phaseId, ids] of Object.entries(orphanedByPhase)) {
|
|
168
|
+
appendMissionJournal(ctx.repoRoot, missionId, {
|
|
169
|
+
ts,
|
|
170
|
+
kind: "drift-detected",
|
|
171
|
+
phase_id: phaseId,
|
|
172
|
+
detail: `dropped via resync — ${ids.length} graduated task(s) orphaned`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
rmSync(markerPath, { force: true });
|
|
176
|
+
return {
|
|
177
|
+
ok: true,
|
|
178
|
+
mission_id: missionId,
|
|
179
|
+
outcome: "applied",
|
|
180
|
+
diff: marker.diff,
|
|
181
|
+
new_phase_count: newPhases.length,
|
|
182
|
+
cursor_phase: state.cursor.active_phase,
|
|
183
|
+
orphaned_tasks_by_phase: orphanedByPhase,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
export const missionResyncAcceptTool = {
|
|
187
|
+
name: "cairn_mission_resync_accept",
|
|
188
|
+
description: "Apply or reject the pending resync marker written by cairn_mission_resync. `outcome=accept` rewrites roadmap.md with the proposed phases, refreshes spec.md from the live spec, reconciles phase_progress (adds/drops), and repositions the cursor when the active phase was deleted. `outcome=reject` deletes the marker without touching anything else. Idempotent — once applied, the marker is gone.",
|
|
189
|
+
inputSchema: missionResyncAcceptInput,
|
|
190
|
+
handler,
|
|
191
|
+
};
|
|
192
|
+
//# sourceMappingURL=mission-resync-accept.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mission-resync-accept.js","sourceRoot":"","sources":["../../../src/mcp/tools/mission-resync-accept.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn_mission_resync` — operator amended the source spec doc and
|
|
3
|
+
* wants Cairn to pick up the changes.
|
|
4
|
+
*
|
|
5
|
+
* Re-parses the spec via Haiku (or `no_llm: true` stub fallback),
|
|
6
|
+
* diffs against the current roadmap.md, and writes a `mission_resync_pending`
|
|
7
|
+
* marker file under `.cairn/missions/<id>/_resync.json`. The operator
|
|
8
|
+
* resolves via `cairn-attention` — accepting the diff overwrites the
|
|
9
|
+
* roadmap and refreshes `spec.md`; rejecting drops the marker.
|
|
10
|
+
*
|
|
11
|
+
* No mutation of `roadmap.md` or `phase_progress` happens here — the
|
|
12
|
+
* operator gates the apply step via `cairn_mission_resync_accept`.
|
|
13
|
+
*/
|
|
14
|
+
import type { ToolDef } from "./types.js";
|
|
15
|
+
interface Input {
|
|
16
|
+
spec_path?: string;
|
|
17
|
+
no_llm?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare const missionResyncTool: ToolDef<Input>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn_mission_resync` — operator amended the source spec doc and
|
|
3
|
+
* wants Cairn to pick up the changes.
|
|
4
|
+
*
|
|
5
|
+
* Re-parses the spec via Haiku (or `no_llm: true` stub fallback),
|
|
6
|
+
* diffs against the current roadmap.md, and writes a `mission_resync_pending`
|
|
7
|
+
* marker file under `.cairn/missions/<id>/_resync.json`. The operator
|
|
8
|
+
* resolves via `cairn-attention` — accepting the diff overwrites the
|
|
9
|
+
* roadmap and refreshes `spec.md`; rejecting drops the marker.
|
|
10
|
+
*
|
|
11
|
+
* No mutation of `roadmap.md` or `phase_progress` happens here — the
|
|
12
|
+
* operator gates the apply step via `cairn_mission_resync_accept`.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { isAbsolute, join, resolve as pathResolve } from "node:path";
|
|
16
|
+
import { appendMissionJournal, findActiveMission, missionRuntimeDir, readRoadmap, } from "@isaacriehm/cairn-state";
|
|
17
|
+
import { requireBootstrap } from "../bootstrap-guard.js";
|
|
18
|
+
import { mcpError } from "../errors.js";
|
|
19
|
+
import { missionResyncInput } from "../schemas.js";
|
|
20
|
+
import { draftRoadmapFromSpec, stubRoadmap } from "../../missions/index.js";
|
|
21
|
+
async function handler(ctx, input) {
|
|
22
|
+
const block = requireBootstrap(ctx.repoRoot);
|
|
23
|
+
if (block !== null)
|
|
24
|
+
return block;
|
|
25
|
+
const missionId = findActiveMission(ctx.repoRoot);
|
|
26
|
+
if (missionId === null) {
|
|
27
|
+
return mcpError("MISSION_NOT_FOUND", "No active mission to resync");
|
|
28
|
+
}
|
|
29
|
+
const roadmap = readRoadmap(ctx.repoRoot, missionId);
|
|
30
|
+
if (roadmap === null) {
|
|
31
|
+
return mcpError("MISSION_NOT_FOUND", `Roadmap missing for ${missionId}`);
|
|
32
|
+
}
|
|
33
|
+
const specPath = input.spec_path ?? roadmap.frontmatter.spec_path;
|
|
34
|
+
const absSpec = isAbsolute(specPath) ? specPath : pathResolve(ctx.repoRoot, specPath);
|
|
35
|
+
if (!absSpec.startsWith(ctx.repoRoot)) {
|
|
36
|
+
return mcpError("PATH_OUTSIDE_REPO", `${specPath} resolves outside the repo`);
|
|
37
|
+
}
|
|
38
|
+
if (!existsSync(absSpec)) {
|
|
39
|
+
return mcpError("FILE_NOT_FOUND", `Spec doc not found: ${specPath}`);
|
|
40
|
+
}
|
|
41
|
+
let source;
|
|
42
|
+
try {
|
|
43
|
+
source = readFileSync(absSpec, "utf8");
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return mcpError("INTERNAL_ERROR", `Failed to read spec doc: ${err instanceof Error ? err.message : String(err)}`);
|
|
47
|
+
}
|
|
48
|
+
let newPhases;
|
|
49
|
+
if (input.no_llm === true) {
|
|
50
|
+
newPhases = stubRoadmap();
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const draft = await draftRoadmapFromSpec({
|
|
54
|
+
repoRoot: ctx.repoRoot,
|
|
55
|
+
spec: source,
|
|
56
|
+
specPath,
|
|
57
|
+
});
|
|
58
|
+
if (draft === null) {
|
|
59
|
+
return mcpError("MISSION_DRAFT_FAILED", "Haiku failed to re-parse the spec. Retry, or pass `no_llm: true` for a stub fallback.");
|
|
60
|
+
}
|
|
61
|
+
newPhases = draft.phases;
|
|
62
|
+
}
|
|
63
|
+
const oldIds = new Set(roadmap.frontmatter.phases.map((p) => p.id));
|
|
64
|
+
const newIds = new Set(newPhases.map((p) => p.id));
|
|
65
|
+
const added = [...newIds].filter((id) => !oldIds.has(id));
|
|
66
|
+
const removed = [...oldIds].filter((id) => !newIds.has(id));
|
|
67
|
+
const renamed = [];
|
|
68
|
+
// crude rename detection — same title at different ids
|
|
69
|
+
for (const op of roadmap.frontmatter.phases) {
|
|
70
|
+
for (const np of newPhases) {
|
|
71
|
+
if (op.id !== np.id && op.title === np.title) {
|
|
72
|
+
renamed.push({ from: op.id, to: np.id });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const exit_criteria_changed = [];
|
|
77
|
+
for (const op of roadmap.frontmatter.phases) {
|
|
78
|
+
const np = newPhases.find((n) => n.id === op.id);
|
|
79
|
+
if (np !== undefined && np.exit_criteria !== op.exit_criteria) {
|
|
80
|
+
exit_criteria_changed.push({ id: op.id, before: op.exit_criteria, after: np.exit_criteria });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const ts = new Date().toISOString();
|
|
84
|
+
const resyncPath = join(missionRuntimeDir(ctx.repoRoot, missionId), "_resync.json");
|
|
85
|
+
mkdirSync(missionRuntimeDir(ctx.repoRoot, missionId), { recursive: true });
|
|
86
|
+
writeFileSync(resyncPath, JSON.stringify({
|
|
87
|
+
generated_at: ts,
|
|
88
|
+
spec_path: specPath,
|
|
89
|
+
proposed_phases: newPhases,
|
|
90
|
+
diff: { added, removed, renamed, exit_criteria_changed },
|
|
91
|
+
}, null, 2), "utf8");
|
|
92
|
+
appendMissionJournal(ctx.repoRoot, missionId, {
|
|
93
|
+
ts,
|
|
94
|
+
kind: "resync-pending",
|
|
95
|
+
detail: `+${added.length} −${removed.length} ↻${renamed.length} criteria${exit_criteria_changed.length}`,
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
ok: true,
|
|
99
|
+
mission_id: missionId,
|
|
100
|
+
spec_path: specPath,
|
|
101
|
+
diff: { added, removed, renamed, exit_criteria_changed },
|
|
102
|
+
proposed_phases: newPhases,
|
|
103
|
+
resync_marker_path: `.cairn/missions/${missionId}/_resync.json`,
|
|
104
|
+
note: "Operator reviews via cairn-attention (kind: mission_resync_pending). Apply or reject via cairn_mission_resync_accept.",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export const missionResyncTool = {
|
|
108
|
+
name: "cairn_mission_resync",
|
|
109
|
+
description: "Re-parse the source spec doc, diff against the current roadmap.md, and write a `mission_resync_pending` marker. Does NOT mutate roadmap.md or phase_progress — the operator must accept the diff via cairn-attention. Use when the operator amended the live spec doc; the frozen `spec.md` snapshot stays untouched until the resync is accepted.",
|
|
110
|
+
inputSchema: missionResyncInput,
|
|
111
|
+
handler,
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=mission-resync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mission-resync.js","sourceRoot":"","sources":["../../../src/mcp/tools/mission-resync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { findActiveMission } from "@isaacriehm/cairn-state";
|
|
4
|
+
import { requireBootstrap } from "../bootstrap-guard.js";
|
|
5
|
+
import { mcpError } from "../errors.js";
|
|
6
|
+
import { missionStartInput } from "../schemas.js";
|
|
7
|
+
import { draftRoadmapFromSpec, stubRoadmap } from "../../missions/index.js";
|
|
8
|
+
async function handler(ctx, input) {
|
|
9
|
+
const block = requireBootstrap(ctx.repoRoot);
|
|
10
|
+
if (block !== null)
|
|
11
|
+
return block;
|
|
12
|
+
if (findActiveMission(ctx.repoRoot) !== null) {
|
|
13
|
+
return mcpError("MISSION_ALREADY_ACTIVE", "An active mission already exists. Close or abort it before starting another (one active mission per repo).");
|
|
14
|
+
}
|
|
15
|
+
const absSpec = isAbsolute(input.spec_path)
|
|
16
|
+
? input.spec_path
|
|
17
|
+
: resolve(ctx.repoRoot, input.spec_path);
|
|
18
|
+
if (!absSpec.startsWith(ctx.repoRoot)) {
|
|
19
|
+
return mcpError("PATH_OUTSIDE_REPO", `${input.spec_path} resolves outside the repo`);
|
|
20
|
+
}
|
|
21
|
+
if (!existsSync(absSpec)) {
|
|
22
|
+
return mcpError("FILE_NOT_FOUND", `Spec doc not found: ${input.spec_path}`);
|
|
23
|
+
}
|
|
24
|
+
let source;
|
|
25
|
+
try {
|
|
26
|
+
source = readFileSync(absSpec, "utf8");
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
return mcpError("INTERNAL_ERROR", `Failed to read spec doc: ${err instanceof Error ? err.message : String(err)}`);
|
|
30
|
+
}
|
|
31
|
+
const proposedTitle = deriveTitleFromSpec(source, input.spec_path);
|
|
32
|
+
if (input.no_llm === true) {
|
|
33
|
+
return {
|
|
34
|
+
ok: true,
|
|
35
|
+
proposed_title: proposedTitle,
|
|
36
|
+
spec_path: input.spec_path,
|
|
37
|
+
exit_gate: input.exit_gate,
|
|
38
|
+
phases: stubRoadmap(),
|
|
39
|
+
truncated: false,
|
|
40
|
+
llm_used: false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const draft = await draftRoadmapFromSpec({
|
|
44
|
+
repoRoot: ctx.repoRoot,
|
|
45
|
+
spec: source,
|
|
46
|
+
specPath: input.spec_path,
|
|
47
|
+
});
|
|
48
|
+
if (draft === null) {
|
|
49
|
+
return mcpError("MISSION_DRAFT_FAILED", "Haiku failed to parse the spec doc. Retry, or pass `no_llm: true` to write a single-phase stub roadmap and hand-edit it.");
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
ok: true,
|
|
53
|
+
proposed_title: proposedTitle,
|
|
54
|
+
spec_path: input.spec_path,
|
|
55
|
+
exit_gate: input.exit_gate,
|
|
56
|
+
phases: draft.phases,
|
|
57
|
+
truncated: draft.truncated,
|
|
58
|
+
llm_used: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* First H1 in the spec, or the spec's filename without extension.
|
|
63
|
+
* Capped at 60 chars so the slug fits within the statusline budget.
|
|
64
|
+
*/
|
|
65
|
+
function deriveTitleFromSpec(source, fallback) {
|
|
66
|
+
const m = source.match(/^#\s+(.+?)\s*$/m);
|
|
67
|
+
const raw = m?.[1] ?? fallback.replace(/^.*\//, "").replace(/\.[a-z]+$/i, "");
|
|
68
|
+
return raw.slice(0, 60);
|
|
69
|
+
}
|
|
70
|
+
export const missionStartTool = {
|
|
71
|
+
name: "cairn_mission_start",
|
|
72
|
+
description: "Read a planning spec doc and draft a mission roadmap via Haiku. Returns the draft (proposed_title + ordered phases + spec_path + exit_gate) for operator approval. Does NOT write anything to disk; the caller invokes cairn_mission_accept_draft once the operator confirms. Pass `no_llm: true` to skip Haiku and return a single-phase stub.",
|
|
73
|
+
inputSchema: missionStartInput,
|
|
74
|
+
handler,
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=mission-start.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mission-start.js","sourceRoot":"","sources":["../../../src/mcp/tools/mission-start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAS5E,KAAK,UAAU,OAAO,CAAC,GAAe,EAAE,KAAY;IAClD,MAAM,KAAK,GAAG,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAEjC,IAAI,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7C,OAAO,QAAQ,CACb,wBAAwB,EACxB,4GAA4G,CAC7G,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC;QACzC,CAAC,CAAC,KAAK,CAAC,SAAS;QACjB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC3C,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,OAAO,QAAQ,CAAC,mBAAmB,EAAE,GAAG,KAAK,CAAC,SAAS,4BAA4B,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,OAAO,QAAQ,CAAC,gBAAgB,EAAE,uBAAuB,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,QAAQ,CACb,gBAAgB,EAChB,4BAA4B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC/E,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAEnE,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAC1B,OAAO;YACL,EAAE,EAAE,IAAI;YACR,cAAc,EAAE,aAAa;YAC7B,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,MAAM,EAAE,WAAW,EAAE;YACrB,SAAS,EAAE,KAAK;YAChB,QAAQ,EAAE,KAAK;SAChB,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,oBAAoB,CAAC;QACvC,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,KAAK,CAAC,SAAS;KAC1B,CAAC,CAAC;IAEH,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,QAAQ,CACb,sBAAsB,EACtB,0HAA0H,CAC3H,CAAC;IACJ,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI;QACR,cAAc,EAAE,aAAa;QAC7B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,QAAQ,EAAE,IAAI;KACf,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAAC,MAAc,EAAE,QAAgB;IAC3D,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC9E,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EACT,iVAAiV;IACnV,WAAW,EAAE,iBAAiB;IAC9B,OAAO;CACR,CAAC"}
|
package/dist/mcp/tools/resume.js
CHANGED
|
@@ -22,9 +22,26 @@ async function handler(ctx, input) {
|
|
|
22
22
|
if (taskId === null) {
|
|
23
23
|
return mcpError("TASK_NOT_FOUND", "no active task to resume");
|
|
24
24
|
}
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const activeTaskDir = join(ctx.repoRoot, ".cairn", "tasks", "active", taskId);
|
|
26
|
+
const doneTaskDir = join(ctx.repoRoot, ".cairn", "tasks", "done", taskId);
|
|
27
|
+
let scope;
|
|
28
|
+
let taskDir;
|
|
29
|
+
let completedAt = null;
|
|
30
|
+
if (existsSync(activeTaskDir)) {
|
|
31
|
+
scope = "active";
|
|
32
|
+
taskDir = activeTaskDir;
|
|
33
|
+
}
|
|
34
|
+
else if (existsSync(doneTaskDir)) {
|
|
35
|
+
// Race: task graduated between the Stop-hook resume prompt and the
|
|
36
|
+
// operator pasting `/cairn-resume`. Surface the final journal frame
|
|
37
|
+
// + completion timestamp instead of the cryptic "not found" error
|
|
38
|
+
// so the operator sees what shipped.
|
|
39
|
+
scope = "done";
|
|
40
|
+
taskDir = doneTaskDir;
|
|
41
|
+
completedAt = readCompletedAt(join(doneTaskDir, "status.yaml"));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
return mcpError("TASK_NOT_FOUND", `task directory missing in active/ and done/: ${taskId}`);
|
|
28
45
|
}
|
|
29
46
|
const specPath = join(taskDir, "spec.tightened.md");
|
|
30
47
|
let title = taskId;
|
|
@@ -60,7 +77,7 @@ async function handler(ctx, input) {
|
|
|
60
77
|
}
|
|
61
78
|
}
|
|
62
79
|
}
|
|
63
|
-
const journal = readTaskJournal(ctx.repoRoot, taskId,
|
|
80
|
+
const journal = readTaskJournal(ctx.repoRoot, taskId, scope);
|
|
64
81
|
const cap = Math.max(1, Math.min(50, input.max_entries ?? 7));
|
|
65
82
|
const recent = journal.slice(-cap);
|
|
66
83
|
const lastEntry = journal.length > 0 ? journal[journal.length - 1] : null;
|
|
@@ -68,6 +85,8 @@ async function handler(ctx, input) {
|
|
|
68
85
|
const payload = {
|
|
69
86
|
ok: true,
|
|
70
87
|
task_id: taskId,
|
|
88
|
+
scope,
|
|
89
|
+
completed_at: completedAt,
|
|
71
90
|
title,
|
|
72
91
|
goal,
|
|
73
92
|
in_scope_decisions: inScopeDecisions,
|
|
@@ -79,6 +98,23 @@ async function handler(ctx, input) {
|
|
|
79
98
|
};
|
|
80
99
|
return payload;
|
|
81
100
|
}
|
|
101
|
+
function readCompletedAt(statusPath) {
|
|
102
|
+
if (!existsSync(statusPath))
|
|
103
|
+
return null;
|
|
104
|
+
try {
|
|
105
|
+
const raw = readFileSync(statusPath, "utf8");
|
|
106
|
+
const parsed = parseYaml(raw);
|
|
107
|
+
if (parsed !== null && typeof parsed === "object") {
|
|
108
|
+
const v = parsed.completed_at;
|
|
109
|
+
if (typeof v === "string" && v.length > 0)
|
|
110
|
+
return v;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// fall through
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
82
118
|
export const resumeTool = {
|
|
83
119
|
name: "cairn_resume",
|
|
84
120
|
description: "Read the active task's journal + tightened spec and emit a resume payload (title, goal, in-scope DECs/INVs, last N journal entries, last-known next_step). Used after `/clear` to rebuild operator context cold. `task_id` defaults to the most-recently-touched active task.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resume.js","sourceRoot":"","sources":["../../../src/mcp/tools/resume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EACL,qBAAqB,EACrB,eAAe,GAEhB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"resume.js","sourceRoot":"","sources":["../../../src/mcp/tools/resume.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import { stringify as stringifyYaml } from "yaml";
|
|
5
|
+
import { findActiveMission, readMissionState, readRoadmap, } from "@isaacriehm/cairn-state";
|
|
5
6
|
import { requireBootstrap } from "../bootstrap-guard.js";
|
|
6
7
|
import { mcpError } from "../errors.js";
|
|
7
8
|
import { taskCreateInput } from "../schemas.js";
|
|
@@ -45,6 +46,36 @@ async function handler(ctx, input) {
|
|
|
45
46
|
mkdirSync(taskDir, { recursive: true });
|
|
46
47
|
const generatedAt = new Date().toISOString();
|
|
47
48
|
const needsReview = input.needs_review ?? true;
|
|
49
|
+
// Mission anchor — explicit input wins; otherwise inherit from the
|
|
50
|
+
// active mission's cursor. Empty string means opt-out (side-task).
|
|
51
|
+
let missionId = null;
|
|
52
|
+
let phaseId = null;
|
|
53
|
+
if (input.mission_id === "") {
|
|
54
|
+
missionId = null;
|
|
55
|
+
phaseId = null;
|
|
56
|
+
}
|
|
57
|
+
else if (input.mission_id !== undefined && input.mission_id !== null) {
|
|
58
|
+
missionId = input.mission_id;
|
|
59
|
+
phaseId = input.phase_id ?? null;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const activeMission = findActiveMission(ctx.repoRoot);
|
|
63
|
+
if (activeMission !== null) {
|
|
64
|
+
const state = readMissionState(ctx.repoRoot, activeMission);
|
|
65
|
+
const roadmap = readRoadmap(ctx.repoRoot, activeMission);
|
|
66
|
+
const cursorPhase = state?.cursor.active_phase ?? null;
|
|
67
|
+
if (cursorPhase !== null && roadmap?.frontmatter.phases.some((p) => p.id === cursorPhase)) {
|
|
68
|
+
missionId = activeMission;
|
|
69
|
+
phaseId = input.phase_id ?? cursorPhase;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (missionId !== null && phaseId !== null) {
|
|
74
|
+
const roadmap = readRoadmap(ctx.repoRoot, missionId);
|
|
75
|
+
if (roadmap !== null && !roadmap.frontmatter.phases.some((p) => p.id === phaseId)) {
|
|
76
|
+
return mcpError("VALIDATION_FAILED", `phase_id ${phaseId} not present in roadmap of ${missionId}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
48
79
|
const specFrontmatter = {
|
|
49
80
|
id: taskId,
|
|
50
81
|
title: input.title,
|
|
@@ -77,13 +108,18 @@ async function handler(ctx, input) {
|
|
|
77
108
|
const specContent = `---\n${stringifyYaml(specFrontmatter)}---\n\n${specBody}`;
|
|
78
109
|
const specPath = join(taskDir, "spec.tightened.md");
|
|
79
110
|
writeFileSync(specPath, specContent, "utf8");
|
|
80
|
-
const
|
|
111
|
+
const statusFrame = {
|
|
81
112
|
id: taskId,
|
|
82
113
|
phase: "running",
|
|
83
114
|
module: input.module ?? input.target_path_globs[0]?.split("/")[0] ?? ".",
|
|
84
115
|
title: input.title,
|
|
85
116
|
started_at: generatedAt,
|
|
86
|
-
}
|
|
117
|
+
};
|
|
118
|
+
if (missionId !== null && phaseId !== null) {
|
|
119
|
+
statusFrame["mission_id"] = missionId;
|
|
120
|
+
statusFrame["phase_id"] = phaseId;
|
|
121
|
+
}
|
|
122
|
+
const statusContent = stringifyYaml(statusFrame);
|
|
87
123
|
const statusPath = join(taskDir, "status.yaml");
|
|
88
124
|
writeFileSync(statusPath, statusContent, "utf8");
|
|
89
125
|
return {
|
|
@@ -93,6 +129,8 @@ async function handler(ctx, input) {
|
|
|
93
129
|
status_path: `.cairn/tasks/active/${taskId}/status.yaml`,
|
|
94
130
|
in_scope_decisions: input.in_scope_decisions ?? [],
|
|
95
131
|
in_scope_invariants: (input.in_scope_invariants ?? []).map(renderInvariantId),
|
|
132
|
+
mission_id: missionId,
|
|
133
|
+
phase_id: phaseId,
|
|
96
134
|
};
|
|
97
135
|
}
|
|
98
136
|
export const taskCreateTool = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-create.js","sourceRoot":"","sources":["../../../src/mcp/tools/task-create.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"task-create.js","sourceRoot":"","sources":["../../../src/mcp/tools/task-create.ts"],"names":[],"mappings":"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"}
|