codecartographer-pi 0.1.0 → 0.1.2
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/README.md +8 -2
- package/{core/index.ts → dist/core/index.d.ts} +0 -4
- package/dist/core/index.js +10 -0
- package/dist/core/pipeline.d.ts +10 -0
- package/dist/core/pipeline.js +160 -0
- package/dist/core/prompts.d.ts +9 -0
- package/dist/core/prompts.js +166 -0
- package/dist/core/status.d.ts +12 -0
- package/dist/core/status.js +143 -0
- package/dist/core/types.d.ts +77 -0
- package/dist/core/types.js +10 -0
- package/dist/core/utils.d.ts +8 -0
- package/dist/core/utils.js +46 -0
- package/dist/core/workspace.d.ts +10 -0
- package/dist/core/workspace.js +83 -0
- package/dist/core/yaml.d.ts +6 -0
- package/dist/core/yaml.js +259 -0
- package/dist/extensions/codecarto/index.d.ts +2 -0
- package/dist/extensions/codecarto/index.js +372 -0
- package/dist/mcp-server/bin.d.mts +2 -0
- package/dist/mcp-server/bin.mjs +9 -0
- package/dist/mcp-server/server.d.ts +105 -0
- package/dist/mcp-server/server.js +392 -0
- package/package.json +14 -10
- package/core/pipeline.ts +0 -175
- package/core/prompts.ts +0 -183
- package/core/status.ts +0 -155
- package/core/types.ts +0 -96
- package/core/utils.ts +0 -52
- package/core/workspace.ts +0 -81
- package/core/yaml.ts +0 -256
- package/extensions/codecarto/index.ts +0 -446
- package/mcp-server/bin.mjs +0 -7
- package/mcp-server/server.ts +0 -497
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { cp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, join, resolve } from "node:path";
|
|
3
|
+
import { buildPhasePrompt, buildSkillPrompt, buildThreadLogEntry, buildValidationSummary, canonicalPath, closeoutFileName, createEmptyStatus, dateOnly, DEFAULT_PIPELINE_PATH, ensureCloseoutStub, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPath, listSkillNames, loadYamlFile, normalizeForComparison, normalizeStatus, packagedWorkspaceDir, pathExists, PIPELINE_ALIASES, resolvePhase, resolvePipelineChoice, stringifySimpleYaml, uniqueStrings, updateStatusAtomically, validatePhaseOutput, } from "../../core/index.js";
|
|
4
|
+
const STATUS_WIDGET_ID = "codecarto-widget";
|
|
5
|
+
const STATUS_LINE_ID = "codecarto-status";
|
|
6
|
+
const SAFE_TOOL_NAMES = ["read", "grep", "find", "ls", "edit", "write"];
|
|
7
|
+
function buildStatusLines(state, extraLines = []) {
|
|
8
|
+
const nextPhase = getNextEligiblePhase(state);
|
|
9
|
+
const currentPhase = nextPhase?.id ?? state.status.current_phase ?? "complete";
|
|
10
|
+
const pipelineLabel = getPipelineLabel(state.status.pipeline);
|
|
11
|
+
const completedCount = state.pipeline.phase_order.filter((phaseId) => state.status.phases[phaseId]?.status === "complete").length;
|
|
12
|
+
const currentOpenQuestions = currentPhase === "complete" ? 0 : state.status.phases[currentPhase]?.open_questions.length ?? 0;
|
|
13
|
+
const totalCarryForward = Object.values(state.status.phases).reduce((sum, phase) => sum + (phase.carry_forward?.length ?? 0), 0);
|
|
14
|
+
const nextAction = state.status.next_actions[0] ?? (nextPhase ? `Next: ${nextPhase.id}` : "All phases complete.");
|
|
15
|
+
const lines = [
|
|
16
|
+
"CodeCartographer",
|
|
17
|
+
`Phase: ${currentPhase}`,
|
|
18
|
+
`Pipeline: ${pipelineLabel}`,
|
|
19
|
+
`Progress: ${completedCount}/${state.pipeline.phase_order.length} complete`,
|
|
20
|
+
`Open questions: ${currentOpenQuestions}`,
|
|
21
|
+
`Carry-forward: ${totalCarryForward}`,
|
|
22
|
+
`Next: ${nextAction}`,
|
|
23
|
+
];
|
|
24
|
+
if (extraLines.length > 0) {
|
|
25
|
+
lines.push("", ...extraLines);
|
|
26
|
+
}
|
|
27
|
+
return lines;
|
|
28
|
+
}
|
|
29
|
+
function setUiState(ctx, state, extraLines = []) {
|
|
30
|
+
if (!ctx.hasUI)
|
|
31
|
+
return;
|
|
32
|
+
if (!state) {
|
|
33
|
+
ctx.ui.setStatus(STATUS_LINE_ID, undefined);
|
|
34
|
+
ctx.ui.setWidget(STATUS_WIDGET_ID, undefined);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const theme = ctx.ui.theme;
|
|
38
|
+
const currentPhase = getNextEligiblePhase(state)?.id ?? state.status.current_phase ?? "complete";
|
|
39
|
+
ctx.ui.setStatus(STATUS_LINE_ID, `${theme.fg("accent", "CC")} ${theme.fg("dim", currentPhase)}`);
|
|
40
|
+
ctx.ui.setWidget(STATUS_WIDGET_ID, buildStatusLines(state, extraLines));
|
|
41
|
+
}
|
|
42
|
+
export default function codeCartographerExtension(pi) {
|
|
43
|
+
let lastFeedbackLines = [];
|
|
44
|
+
const readWorkspaceState = async (ctx, notifyOnError = true) => {
|
|
45
|
+
try {
|
|
46
|
+
return await getWorkspaceState(ctx.cwd);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
50
|
+
lastFeedbackLines = [message];
|
|
51
|
+
setUiState(ctx, null);
|
|
52
|
+
if (notifyOnError && ctx.hasUI)
|
|
53
|
+
ctx.ui.notify(message, "error");
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const refreshWorkspaceUi = async (ctx, extraLines) => {
|
|
58
|
+
const state = await readWorkspaceState(ctx, false);
|
|
59
|
+
setUiState(ctx, state, extraLines ?? lastFeedbackLines);
|
|
60
|
+
if (state) {
|
|
61
|
+
const phaseId = getNextEligiblePhase(state)?.id ?? state.status.current_phase;
|
|
62
|
+
if (phaseId)
|
|
63
|
+
pi.setSessionName(`CodeCartographer: ${phaseId}`);
|
|
64
|
+
}
|
|
65
|
+
return state;
|
|
66
|
+
};
|
|
67
|
+
const ensureWorkspaceState = async (ctx) => {
|
|
68
|
+
const state = await readWorkspaceState(ctx);
|
|
69
|
+
if (state)
|
|
70
|
+
return state;
|
|
71
|
+
const hasWorkspace = await pathExists(join(ctx.cwd, ".codecarto", "workflow", "status.yaml"));
|
|
72
|
+
if (!hasWorkspace)
|
|
73
|
+
ctx.ui.notify("No .codecarto/ workspace found. Run /codecarto-init first.", "warning");
|
|
74
|
+
return null;
|
|
75
|
+
};
|
|
76
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
77
|
+
const state = await refreshWorkspaceUi(ctx);
|
|
78
|
+
if (!state)
|
|
79
|
+
return;
|
|
80
|
+
pi.setActiveTools(SAFE_TOOL_NAMES);
|
|
81
|
+
});
|
|
82
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
83
|
+
await refreshWorkspaceUi(ctx);
|
|
84
|
+
});
|
|
85
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
86
|
+
const workspaceDir = join(ctx.cwd, ".codecarto");
|
|
87
|
+
if (!(await pathExists(workspaceDir)))
|
|
88
|
+
return undefined;
|
|
89
|
+
if (event.toolName === "bash") {
|
|
90
|
+
if (ctx.hasUI)
|
|
91
|
+
ctx.ui.notify("Blocked bash in CodeCartographer mode", "warning");
|
|
92
|
+
return { block: true, reason: "CodeCartographer mode disables bash to keep source analysis read-only." };
|
|
93
|
+
}
|
|
94
|
+
if (event.toolName === "edit" || event.toolName === "write") {
|
|
95
|
+
const inputPath = typeof event.input.path === "string" ? event.input.path : "";
|
|
96
|
+
const strippedPath = inputPath.startsWith("@") ? inputPath.slice(1) : inputPath;
|
|
97
|
+
const targetPath = await canonicalPath(resolve(ctx.cwd, strippedPath));
|
|
98
|
+
const allowedRoot = await canonicalPath(workspaceDir);
|
|
99
|
+
if (!isWithinPath(targetPath, allowedRoot)) {
|
|
100
|
+
if (ctx.hasUI) {
|
|
101
|
+
ctx.ui.notify(`Blocked ${event.toolName} outside .codecarto/: ${inputPath}`, "warning");
|
|
102
|
+
}
|
|
103
|
+
return { block: true, reason: `CodeCartographer mode only allows ${event.toolName} within .codecarto/` };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
});
|
|
108
|
+
pi.registerCommand("codecarto-init", {
|
|
109
|
+
description: "Initialize .codecarto/ in the current repository",
|
|
110
|
+
getArgumentCompletions: (prefix) => {
|
|
111
|
+
const items = Object.keys(PIPELINE_ALIASES)
|
|
112
|
+
.filter((value) => value.startsWith(prefix))
|
|
113
|
+
.map((value) => ({ value, label: value }));
|
|
114
|
+
return items.length > 0 ? items : null;
|
|
115
|
+
},
|
|
116
|
+
handler: async (args, ctx) => {
|
|
117
|
+
const trimmedArgs = args.trim();
|
|
118
|
+
const pipelineChoice = resolvePipelineChoice(trimmedArgs);
|
|
119
|
+
if (trimmedArgs && !pipelineChoice) {
|
|
120
|
+
ctx.ui.notify(`Unknown pipeline: ${trimmedArgs}`, "error");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const targetWorkspaceDir = join(ctx.cwd, ".codecarto");
|
|
124
|
+
const sourceWorkspaceDir = packagedWorkspaceDir;
|
|
125
|
+
if (!(await pathExists(sourceWorkspaceDir))) {
|
|
126
|
+
ctx.ui.notify("Packaged .codecarto assets are missing.", "error");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const targetExists = await pathExists(targetWorkspaceDir);
|
|
130
|
+
if (targetExists) {
|
|
131
|
+
const sameWorkspace = normalizeForComparison(await canonicalPath(targetWorkspaceDir)) === normalizeForComparison(await canonicalPath(sourceWorkspaceDir));
|
|
132
|
+
if (!sameWorkspace) {
|
|
133
|
+
const overwrite = await ctx.ui.confirm("CodeCartographer already exists", "A .codecarto/ directory already exists in this repository. Overwrite it?");
|
|
134
|
+
if (!overwrite)
|
|
135
|
+
return;
|
|
136
|
+
await rm(targetWorkspaceDir, { recursive: true, force: true });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!(await pathExists(targetWorkspaceDir))) {
|
|
140
|
+
await mkdir(ctx.cwd, { recursive: true });
|
|
141
|
+
await cp(sourceWorkspaceDir, targetWorkspaceDir, { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
const rawStatusPath = join(targetWorkspaceDir, "workflow", "status.yaml");
|
|
144
|
+
const rawStatus = (await loadYamlFile(rawStatusPath)) ?? {};
|
|
145
|
+
const selectedPipelinePath = pipelineChoice ?? rawStatus.pipeline?.trim() ?? DEFAULT_PIPELINE_PATH;
|
|
146
|
+
const resolvedPipelinePath = join(targetWorkspaceDir, selectedPipelinePath);
|
|
147
|
+
if (!(await pathExists(resolvedPipelinePath))) {
|
|
148
|
+
ctx.ui.notify(`Pipeline not found: ${selectedPipelinePath}`, "error");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const pipeline = await loadYamlFile(resolvedPipelinePath);
|
|
152
|
+
const normalizedStatus = createEmptyStatus(basename(ctx.cwd), selectedPipelinePath, pipeline);
|
|
153
|
+
normalizedStatus.last_updated = new Date().toISOString();
|
|
154
|
+
await writeFile(rawStatusPath, `${stringifySimpleYaml(normalizedStatus)}\n`, "utf8");
|
|
155
|
+
lastFeedbackLines = [`Initialized workspace with pipeline: ${getPipelineLabel(selectedPipelinePath)}`];
|
|
156
|
+
ctx.ui.notify(`Initialized CodeCartographer (${getPipelineLabel(selectedPipelinePath)})`, "info");
|
|
157
|
+
await ctx.reload();
|
|
158
|
+
return;
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
pi.registerCommand("codecarto-status", {
|
|
162
|
+
description: "Show the current CodeCartographer phase and progress",
|
|
163
|
+
handler: async (_args, ctx) => {
|
|
164
|
+
const state = await ensureWorkspaceState(ctx);
|
|
165
|
+
if (!state)
|
|
166
|
+
return;
|
|
167
|
+
const nextPhase = getNextEligiblePhase(state)?.id ?? "complete";
|
|
168
|
+
lastFeedbackLines = [`Current phase: ${nextPhase}`, `Pipeline: ${getPipelineLabel(state.status.pipeline)}`];
|
|
169
|
+
setUiState(ctx, state, lastFeedbackLines);
|
|
170
|
+
ctx.ui.notify(`CodeCartographer phase: ${nextPhase}`, "info");
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
pi.registerCommand("codecarto-next", {
|
|
174
|
+
description: "Queue the next eligible CodeCartographer phase prompt",
|
|
175
|
+
handler: async (_args, ctx) => {
|
|
176
|
+
const state = await ensureWorkspaceState(ctx);
|
|
177
|
+
if (!state)
|
|
178
|
+
return;
|
|
179
|
+
const phase = getNextEligiblePhase(state);
|
|
180
|
+
if (!phase) {
|
|
181
|
+
lastFeedbackLines = ["All phases complete."];
|
|
182
|
+
setUiState(ctx, state, lastFeedbackLines);
|
|
183
|
+
ctx.ui.notify("All CodeCartographer phases are complete.", "info");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const prompt = await buildPhasePrompt(state, phase, false);
|
|
187
|
+
if (ctx.isIdle()) {
|
|
188
|
+
pi.sendUserMessage(prompt);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
192
|
+
}
|
|
193
|
+
lastFeedbackLines = [`Queued phase prompt for ${phase.id}`];
|
|
194
|
+
setUiState(ctx, state, lastFeedbackLines);
|
|
195
|
+
ctx.ui.notify(`Queued CodeCartographer phase: ${phase.id}`, "info");
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
pi.registerCommand("codecarto-phase", {
|
|
199
|
+
description: "Queue a specific CodeCartographer phase prompt: /codecarto-phase <phase>",
|
|
200
|
+
handler: async (args, ctx) => {
|
|
201
|
+
const phaseId = args.trim();
|
|
202
|
+
if (!phaseId) {
|
|
203
|
+
ctx.ui.notify("Usage: /codecarto-phase <phase>", "warning");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const state = await ensureWorkspaceState(ctx);
|
|
207
|
+
if (!state)
|
|
208
|
+
return;
|
|
209
|
+
const phase = resolvePhase(state, phaseId);
|
|
210
|
+
if (!phase) {
|
|
211
|
+
ctx.ui.notify(`Unknown phase: ${phaseId}`, "error");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const prompt = await buildPhasePrompt(state, phase, true);
|
|
215
|
+
if (ctx.isIdle()) {
|
|
216
|
+
pi.sendUserMessage(prompt);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
220
|
+
}
|
|
221
|
+
lastFeedbackLines = [`Queued explicit phase prompt for ${phase.id}`];
|
|
222
|
+
setUiState(ctx, state, lastFeedbackLines);
|
|
223
|
+
ctx.ui.notify(`Queued CodeCartographer phase: ${phase.id}`, "info");
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
pi.registerCommand("codecarto-validate", {
|
|
227
|
+
description: "Validate a phase output: /codecarto-validate [phase]",
|
|
228
|
+
handler: async (args, ctx) => {
|
|
229
|
+
const state = await ensureWorkspaceState(ctx);
|
|
230
|
+
if (!state)
|
|
231
|
+
return;
|
|
232
|
+
const validation = await validatePhaseOutput(state, args.trim() || undefined);
|
|
233
|
+
lastFeedbackLines = buildValidationSummary(validation);
|
|
234
|
+
setUiState(ctx, state, lastFeedbackLines);
|
|
235
|
+
const level = validation.overall === "FAIL" || validation.overall === "MISSING" ? "error" : validation.overall === "PASS WITH GAPS" ? "warning" : "info";
|
|
236
|
+
ctx.ui.notify(`Validation ${validation.phaseId}: ${validation.overall}`, level);
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
pi.registerCommand("codecarto-complete", {
|
|
240
|
+
description: "Mark a phase complete after validation passes: /codecarto-complete [phase]",
|
|
241
|
+
handler: async (args, ctx) => {
|
|
242
|
+
const currentState = await ensureWorkspaceState(ctx);
|
|
243
|
+
if (!currentState)
|
|
244
|
+
return;
|
|
245
|
+
const validation = await validatePhaseOutput(currentState, args.trim() || undefined);
|
|
246
|
+
if (validation.overall === "FAIL" || validation.overall === "MISSING") {
|
|
247
|
+
lastFeedbackLines = buildValidationSummary(validation);
|
|
248
|
+
setUiState(ctx, currentState, lastFeedbackLines);
|
|
249
|
+
ctx.ui.notify(`Cannot complete ${validation.phaseId}: ${validation.overall}`, "error");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const completionTimestamp = new Date().toISOString();
|
|
253
|
+
const updatedState = await updateStatusAtomically(ctx.cwd, (lockedState) => {
|
|
254
|
+
const phase = resolvePhase(lockedState, validation.phaseId);
|
|
255
|
+
if (!phase?.primary_output) {
|
|
256
|
+
throw new Error(`Phase ${validation.phaseId} is missing primary_output.`);
|
|
257
|
+
}
|
|
258
|
+
const nextStatus = normalizeStatus(lockedState.status, lockedState.pipeline, lockedState.status.pipeline, lockedState.cwd);
|
|
259
|
+
const existingPhase = nextStatus.phases[validation.phaseId] ?? {
|
|
260
|
+
status: "pending",
|
|
261
|
+
owner_notes: [],
|
|
262
|
+
outputs_present: [],
|
|
263
|
+
open_questions: [],
|
|
264
|
+
carry_forward: [],
|
|
265
|
+
};
|
|
266
|
+
const gapEntries = validation.rows
|
|
267
|
+
.filter((row) => row.result.toUpperCase().includes("PARTIAL"))
|
|
268
|
+
.map((row) => ({
|
|
269
|
+
kind: "needs-maintainer-decision",
|
|
270
|
+
description: row.criterion || "Partial validation gap",
|
|
271
|
+
deferred_reason: row.evidence || "Marked PARTIAL by validation",
|
|
272
|
+
}));
|
|
273
|
+
const mergedOpenQuestions = [...existingPhase.open_questions];
|
|
274
|
+
for (const candidate of gapEntries) {
|
|
275
|
+
const dupe = mergedOpenQuestions.some((entry) => entry.description === candidate.description && entry.deferred_reason === candidate.deferred_reason);
|
|
276
|
+
if (!dupe)
|
|
277
|
+
mergedOpenQuestions.push(candidate);
|
|
278
|
+
}
|
|
279
|
+
nextStatus.phases[validation.phaseId] = {
|
|
280
|
+
status: "complete",
|
|
281
|
+
owner_notes: uniqueStrings([
|
|
282
|
+
...existingPhase.owner_notes,
|
|
283
|
+
`Completed via /codecarto-complete on ${completionTimestamp}.`,
|
|
284
|
+
`Primary output: .codecarto/${validation.primaryOutput}`,
|
|
285
|
+
`Validation: ${validation.overall}`,
|
|
286
|
+
]).slice(-3),
|
|
287
|
+
outputs_present: uniqueStrings([...existingPhase.outputs_present, validation.primaryOutput]),
|
|
288
|
+
open_questions: mergedOpenQuestions,
|
|
289
|
+
carry_forward: existingPhase.carry_forward ?? [],
|
|
290
|
+
};
|
|
291
|
+
nextStatus.last_updated = completionTimestamp;
|
|
292
|
+
const updatedWorkspaceState = {
|
|
293
|
+
...lockedState,
|
|
294
|
+
status: nextStatus,
|
|
295
|
+
};
|
|
296
|
+
const nextEligible = getNextEligiblePhase(updatedWorkspaceState);
|
|
297
|
+
nextStatus.current_phase = nextEligible?.id ?? "complete";
|
|
298
|
+
nextStatus.next_actions = nextEligible
|
|
299
|
+
? [
|
|
300
|
+
`Begin ${nextEligible.id} phase by producing ${nextEligible.primary_output ?? `findings/${nextEligible.id}/`}`,
|
|
301
|
+
]
|
|
302
|
+
: ["All phases complete. Review findings, open questions, and downstream implementation notes."];
|
|
303
|
+
return {
|
|
304
|
+
state: {
|
|
305
|
+
...updatedWorkspaceState,
|
|
306
|
+
status: nextStatus,
|
|
307
|
+
},
|
|
308
|
+
threadLogEntry: buildThreadLogEntry(validation.phaseId, validation, completionTimestamp),
|
|
309
|
+
};
|
|
310
|
+
});
|
|
311
|
+
let closeoutNotice;
|
|
312
|
+
try {
|
|
313
|
+
const created = await ensureCloseoutStub(updatedState.workspaceDir, validation.phaseId, completionTimestamp);
|
|
314
|
+
if (created) {
|
|
315
|
+
closeoutNotice = `Closeout stub: .codecarto/closeouts/${closeoutFileName(dateOnly(completionTimestamp), validation.phaseId)} (fill it in)`;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
320
|
+
closeoutNotice = `Closeout stub not created: ${message}`;
|
|
321
|
+
}
|
|
322
|
+
lastFeedbackLines = [
|
|
323
|
+
`Completed phase: ${validation.phaseId}`,
|
|
324
|
+
`Validation: ${validation.overall}`,
|
|
325
|
+
`Next phase: ${updatedState.status.current_phase}`,
|
|
326
|
+
];
|
|
327
|
+
if (closeoutNotice)
|
|
328
|
+
lastFeedbackLines.push(closeoutNotice);
|
|
329
|
+
setUiState(ctx, updatedState, lastFeedbackLines);
|
|
330
|
+
ctx.ui.notify(`Marked ${validation.phaseId} complete`, validation.overall === "PASS WITH GAPS" ? "warning" : "info");
|
|
331
|
+
if (closeoutNotice)
|
|
332
|
+
ctx.ui.notify(closeoutNotice, "info");
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
pi.registerCommand("codecarto-skill", {
|
|
336
|
+
description: "Run a post-pipeline skill (after all phases are complete): /codecarto-skill <name>",
|
|
337
|
+
handler: async (args, ctx) => {
|
|
338
|
+
const skillName = args.trim();
|
|
339
|
+
if (!skillName) {
|
|
340
|
+
const available = await listSkillNames(join(ctx.cwd, ".codecarto"));
|
|
341
|
+
const hint = available.length > 0 ? ` (available: ${available.join(", ")})` : "";
|
|
342
|
+
ctx.ui.notify(`Usage: /codecarto-skill <name>${hint}`, "warning");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const state = await ensureWorkspaceState(ctx);
|
|
346
|
+
if (!state)
|
|
347
|
+
return;
|
|
348
|
+
const nextPhase = getNextEligiblePhase(state);
|
|
349
|
+
if (nextPhase) {
|
|
350
|
+
ctx.ui.notify(`Cannot run skill: pipeline is not complete (next phase: ${nextPhase.id}). Finish the pipeline before running post-pipeline skills.`, "error");
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const skillFile = join(state.workspaceDir, "skills", skillName, "SKILL.md");
|
|
354
|
+
if (!(await pathExists(skillFile))) {
|
|
355
|
+
const available = await listSkillNames(state.workspaceDir);
|
|
356
|
+
const hint = available.length > 0 ? ` (available: ${available.join(", ")})` : " (no skills installed)";
|
|
357
|
+
ctx.ui.notify(`Unknown skill: ${skillName}${hint}`, "error");
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const prompt = await buildSkillPrompt(state, skillName);
|
|
361
|
+
if (ctx.isIdle()) {
|
|
362
|
+
pi.sendUserMessage(prompt);
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
366
|
+
}
|
|
367
|
+
lastFeedbackLines = [`Queued post-pipeline skill: ${skillName}`];
|
|
368
|
+
setUiState(ctx, state, lastFeedbackLines);
|
|
369
|
+
ctx.ui.notify(`Queued CodeCartographer skill: ${skillName}`, "info");
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// This file is shipped from dist/. It imports the compiled server.js next to
|
|
3
|
+
// it. Running it directly from the source tree will fail (server.js does not
|
|
4
|
+
// exist in source); run `npm run build` first, or use the published package.
|
|
5
|
+
import { startStdioServer } from "./server.js";
|
|
6
|
+
startStdioServer().catch((error) => {
|
|
7
|
+
console.error("[codecarto-mcp] failed to start:", error);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
export declare function handleInit(args: {
|
|
3
|
+
cwd: string;
|
|
4
|
+
pipeline?: string;
|
|
5
|
+
force?: boolean;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
content: Array<{
|
|
8
|
+
type: "text";
|
|
9
|
+
text: string;
|
|
10
|
+
}>;
|
|
11
|
+
structuredContent?: Record<string, unknown>;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function handleStatus(args: {
|
|
14
|
+
cwd: string;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
content: Array<{
|
|
17
|
+
type: "text";
|
|
18
|
+
text: string;
|
|
19
|
+
}>;
|
|
20
|
+
structuredContent?: Record<string, unknown>;
|
|
21
|
+
}>;
|
|
22
|
+
export declare function handleNext(args: {
|
|
23
|
+
cwd: string;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
content: Array<{
|
|
26
|
+
type: "text";
|
|
27
|
+
text: string;
|
|
28
|
+
}>;
|
|
29
|
+
structuredContent?: Record<string, unknown>;
|
|
30
|
+
}>;
|
|
31
|
+
export declare function handlePhase(args: {
|
|
32
|
+
cwd: string;
|
|
33
|
+
phase: string;
|
|
34
|
+
}): Promise<{
|
|
35
|
+
content: Array<{
|
|
36
|
+
type: "text";
|
|
37
|
+
text: string;
|
|
38
|
+
}>;
|
|
39
|
+
structuredContent?: Record<string, unknown>;
|
|
40
|
+
}>;
|
|
41
|
+
export declare function handleValidate(args: {
|
|
42
|
+
cwd: string;
|
|
43
|
+
phase?: string;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
content: Array<{
|
|
46
|
+
type: "text";
|
|
47
|
+
text: string;
|
|
48
|
+
}>;
|
|
49
|
+
structuredContent?: Record<string, unknown>;
|
|
50
|
+
}>;
|
|
51
|
+
export declare function handleComplete(args: {
|
|
52
|
+
cwd: string;
|
|
53
|
+
phase?: string;
|
|
54
|
+
}): Promise<{
|
|
55
|
+
content: Array<{
|
|
56
|
+
type: "text";
|
|
57
|
+
text: string;
|
|
58
|
+
}>;
|
|
59
|
+
structuredContent?: Record<string, unknown>;
|
|
60
|
+
}>;
|
|
61
|
+
export declare function handleSkill(args: {
|
|
62
|
+
cwd: string;
|
|
63
|
+
name: string;
|
|
64
|
+
}): Promise<{
|
|
65
|
+
content: Array<{
|
|
66
|
+
type: "text";
|
|
67
|
+
text: string;
|
|
68
|
+
}>;
|
|
69
|
+
structuredContent?: Record<string, unknown>;
|
|
70
|
+
}>;
|
|
71
|
+
export declare function buildServer(): Server<{
|
|
72
|
+
method: string;
|
|
73
|
+
params?: {
|
|
74
|
+
[x: string]: unknown;
|
|
75
|
+
_meta?: {
|
|
76
|
+
[x: string]: unknown;
|
|
77
|
+
progressToken?: string | number;
|
|
78
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
79
|
+
taskId: string;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
}, {
|
|
84
|
+
method: string;
|
|
85
|
+
params?: {
|
|
86
|
+
[x: string]: unknown;
|
|
87
|
+
_meta?: {
|
|
88
|
+
[x: string]: unknown;
|
|
89
|
+
progressToken?: string | number;
|
|
90
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
91
|
+
taskId: string;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
}, {
|
|
96
|
+
[x: string]: unknown;
|
|
97
|
+
_meta?: {
|
|
98
|
+
[x: string]: unknown;
|
|
99
|
+
progressToken?: string | number;
|
|
100
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
101
|
+
taskId: string;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
}>;
|
|
105
|
+
export declare function startStdioServer(): Promise<void>;
|