codecartographer-pi 0.14.1 → 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.
Files changed (33) hide show
  1. package/.codecarto/BACKLOG.md +4 -12
  2. package/.codecarto/GUIDE.md +35 -36
  3. package/.codecarto/NEW_THREAD_BLURB.md +3 -3
  4. package/.codecarto/skills/spec-delta-application/SKILL.md +2 -2
  5. package/.codecarto/templates/amendment.yaml +17 -0
  6. package/.codecarto/templates/conventions-template.md +6 -5
  7. package/.codecarto/templates/decisions-template.md +13 -10
  8. package/.codecarto/templates/phase-handoff.yaml +7 -0
  9. package/.codecarto/templates/spike-report.md +51 -0
  10. package/.codecarto/workflow/scaffold-version.yaml +1 -1
  11. package/agent-skill/codecartographer/SKILL.md +4 -1
  12. package/agent-skill/codecartographer/references/handoff-contract.md +12 -1
  13. package/agent-skill/codecartographer/references/orchestration.md +45 -0
  14. package/dist/core/amendment.d.ts +41 -0
  15. package/dist/core/amendment.js +140 -0
  16. package/dist/core/completion.d.ts +16 -0
  17. package/dist/core/completion.js +175 -3
  18. package/dist/core/index.d.ts +1 -0
  19. package/dist/core/index.js +1 -0
  20. package/dist/core/pipeline.js +18 -0
  21. package/dist/core/prompts.js +53 -0
  22. package/dist/core/status.d.ts +8 -1
  23. package/dist/core/status.js +27 -1
  24. package/dist/core/types.d.ts +24 -0
  25. package/dist/core/usage.d.ts +7 -0
  26. package/dist/core/usage.js +2 -1
  27. package/dist/core/workspace.d.ts +39 -0
  28. package/dist/core/workspace.js +92 -4
  29. package/dist/extensions/codecarto/auto-runner.js +1 -0
  30. package/dist/extensions/codecarto/index.js +4 -1
  31. package/dist/mcp-server/server.d.ts +23 -0
  32. package/dist/mcp-server/server.js +116 -5
  33. package/package.json +1 -1
@@ -4,6 +4,45 @@ export declare const packageRoot: string;
4
4
  export declare const packagedWorkspaceDir: string;
5
5
  export declare const PACKAGE_VERSION: string;
6
6
  export declare function getWorkspaceState(cwd: string): Promise<WorkspaceState | null>;
