codecartographer-pi 0.14.0 → 0.15.0
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/.codecarto/BACKLOG.md +4 -12
- package/.codecarto/GUIDE.md +35 -36
- package/.codecarto/NEW_THREAD_BLURB.md +3 -3
- package/.codecarto/skills/spec-delta-application/SKILL.md +2 -2
- package/.codecarto/templates/amendment.yaml +17 -0
- package/.codecarto/templates/conventions-template.md +6 -5
- package/.codecarto/templates/decisions-template.md +13 -10
- package/.codecarto/templates/phase-handoff.yaml +7 -0
- package/.codecarto/templates/spike-report.md +51 -0
- package/.codecarto/workflow/scaffold-version.yaml +1 -1
- package/agent-skill/codecartographer/SKILL.md +4 -1
- package/agent-skill/codecartographer/references/handoff-contract.md +12 -1
- package/agent-skill/codecartographer/references/orchestration.md +45 -0
- package/dist/core/amendment.d.ts +41 -0
- package/dist/core/amendment.js +140 -0
- package/dist/core/completion.d.ts +16 -0
- package/dist/core/completion.js +175 -3
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/pipeline.js +18 -0
- package/dist/core/prompts.js +53 -0
- package/dist/core/status.d.ts +8 -1
- package/dist/core/status.js +27 -1
- package/dist/core/types.d.ts +24 -0
- package/dist/core/usage.d.ts +7 -0
- package/dist/core/usage.js +2 -1
- package/dist/core/workspace.d.ts +39 -0
- package/dist/core/workspace.js +92 -4
- package/dist/extensions/codecarto/auto-runner.js +1 -0
- package/dist/extensions/codecarto/index.js +4 -1
- package/dist/mcp-server/server.d.ts +118 -57
- package/dist/mcp-server/server.js +124 -9
- package/package.json +1 -1
|
@@ -15,7 +15,9 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
15
15
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
16
16
|
import { cp, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
17
17
|
import { basename, isAbsolute, join } from "node:path";
|
|
18
|
-
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, completeValidatedPhase, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, describeScaffoldStaleness, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isValidSlug, isWithinPathResolved, listEntries, listGuideTopics, readGuide, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, PACKAGE_VERSION, packagedWorkspaceDir, pathExists, PhasePreflightError, publishEntry, reindex as libraryReindex, resolvePhase, resolvePipelineChoice, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../core/index.js";
|
|
18
|
+
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, completeValidatedPhase, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, describeScaffoldStaleness, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isValidSlug, isWithinPathResolved, listEntries, listGuideTopics, readGuide, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, PACKAGE_VERSION, packagedWorkspaceDir, pathExists, PhasePreflightError, publishEntry, reindex as libraryReindex, refreshScaffold, resolvePhase, resolvePipelineChoice, seedOrchestratorFiles, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../core/index.js";
|
|
19
|
+
import { applyAmendment } from "../core/amendment.js";
|
|
20
|
+
import { appendUsageRun } from "../core/usage.js";
|
|
19
21
|
import { initLibrary } from "../core/library.js";
|
|
20
22
|
import { loadUserConfig, resolveUserConfigPath } from "../core/orchestrator-config.js";
|
|
21
23
|
import { writeDashboard } from "../extensions/codecarto/dashboard-writer.js";
|
|
@@ -41,13 +43,17 @@ async function requireWorkspace(cwd) {
|
|
|
41
43
|
}
|
|
42
44
|
return state;
|
|
43
45
|
}
|
|
46
|
+
// MCP clients may read structuredContent in preference to content when both are
|
|
47
|
+
// present. The tools whose payload IS prose — the phase prompts, the guide —
|
|
48
|
+
// therefore have to expose it structurally too, or such a client receives labels
|
|
49
|
+
// and no payload: codecarto_next returned no phase prompt at all (issue #94).
|
|
50
|
+
// Carrying the rendered text under a stable key keeps both client styles whole,
|
|
51
|
+
// and no tool declares an outputSchema that this could violate.
|
|
44
52
|
function textResult(text, structured) {
|
|
45
|
-
|
|
53
|
+
return {
|
|
46
54
|
content: [{ type: "text", text }],
|
|
55
|
+
structuredContent: { ...structured, text },
|
|
47
56
|
};
|
|
48
|
-
if (structured)
|
|
49
|
-
result.structuredContent = structured;
|
|
50
|
-
return result;
|
|
51
57
|
}
|
|
52
58
|
async function buildMcpPhasePrompt(state, phase, forced) {
|
|
53
59
|
try {
|
|
@@ -100,12 +106,23 @@ export async function handleInit(args) {
|
|
|
100
106
|
const normalizedStatus = createEmptyStatus(basename(cwd), selectedPipelinePath, pipeline);
|
|
101
107
|
normalizedStatus.last_updated = new Date().toISOString();
|
|
102
108
|
await writeFile(statusPath, `${stringifySimpleYaml(normalizedStatus)}\n`, "utf8");
|
|
109
|
+
// Orchestration is on by default (issue #97/#98): the driving chat holds the
|
|
110
|
+
// duties, so the files those duties maintain exist from the first phase.
|
|
111
|
+
const seeded = await seedOrchestratorFiles(targetWorkspaceDir);
|
|
103
112
|
const label = getPipelineLabel(selectedPipelinePath);
|
|
104
|
-
|
|
113
|
+
const initLines = [
|
|
114
|
+
`Initialized CodeCartographer workspace at ${targetWorkspaceDir}.`,
|
|
115
|
+
`Pipeline: ${label} (${selectedPipelinePath})`,
|
|
116
|
+
`First phase: ${normalizedStatus.current_phase}`,
|
|
117
|
+
];
|
|
118
|
+
if (seeded.length > 0)
|
|
119
|
+
initLines.push(`Seeded orchestrator files: ${seeded.join(", ")} (the driving chat holds the orchestrator duties — see GUIDE.md §Roles).`);
|
|
120
|
+
return textResult(initLines.join("\n"), {
|
|
105
121
|
workspaceDir: targetWorkspaceDir,
|
|
106
122
|
pipeline: selectedPipelinePath,
|
|
107
123
|
pipelineLabel: label,
|
|
108
124
|
firstPhase: normalizedStatus.current_phase,
|
|
125
|
+
seededOrchestratorFiles: seeded,
|
|
109
126
|
});
|
|
110
127
|
}
|
|
111
128
|
export async function handleStatus(args) {
|
|
@@ -212,6 +229,7 @@ export async function handleValidate(args) {
|
|
|
212
229
|
rows: validation.rows,
|
|
213
230
|
gaps: validation.gaps,
|
|
214
231
|
errors: validation.errors,
|
|
232
|
+
secondaryOutputs: validation.secondaryOutputs ?? [],
|
|
215
233
|
});
|
|
216
234
|
}
|
|
217
235
|
export async function handleComplete(args) {
|
|
@@ -223,20 +241,46 @@ export async function handleComplete(args) {
|
|
|
223
241
|
if (validation.overall === "FAIL" || validation.overall === "MISSING") {
|
|
224
242
|
throw new McpError(ErrorCode.InvalidRequest, `Cannot complete ${validation.phaseId}: validation is ${validation.overall}.\n${buildValidationSummary(validation).join("\n")}`);
|
|
225
243
|
}
|
|
226
|
-
const { updatedState, closeoutNotice } = await completeValidatedPhase(cwd, validation, "codecarto_complete").catch((error) => {
|
|
244
|
+
const { updatedState, closeoutNotice, orchestratorCheckpoint } = await completeValidatedPhase(cwd, validation, "codecarto_complete").catch((error) => {
|
|
227
245
|
throw new McpError(ErrorCode.InvalidParams, error instanceof Error ? error.message : String(error));
|
|
228
246
|
});
|
|
247
|
+
// Record the run in the usage log (issue #100). MCP hosts execute phases in
|
|
248
|
+
// their own context, so tokens and activity are unknowable here — this is a
|
|
249
|
+
// completion receipt, marked recorded_by so totals can tell it apart from
|
|
250
|
+
// the Pi runner's token-bearing entries. Recording lives in this handler,
|
|
251
|
+
// not core completion, because Pi-driven runs already record with real
|
|
252
|
+
// telemetry and must not double-count.
|
|
253
|
+
try {
|
|
254
|
+
await appendUsageRun(updatedState.workspaceDir, {
|
|
255
|
+
timestamp: new Date().toISOString(),
|
|
256
|
+
phase: validation.phaseId,
|
|
257
|
+
status: "completed",
|
|
258
|
+
turn_count: 0,
|
|
259
|
+
tool_uses: 0,
|
|
260
|
+
duration_ms: 0,
|
|
261
|
+
tokens: { input: 0, output: 0, cache_write: 0 },
|
|
262
|
+
recorded_by: "mcp-complete",
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Usage is best-effort telemetry: the phase completed and canonical
|
|
267
|
+
// state is already written, so a usage-log write failure must not fail
|
|
268
|
+
// the completion result. Nothing else can act on the error here.
|
|
269
|
+
}
|
|
229
270
|
const lines = [
|
|
230
271
|
`Marked ${validation.phaseId} complete (validation: ${validation.overall}).`,
|
|
231
272
|
`Next phase: ${updatedState.status.current_phase}`,
|
|
232
273
|
];
|
|
233
274
|
if (closeoutNotice)
|
|
234
275
|
lines.push(closeoutNotice);
|
|
276
|
+
if (orchestratorCheckpoint)
|
|
277
|
+
lines.push(orchestratorCheckpoint);
|
|
235
278
|
return textResult(lines.join("\n"), {
|
|
236
279
|
completedPhase: validation.phaseId,
|
|
237
280
|
validation: validation.overall,
|
|
238
281
|
nextPhase: updatedState.status.current_phase,
|
|
239
282
|
closeoutNotice,
|
|
283
|
+
orchestratorCheckpoint,
|
|
240
284
|
});
|
|
241
285
|
}
|
|
242
286
|
export async function handleSkill(args) {
|
|
@@ -615,18 +659,22 @@ export async function handleUsage(args) {
|
|
|
615
659
|
}
|
|
616
660
|
const totals = computeTotals(usage);
|
|
617
661
|
const perPhase = computePerPhaseTotals(usage);
|
|
662
|
+
const receiptRuns = usage.runs.filter((run) => run.recorded_by === "mcp-complete").length;
|
|
618
663
|
const lines = [
|
|
619
664
|
`Total runs: ${totals.runs}`,
|
|
620
665
|
`Total tokens: ${totals.tokens.input} in / ${totals.tokens.output} out / ${totals.tokens.cache_write} cache-write`,
|
|
621
666
|
`Total duration: ${totals.duration_ms}ms / ${totals.tool_uses} tool uses`,
|
|
622
|
-
"",
|
|
623
|
-
"Per-phase totals:",
|
|
624
667
|
];
|
|
668
|
+
if (receiptRuns > 0) {
|
|
669
|
+
lines.push(`Note: ${receiptRuns} run(s) recorded via codecarto_complete carry no token or activity data (MCP hosts execute phases in their own context) — zeros above are unknowns, not free runs.`);
|
|
670
|
+
}
|
|
671
|
+
lines.push("", "Per-phase totals:");
|
|
625
672
|
for (const [phaseId, t] of perPhase) {
|
|
626
673
|
lines.push(` ${phaseId}: ${t.runs} run(s), ${t.tokens.input + t.tokens.output} tokens, ${t.tool_uses} tool uses, ${t.duration_ms}ms`);
|
|
627
674
|
}
|
|
628
675
|
return textResult(lines.join("\n"), {
|
|
629
676
|
runs: totals.runs,
|
|
677
|
+
receiptRuns,
|
|
630
678
|
tokens: totals.tokens,
|
|
631
679
|
toolUses: totals.tool_uses,
|
|
632
680
|
durationMs: totals.duration_ms,
|
|
@@ -648,6 +696,50 @@ export async function handleListSkills(args) {
|
|
|
648
696
|
: ["No skills installed."];
|
|
649
697
|
return textResult(lines.join("\n"), { skills });
|
|
650
698
|
}
|
|
699
|
+
export async function handleRefreshScaffold(args) {
|
|
700
|
+
const cwd = await validateCwd(args.cwd);
|
|
701
|
+
await requireWorkspace(cwd);
|
|
702
|
+
const result = await refreshScaffold(cwd).catch((error) => {
|
|
703
|
+
throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
|
|
704
|
+
});
|
|
705
|
+
const shown = result.written.slice(0, 20);
|
|
706
|
+
const lines = [
|
|
707
|
+
`Refreshed ${result.written.length} framework-owned file(s) from the packaged template (${result.scaffoldVersionBefore ?? "unversioned"} → ${result.scaffoldVersionAfter}).`,
|
|
708
|
+
"Project state, user config, findings outputs, scratch, closeouts, and orchestrator files were not touched.",
|
|
709
|
+
...shown.map((path) => ` - .codecarto/${path}`),
|
|
710
|
+
];
|
|
711
|
+
if (result.written.length > shown.length)
|
|
712
|
+
lines.push(` … +${result.written.length - shown.length} more`);
|
|
713
|
+
return textResult(lines.join("\n"), {
|
|
714
|
+
written: result.written,
|
|
715
|
+
scaffoldVersionBefore: result.scaffoldVersionBefore,
|
|
716
|
+
scaffoldVersionAfter: result.scaffoldVersionAfter,
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
export async function handleAmend(args) {
|
|
720
|
+
if (typeof args.name !== "string" || !args.name.trim()) {
|
|
721
|
+
throw new McpError(ErrorCode.InvalidParams, "name is required (the amendment file's slug under .codecarto/scratch/amendments/)");
|
|
722
|
+
}
|
|
723
|
+
const cwd = await validateCwd(args.cwd);
|
|
724
|
+
await requireWorkspace(cwd);
|
|
725
|
+
const { applied, closeoutNotice } = await applyAmendment(cwd, args.name).catch((error) => {
|
|
726
|
+
throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
|
|
727
|
+
});
|
|
728
|
+
const lines = [
|
|
729
|
+
`Amendment applied.`,
|
|
730
|
+
`Open questions closed: ${applied.openQuestionsClosed.length > 0 ? applied.openQuestionsClosed.join(", ") : "none"}`,
|
|
731
|
+
`Post-pipeline items closed: ${applied.postPipelineClosed.length > 0 ? applied.postPipelineClosed.join(", ") : "none"}`,
|
|
732
|
+
];
|
|
733
|
+
if (applied.unknownIds.length > 0)
|
|
734
|
+
lines.push(`Ids that matched nothing (already closed or unknown): ${applied.unknownIds.join(", ")}`);
|
|
735
|
+
lines.push(closeoutNotice);
|
|
736
|
+
return textResult(lines.join("\n"), {
|
|
737
|
+
openQuestionsClosed: applied.openQuestionsClosed,
|
|
738
|
+
postPipelineClosed: applied.postPipelineClosed,
|
|
739
|
+
unknownIds: applied.unknownIds,
|
|
740
|
+
closeoutNotice,
|
|
741
|
+
});
|
|
742
|
+
}
|
|
651
743
|
// ---------- tool registry ----------
|
|
652
744
|
const TOOLS = [
|
|
653
745
|
{
|
|
@@ -898,9 +990,32 @@ const TOOLS = [
|
|
|
898
990
|
required: ["cwd"],
|
|
899
991
|
},
|
|
900
992
|
},
|
|
993
|
+
{
|
|
994
|
+
name: "codecarto_amend",
|
|
995
|
+
description: "Apply a post-pipeline amendment from .codecarto/scratch/amendments/<name>.yaml to workflow/status.yaml: close open questions resolved on evidence after the pipeline completed and retire finished post-pipeline backlog items, under the same lock completion uses. Writes an amendment closeout and THREAD_LOG entry. Refused while the pipeline is incomplete — mid-pipeline resolutions belong in the phase handoff. Idempotent: ids that no longer match are reported, not fatal.",
|
|
996
|
+
inputSchema: {
|
|
997
|
+
type: "object",
|
|
998
|
+
properties: {
|
|
999
|
+
cwd: { type: "string", description: "Absolute path to the target repository." },
|
|
1000
|
+
name: { type: "string", description: "Amendment file slug under .codecarto/scratch/amendments/ (with or without .yaml)." },
|
|
1001
|
+
},
|
|
1002
|
+
required: ["cwd", "name"],
|
|
1003
|
+
},
|
|
1004
|
+
},
|
|
1005
|
+
{
|
|
1006
|
+
name: "codecarto_refresh_scaffold",
|
|
1007
|
+
description: "Refresh a workspace's framework-owned files (GUIDE.md, templates/, workflow pipelines and VALIDATE.md, skills/, findings SKILL and README stubs) from the packaged template — the action every scaffold-staleness warning instructs. Never touches project state (status.yaml), user config (workflow/config.yaml, usage log), findings outputs, scratch/, closeouts/, or the orchestrator files (CONVENTIONS.md, DECISIONS.md, BACKLOG.md, THREAD_LOG.md). Appends one THREAD_LOG entry naming the version transition. Unlike codecarto_init force:true, nothing is backed up or lost.",
|
|
1008
|
+
inputSchema: {
|
|
1009
|
+
type: "object",
|
|
1010
|
+
properties: { cwd: { type: "string", description: "Absolute path to the target repository." } },
|
|
1011
|
+
required: ["cwd"],
|
|
1012
|
+
},
|
|
1013
|
+
},
|
|
901
1014
|
];
|
|
902
1015
|
const HANDLERS = {
|
|
1016
|
+
codecarto_amend: handleAmend,
|
|
903
1017
|
codecarto_init: handleInit,
|
|
1018
|
+
codecarto_refresh_scaffold: handleRefreshScaffold,
|
|
904
1019
|
codecarto_status: handleStatus,
|
|
905
1020
|
codecarto_switch_pipeline: handleSwitchPipeline,
|
|
906
1021
|
codecarto_next: handleNext,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codecartographer-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"mcpName": "io.github.HuginnIndustries/codecartographer",
|
|
5
5
|
"description": "Turn an unfamiliar codebase into a validated reimplementation spec, then synthesize confirmed specs and a product vision into a traceable plan.",
|
|
6
6
|
"type": "module",
|