7
+ /** The orchestrator-maintained files init seeds and completion appends to. */
8
+ export declare const ORCHESTRATOR_FILES: readonly [{
9
+ readonly file: "CONVENTIONS.md";
10
+ readonly template: "conventions-template.md";
11
+ }, {
12
+ readonly file: "DECISIONS.md";
13
+ readonly template: "decisions-template.md";
14
+ }];
15
+ /**
16
+ * Seed the orchestrator-maintained files from the workspace's templates
17
+ * (issue #98): orchestration is on by default, so a fresh workspace starts
18
+ * with both skeletons instead of gating them behind a role ritual. Idempotent
19
+ * — existing files are never touched, and a scaffold without the templates
20
+ * (pre-template era) is left for completion's minimal-header fallback.
21
+ * @returns the file names created, for the caller's report.
22
+ */
23
+ export declare function seedOrchestratorFiles(workspaceDir: string): Promise<string[]>;
24
+ /** One scaffold refresh's outcome. */
25
+ export type RefreshScaffoldResult = {
26
+ /** Workspace-relative paths written, sorted. */
27
+ written: string[];
28
+ /** The workspace's scaffold version before the refresh, if any. */
29
+ scaffoldVersionBefore?: string;
30
+ /** The running framework version the scaffold now matches. */
31
+ scaffoldVersionAfter: string;
32
+ };
33
+ /**
34
+ * Refresh a workspace's framework-owned files from the packaged template
35
+ * (issue #102): both staleness notices instruct exactly this, and the only
36
+ * tool that previously touched scaffold files was init's force mode, which
37
+ * backs up the entire workspace. Copies every file the packaged template
38
+ * ships except project state (`workflow/status.yaml`), user configuration
39
+ * (`workflow/config.yaml`, usage log), user-owned top-level files
40
+ * (BACKLOG, THREAD_LOG, CONVENTIONS, DECISIONS), and session-written
41
+ * directories (`scratch/`, `inputs/`, `closeouts/`). Files the template no
42
+ * longer ships are left in place. Appends one THREAD_LOG entry naming the
43
+ * version transition.
44
+ */
45
+ export declare function refreshScaffold(cwd: string): Promise<RefreshScaffoldResult>;
7
46
  /**
8
47
  * Human-readable staleness notice for the workspace's .codecarto/ scaffold,
9
48
  * or null when the scaffold matches the running framework. A missing marker
@@ -3,7 +3,7 @@
3
3
  // + normalizes the per-project workspace state from disk, and provides the
4
4
  // atomic status-update primitive used by /codecarto-complete.
5
5
  import { existsSync, readFileSync } from "node:fs";
6
- import { appendFile, readFile, rename, writeFile } from "node:fs/promises";
6
+ import { appendFile, copyFile, mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
7
7
  import { basename, dirname, join, relative } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import { acquireLock, applyHandoff, createEmptyStatus, normalizeStatus, parseHandoff } from "./status.js";
@@ -98,6 +98,94 @@ export async function getWorkspaceState(cwd) {
98
98
  ...(scaffoldVersion !== undefined && { scaffoldVersion }),
99
99
  };
100
100
  }
101
+ /** The orchestrator-maintained files init seeds and completion appends to. */
102
+ export const ORCHESTRATOR_FILES = [
103
+ { file: "CONVENTIONS.md", template: "conventions-template.md" },
104
+ { file: "DECISIONS.md", template: "decisions-template.md" },
105
+ ];
106
+ /**
107
+ * Seed the orchestrator-maintained files from the workspace's templates
108
+ * (issue #98): orchestration is on by default, so a fresh workspace starts
109
+ * with both skeletons instead of gating them behind a role ritual. Idempotent
110
+ * — existing files are never touched, and a scaffold without the templates
111
+ * (pre-template era) is left for completion's minimal-header fallback.
112
+ * @returns the file names created, for the caller's report.
113
+ */
114
+ export async function seedOrchestratorFiles(workspaceDir) {
115
+ const created = [];
116
+ for (const { file, template } of ORCHESTRATOR_FILES) {
117
+ const target = join(workspaceDir, file);
118
+ if (await pathExists(target))
119
+ continue;
120
+ const templatePath = join(workspaceDir, "templates", template);
121
+ if (!(await pathExists(templatePath)))
122
+ continue;
123
+ await copyFile(templatePath, target);
124
+ created.push(file);
125
+ }
126
+ return created;
127
+ }
128
+ /**
129
+ * Workspace paths refresh never touches: project state, user configuration,
130
+ * user-owned top-level files, and the directories sessions write into.
131
+ * Everything else present in the packaged template is framework-owned.
132
+ */
133
+ const REFRESH_EXCLUDED_TOP_LEVEL = new Set(["BACKLOG.md", "THREAD_LOG.md", "CONVENTIONS.md", "DECISIONS.md"]);
134
+ const REFRESH_EXCLUDED_DIRS = new Set(["scratch", "inputs", "closeouts"]);
135
+ const REFRESH_EXCLUDED_WORKFLOW_FILES = new Set(["status.yaml", "config.yaml", ".usage.local.yaml"]);
136
+ async function listTemplateFiles(dir, relativeDir = "") {
137
+ const entries = await readdir(dir, { withFileTypes: true });
138
+ const files = [];
139
+ for (const entry of entries) {
140
+ const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
141
+ if (entry.isDirectory()) {
142
+ if (!relativeDir && REFRESH_EXCLUDED_DIRS.has(entry.name))
143
+ continue;
144
+ files.push(...await listTemplateFiles(join(dir, entry.name), relativePath));
145
+ continue;
146
+ }
147
+ if (!relativeDir && REFRESH_EXCLUDED_TOP_LEVEL.has(entry.name))
148
+ continue;
149
+ if (relativeDir === "workflow" && REFRESH_EXCLUDED_WORKFLOW_FILES.has(entry.name))
150
+ continue;
151
+ files.push(relativePath);
152
+ }
153
+ return files;
154
+ }
155
+ /**
156
+ * Refresh a workspace's framework-owned files from the packaged template
157
+ * (issue #102): both staleness notices instruct exactly this, and the only
158
+ * tool that previously touched scaffold files was init's force mode, which
159
+ * backs up the entire workspace. Copies every file the packaged template
160
+ * ships except project state (`workflow/status.yaml`), user configuration
161
+ * (`workflow/config.yaml`, usage log), user-owned top-level files
162
+ * (BACKLOG, THREAD_LOG, CONVENTIONS, DECISIONS), and session-written
163
+ * directories (`scratch/`, `inputs/`, `closeouts/`). Files the template no
164
+ * longer ships are left in place. Appends one THREAD_LOG entry naming the
165
+ * version transition.
166
+ */
167
+ export async function refreshScaffold(cwd) {
168
+ const state = await getWorkspaceState(cwd);
169
+ if (!state)
170
+ throw new Error("CodeCartographer workspace not found. Run /codecarto-init first.");
171
+ if (!existsSync(packagedWorkspaceDir)) {
172
+ throw new Error("Packaged .codecarto template is missing. Reinstall codecartographer-pi.");
173
+ }
174
+ const scaffoldVersionBefore = state.scaffoldVersion;
175
+ const files = (await listTemplateFiles(packagedWorkspaceDir)).sort();
176
+ for (const relativePath of files) {
177
+ const target = join(state.workspaceDir, relativePath);
178
+ await mkdir(dirname(target), { recursive: true });
179
+ await copyFile(join(packagedWorkspaceDir, relativePath), target);
180
+ }
181
+ const entry = `- ${new Date().toISOString().slice(0, 10)} — scaffold-refresh — Refreshed ${files.length} framework-owned file(s) from the packaged template (${scaffoldVersionBefore ?? "unversioned"} → ${PACKAGE_VERSION}); project state, user config, and session outputs untouched.`;
182
+ await appendFile(join(state.workspaceDir, "THREAD_LOG.md"), `${entry}\n`, "utf8");
183
+ return {
184
+ written: files,
185
+ ...(scaffoldVersionBefore !== undefined && { scaffoldVersionBefore }),
186
+ scaffoldVersionAfter: PACKAGE_VERSION,
187
+ };
188
+ }
101
189
  // Numeric x.y.z comparison; null when either side is not a plain dotted triple.
102
190
  function compareDottedVersions(a, b) {
103
191
  const parse = (version) => {
@@ -125,7 +213,7 @@ function compareDottedVersions(a, b) {
125
213
  export function describeScaffoldStaleness(state) {
126
214
  const scaffold = state.scaffoldVersion;
127
215
  if (!scaffold) {
128
- return "This workspace's .codecarto/ scaffold has no workflow/scaffold-version.yaml marker (introduced after v0.12.11), so its framework-owned files (GUIDE.md, templates/, workflow/ pipelines and VALIDATE.md) may predate the v0.12.0 handoff contract. Refresh them from the packaged CodeCartographer template.";
216
+ return "This workspace's .codecarto/ scaffold has no workflow/scaffold-version.yaml marker (introduced after v0.12.11), so its framework-owned files (GUIDE.md, templates/, workflow/ pipelines and VALIDATE.md) may predate the v0.12.0 handoff contract. Refresh them from the packaged CodeCartographer template (codecarto_refresh_scaffold does exactly this without touching project state).";
129
217
  }
130
218
  const comparison = compareDottedVersions(scaffold, PACKAGE_VERSION);
131
219
  if (comparison === 0)
@@ -133,10 +221,10 @@ export function describeScaffoldStaleness(state) {
133
221
  if (comparison === null) {
134
222
  return scaffold === PACKAGE_VERSION
135
223
  ? null
136
- : `This workspace's scaffold version (${scaffold}) does not match the running framework (${PACKAGE_VERSION}). Refresh the framework-owned files (GUIDE.md, templates/, workflow/) from the packaged template.`;
224
+ : `This workspace's scaffold version (${scaffold}) does not match the running framework (${PACKAGE_VERSION}). Refresh the framework-owned files (GUIDE.md, templates/, workflow/) from the packaged template — codecarto_refresh_scaffold does exactly this without touching project state.`;
137
225
  }
138
226
  if (comparison < 0) {
139
- return `This workspace's scaffold (v${scaffold}) is older than the running framework (v${PACKAGE_VERSION}). Refresh the framework-owned files (GUIDE.md, templates/, workflow/) from the packaged template to pick up pipeline and template fixes.`;
227
+ return `This workspace's scaffold (v${scaffold}) is older than the running framework (v${PACKAGE_VERSION}). Refresh the framework-owned files (GUIDE.md, templates/, workflow/) from the packaged template to pick up pipeline and template fixes — codecarto_refresh_scaffold does exactly this without touching project state.`;
140
228
  }
141
229
  return `This workspace's scaffold (v${scaffold}) is newer than the running framework (v${PACKAGE_VERSION}). Upgrade CodeCartographer to at least v${scaffold}.`;
142
230
  }
@@ -357,6 +357,7 @@ async function recordUsage(workspaceDir, phaseId, status, activity, sessionFile)
357
357
  output: activity.lifetimeUsage.output,
358
358
  cache_write: activity.lifetimeUsage.cacheWrite,
359
359
  },
360
+ recorded_by: "pi-runner",
360
361
  compactions: activity.compactions,
361
362
  ...(sessionFile ? { session_file: sessionFile } : {}),
362
363
  });
@@ -8,7 +8,7 @@ import { narrateDashboard } from "./dashboard-narrator.js";
8
8
  import { writeDashboard } from "./dashboard-writer.js";
9
9
  import { parseNextFlags } from "./next-flags.js";
10
10
  import { phaseCompactionExtension } from "./phase-compaction.js";
11
- import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, describeScaffoldStaleness, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPathResolved, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, packagedWorkspaceDir, pathExists, PACKAGE_VERSION, PhasePreflightError, PIPELINE_ALIASES, publishEntry, resolvePhase, resolvePipelineChoice, runPhasePreflight, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../../core/index.js";
11
+ import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, describeScaffoldStaleness, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPathResolved, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, packagedWorkspaceDir, pathExists, PACKAGE_VERSION, PhasePreflightError, PIPELINE_ALIASES, publishEntry, resolvePhase, resolvePipelineChoice, runPhasePreflight, seedOrchestratorFiles, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../../core/index.js";
12
12
  import { initLibrary } from "../../core/library.js";
13
13
  import { resolveUserConfigPath } from "../../core/orchestrator-config.js";
14
14
  const STATUS_WIDGET_ID = "codecarto-widget";
@@ -282,6 +282,9 @@ export default function codeCartographerExtension(pi) {
282
282
  const normalizedStatus = createEmptyStatus(basename(ctx.cwd), selectedPipelinePath, pipeline);
283
283
  normalizedStatus.last_updated = new Date().toISOString();
284
284
  await writeFile(rawStatusPath, `${stringifySimpleYaml(normalizedStatus)}\n`, "utf8");
285
+ // Orchestration is on by default (issue #97/#98): seed the files its
286
+ // duties maintain so they exist from the first phase.
287
+ await seedOrchestratorFiles(targetWorkspaceDir);
285
288
  codecartoModeActive = true;
286
289
  lastFeedbackLines = [`Initialized workspace with pipeline: ${getPipelineLabel(selectedPipelinePath)}`];
287
290
  ctx.ui.notify(`Initialized CodeCartographer (${getPipelineLabel(selectedPipelinePath)})`, "info");
@@ -207,6 +207,29 @@ export declare function handleListSkills(args: {
207
207
  text: string;
208
208
  };
209
209
  }>;
210
+ export declare function handleRefreshScaffold(args: {
211
+ cwd: string;
212
+ }): Promise<{
213
+ content: {
214
+ type: "text";
215
+ text: string;
216
+ }[];
217
+ structuredContent: {
218
+ text: string;
219
+ };
220
+ }>;
221
+ export declare function handleAmend(args: {
222
+ cwd: string;
223
+ name: string;
224
+ }): Promise<{
225
+ content: {
226
+ type: "text";
227
+ text: string;
228
+ }[];
229
+ structuredContent: {
230
+ text: string;
231
+ };
232
+ }>;
210
233
  export declare function handleGuide(args: {
211
234
  topic?: string;
212
235
  }): Promise<{
@@ -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";
@@ -104,12 +106,23 @@ export async function handleInit(args) {
104
106
  const normalizedStatus = createEmptyStatus(basename(cwd), selectedPipelinePath, pipeline);
105
107
  normalizedStatus.last_updated = new Date().toISOString();
106
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);
107
112
  const label = getPipelineLabel(selectedPipelinePath);
108
- return textResult(`Initialized CodeCartographer workspace at ${targetWorkspaceDir}.\nPipeline: ${label} (${selectedPipelinePath})\nFirst phase: ${normalizedStatus.current_phase}`, {
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"), {
109
121
  workspaceDir: targetWorkspaceDir,
110
122
  pipeline: selectedPipelinePath,
111
123
  pipelineLabel: label,
112
124
  firstPhase: normalizedStatus.current_phase,
125
+ seededOrchestratorFiles: seeded,
113
126
  });
114
127
  }
115
128
  export async function handleStatus(args) {
@@ -216,6 +229,7 @@ export async function handleValidate(args) {
216
229
  rows: validation.rows,
217
230
  gaps: validation.gaps,
218
231
  errors: validation.errors,
232
+ secondaryOutputs: validation.secondaryOutputs ?? [],
219
233
  });
220
234
  }
221
235
  export async function handleComplete(args) {
@@ -227,20 +241,46 @@ export async function handleComplete(args) {
227
241
  if (validation.overall === "FAIL" || validation.overall === "MISSING") {
228
242
  throw new McpError(ErrorCode.InvalidRequest, `Cannot complete ${validation.phaseId}: validation is ${validation.overall}.\n${buildValidationSummary(validation).join("\n")}`);
229
243
  }
230
- const { updatedState, closeoutNotice } = await completeValidatedPhase(cwd, validation, "codecarto_complete").catch((error) => {
244
+ const { updatedState, closeoutNotice, orchestratorCheckpoint } = await completeValidatedPhase(cwd, validation, "codecarto_complete").catch((error) => {
231
245
  throw new McpError(ErrorCode.InvalidParams, error instanceof Error ? error.message : String(error));
232
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
+ }
233
270
  const lines = [
234
271
  `Marked ${validation.phaseId} complete (validation: ${validation.overall}).`,
235
272
  `Next phase: ${updatedState.status.current_phase}`,
236
273
  ];
237
274
  if (closeoutNotice)
238
275
  lines.push(closeoutNotice);
276
+ if (orchestratorCheckpoint)
277
+ lines.push(orchestratorCheckpoint);
239
278
  return textResult(lines.join("\n"), {
240
279
  completedPhase: validation.phaseId,
241
280
  validation: validation.overall,
242
281
  nextPhase: updatedState.status.current_phase,
243
282
  closeoutNotice,
283
+ orchestratorCheckpoint,
244
284
  });
245
285
  }
246
286
  export async function handleSkill(args) {
@@ -619,18 +659,22 @@ export async function handleUsage(args) {
619
659
  }
620
660
  const totals = computeTotals(usage);
621
661
  const perPhase = computePerPhaseTotals(usage);
662
+ const receiptRuns = usage.runs.filter((run) => run.recorded_by === "mcp-complete").length;
622
663
  const lines = [
623
664
  `Total runs: ${totals.runs}`,
624
665
  `Total tokens: ${totals.tokens.input} in / ${totals.tokens.output} out / ${totals.tokens.cache_write} cache-write`,
625
666
  `Total duration: ${totals.duration_ms}ms / ${totals.tool_uses} tool uses`,
626
- "",
627
- "Per-phase totals:",
628
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:");
629
672
  for (const [phaseId, t] of perPhase) {
630
673
  lines.push(` ${phaseId}: ${t.runs} run(s), ${t.tokens.input + t.tokens.output} tokens, ${t.tool_uses} tool uses, ${t.duration_ms}ms`);
631
674
  }
632
675
  return textResult(lines.join("\n"), {
633
676
  runs: totals.runs,
677
+ receiptRuns,
634
678
  tokens: totals.tokens,
635
679
  toolUses: totals.tool_uses,
636
680
  durationMs: totals.duration_ms,
@@ -652,6 +696,50 @@ export async function handleListSkills(args) {
652
696
  : ["No skills installed."];
653
697
  return textResult(lines.join("\n"), { skills });
654
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
+ }
655
743
  // ---------- tool registry ----------
656
744
  const TOOLS = [
657
745
  {
@@ -902,9 +990,32 @@ const TOOLS = [
902
990
  required: ["cwd"],
903
991
  },
904
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
+ },
905
1014
  ];
906
1015
  const HANDLERS = {
1016
+ codecarto_amend: handleAmend,
907
1017
  codecarto_init: handleInit,
1018
+ codecarto_refresh_scaffold: handleRefreshScaffold,
908
1019
  codecarto_status: handleStatus,
909
1020
  codecarto_switch_pipeline: handleSwitchPipeline,
910
1021
  codecarto_next: handleNext,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codecartographer-pi",
3
- "version": "0.14.1",
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",