codecartographer-pi 0.20.0 → 0.21.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 (39) hide show
  1. package/.codecarto/GUIDE.md +1 -1
  2. package/.codecarto/broadside/SKILL.md +7 -3
  3. package/.codecarto/broadside/config.yaml +17 -9
  4. package/.codecarto/findings/contracts/SKILL.md +4 -1
  5. package/.codecarto/findings/defect-scan/SKILL.md +10 -0
  6. package/.codecarto/findings/defect-scan-mechanical/SKILL.md +6 -0
  7. package/.codecarto/findings/defect-scan-semantic/SKILL.md +8 -1
  8. package/.codecarto/findings/porting/SKILL.md +4 -0
  9. package/.codecarto/findings/protocols/SKILL.md +4 -0
  10. package/.codecarto/templates/mechanical-defects.md +15 -0
  11. package/.codecarto/templates/reimplementation-spec.md +5 -3
  12. package/.codecarto/templates/reverse-engineering-bundle.md +10 -1
  13. package/.codecarto/templates/semantic-defects.md +15 -0
  14. package/.codecarto/workflow/VALIDATE.md +1 -1
  15. package/.codecarto/workflow/scaffold-version.yaml +1 -1
  16. package/README.md +3 -3
  17. package/dist/core/amendment.js +9 -4
  18. package/dist/core/broadside.d.ts +49 -0
  19. package/dist/core/broadside.js +131 -30
  20. package/dist/core/completion.js +7 -1
  21. package/dist/core/library.js +5 -3
  22. package/dist/core/pipeline.d.ts +39 -3
  23. package/dist/core/pipeline.js +61 -7
  24. package/dist/core/prompts.js +10 -3
  25. package/dist/core/status.d.ts +8 -0
  26. package/dist/core/status.js +39 -17
  27. package/dist/core/utils.d.ts +7 -0
  28. package/dist/core/utils.js +7 -0
  29. package/dist/core/workspace.js +2 -2
  30. package/dist/core/yaml.js +8 -1
  31. package/dist/extensions/codecarto/auto-runner.d.ts +1 -1
  32. package/dist/extensions/codecarto/auto-runner.js +18 -3
  33. package/dist/extensions/codecarto/broadside-flags.d.ts +3 -1
  34. package/dist/extensions/codecarto/broadside-flags.js +12 -0
  35. package/dist/extensions/codecarto/index.js +95 -31
  36. package/dist/extensions/codecarto/phase-compaction.js +5 -1
  37. package/dist/mcp-server/server.d.ts +3 -1
  38. package/dist/mcp-server/server.js +99 -29
  39. package/package.json +1 -1
@@ -17,7 +17,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
17
17
  import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
18
18
  import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
19
19
  import { basename, isAbsolute, join } from "node:path";
20
- import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, BROADSIDE_DIR, broadsideDirFor, BROADSIDE_LENS_IDS, BROADSIDE_SKILL_NAME, backupWorkspaceState, canonicalPath, copyPackagedWorkspace, collectResultText, completeValidatedPhase, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, describeScaffoldStaleness, detectProvenanceConflicts, estimateSubmitText, getLens, describeDanglingCarryForward, describeMissingCompletedOutputs, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isValidSlug, isWithinPathResolved, listBatchModels, listEntries, listGuideTopics, loadBroadsideConfig, modelsText, readGuide, listMissingCompletedOutputs, listSkillNames, resolveSkillName, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, PACKAGE_VERSION, packagedWorkspaceDir, pathExists, PhasePreflightError, previewPublishVersion, publishEntry, reindex as libraryReindex, refreshScaffold, resolvePhase, resolvePipelineChoice, readBroadsideSkill, runBroadsideCollect, runBroadsideStatus, runBroadsideSubmit, seedOrchestratorFiles, statusText, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, writeDashboard, } from "../core/index.js";
20
+ import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, BROADSIDE_DIR, broadsideDirFor, BROADSIDE_LENS_IDS, BROADSIDE_SKILL_NAME, BroadsideConfigError, defaultBroadsideConfig, backupWorkspaceState, canonicalPath, copyPackagedWorkspace, collectResultText, completeValidatedPhase, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, describeScaffoldStaleness, detectProvenanceConflicts, estimateSubmitText, getLens, describeDanglingCarryForward, describeMissingCompletedOutputs, describeStuckPipeline, getPipelineLabel, getWorkspaceState, isValidSlug, isWithinPathResolved, listBatchModels, listEntries, listGuideTopics, loadBroadsideConfig, modelsText, readGuide, listMissingCompletedOutputs, listSkillNames, resolvePipelineOutcome, resolveSkillName, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, PACKAGE_VERSION, packagedWorkspaceDir, pathExists, PhasePreflightError, previewPublishVersion, publishEntry, reindex as libraryReindex, refreshScaffold, resolvePhase, resolvePipelineChoice, readBroadsideSkill, runBroadsideCollect, runBroadsideStatus, runBroadsideSubmit, seedOrchestratorFiles, statusText, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, writeDashboard, } from "../core/index.js";
21
21
  import { applyAmendment } from "../core/amendment.js";
22
22
  import { appendUsageRun } from "../core/usage.js";
23
23
  import { initLibrary } from "../core/library.js";
@@ -69,8 +69,12 @@ async function optionalCwd(cwd) {
69
69
  return validateCwd(cwd.trim());
70
70
  }
71
71
  async function requireWorkspace(cwd) {
72
+ // What getWorkspaceState throws is the user's to fix — a status.yaml that
73
+ // does not parse, a missing `pipeline:`, a pipeline file that is not there
74
+ // — so it is InvalidRequest, not the InternalError a host would retry or
75
+ // report as a server bug (self-audit mech 2.8).
72
76
  const state = await getWorkspaceState(cwd).catch((error) => {
73
- throw new McpError(ErrorCode.InternalError, error instanceof Error ? error.message : String(error));
77
+ throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
74
78
  });
75
79
  if (!state) {
76
80
  throw new McpError(ErrorCode.InvalidRequest, `No CodeCartographer workspace at ${cwd}. Call codecarto_init first.`);
@@ -89,9 +93,9 @@ function textResult(text, structured) {
89
93
  structuredContent: { ...structured, text },
90
94
  };
91
95
  }
92
- async function buildMcpPhasePrompt(state, phase, forced) {
96
+ async function buildMcpPhasePrompt(state, phase, forced, auto = false) {
93
97
  try {
94
- return await buildPhasePrompt(state, phase, forced);
98
+ return await buildPhasePrompt(state, phase, forced, { auto });
95
99
  }
96
100
  catch (error) {
97
101
  if (error instanceof PhasePreflightError) {
@@ -188,8 +192,11 @@ export async function handleInit(args) {
188
192
  export async function handleStatus(args) {
189
193
  const cwd = await validateCwd(args.cwd);
190
194
  const state = await requireWorkspace(cwd);
191
- const nextPhase = getNextEligiblePhase(state);
192
- const currentPhase = nextPhase?.id ?? state.status.current_phase ?? "complete";
195
+ const outcome = resolvePipelineOutcome(state);
196
+ const nextPhase = outcome.kind === "eligible" ? outcome.phase : null;
197
+ // "complete" is the terminal marker; a stuck pipeline sits on its first
198
+ // blocked phase and says so below (#228).
199
+ const currentPhase = outcome.kind === "eligible" ? outcome.phase.id : outcome.kind === "stuck" ? outcome.blocked[0].phaseId : "complete";
193
200
  const completed = state.pipeline.phase_order.filter((id) => state.status.phases[id]?.status === "complete").length;
194
201
  const totalCarryForward = Object.values(state.status.phases).reduce((sum, phase) => sum + (phase.carry_forward?.length ?? 0), 0);
195
202
  const currentOpenQuestions = currentPhase === "complete" ? 0 : state.status.phases[currentPhase]?.open_questions?.length ?? 0;
@@ -199,7 +206,7 @@ export async function handleStatus(args) {
199
206
  const missingOutputs = await listMissingCompletedOutputs(state);
200
207
  const summaryLines = [
201
208
  `Phase: ${currentPhase}`,
202
- `Pipeline state: ${currentPhase === "complete" ? "complete" : "in progress"}`,
209
+ `Pipeline state: ${outcome.kind === "eligible" ? "in progress" : outcome.kind}`,
203
210
  `Pipeline: ${getPipelineLabel(state.status.pipeline)} (${state.status.pipeline})`,
204
211
  `Progress: ${completed}/${state.pipeline.phase_order.length} complete`,
205
212
  `Open questions (terminal unresolved): ${terminalOpenQuestions}`,
@@ -212,6 +219,8 @@ export async function handleStatus(args) {
212
219
  ? state.status.next_actions.map((action, index) => `${index === 0 ? "Next: " : " "}${action}`)
213
220
  : [`Next: ${nextPhase ? `Begin ${nextPhase.id}` : "All phases complete."}`]),
214
221
  ];
222
+ if (outcome.kind === "stuck")
223
+ summaryLines.push(describeStuckPipeline(outcome.blocked));
215
224
  if (scaffoldNotice)
216
225
  summaryLines.push(`Scaffold: ${scaffoldNotice}`);
217
226
  summaryLines.push(...describeMissingCompletedOutputs(missingOutputs));
@@ -219,6 +228,8 @@ export async function handleStatus(args) {
219
228
  return textResult(summary, {
220
229
  ...(scaffoldNotice ? { scaffoldNotice } : {}),
221
230
  ...(missingOutputs.length > 0 ? { missingOutputs } : {}),
231
+ ...(outcome.kind === "stuck" ? { stuck: outcome.blocked } : {}),
232
+ pipelineState: outcome.kind === "eligible" ? "in progress" : outcome.kind,
222
233
  currentPhase,
223
234
  pipeline: state.status.pipeline,
224
235
  pipelineLabel: getPipelineLabel(state.status.pipeline),
@@ -271,14 +282,24 @@ export async function handleSwitchPipeline(args) {
271
282
  export async function handleNext(args) {
272
283
  const cwd = await validateCwd(args.cwd);
273
284
  const state = await requireWorkspace(cwd);
274
- const phase = getNextEligiblePhase(state);
275
- if (!phase) {
285
+ const outcome = resolvePipelineOutcome(state);
286
+ if (outcome.kind === "stuck") {
287
+ // Not a result: a host looping on codecarto_next would read a text
288
+ // answer as "done" (#228). There is no prompt to hand out until the
289
+ // pipeline file is fixed.
290
+ throw new McpError(ErrorCode.InvalidRequest, describeStuckPipeline(outcome.blocked));
291
+ }
292
+ if (outcome.kind === "complete") {
276
293
  return textResult("All CodeCartographer phases are complete. Run codecarto_skill for post-pipeline work.", {
277
294
  complete: true,
278
295
  });
279
296
  }
280
- const prompt = await buildMcpPhasePrompt(state, phase, false);
281
- return textResult(prompt, { phase: phase.id, forced: false });
297
+ // `unattended` is the MCP spelling of Pi's --auto: nobody is there to
298
+ // answer the reimplementation-spec phase's Strategic Alignment Hook, so
299
+ // the prompt carries the auto-default instruction instead (#270).
300
+ const unattended = args.unattended === true;
301
+ const prompt = await buildMcpPhasePrompt(state, outcome.phase, false, unattended);
302
+ return textResult(prompt, { phase: outcome.phase.id, forced: false, unattended });
282
303
  }
283
304
  export async function handlePhase(args) {
284
305
  if (typeof args.phase !== "string" || !args.phase.trim()) {
@@ -290,8 +311,9 @@ export async function handlePhase(args) {
290
311
  if (!phase) {
291
312
  throw new McpError(ErrorCode.InvalidParams, `Unknown phase: ${args.phase}`);
292
313
  }
293
- const prompt = await buildMcpPhasePrompt(state, phase, true);
294
- return textResult(prompt, { phase: phase.id, forced: true });
314
+ const unattended = args.unattended === true;
315
+ const prompt = await buildMcpPhasePrompt(state, phase, true, unattended);
316
+ return textResult(prompt, { phase: phase.id, forced: true, unattended });
295
317
  }
296
318
  export async function handleValidate(args) {
297
319
  const cwd = await validateCwd(args.cwd);
@@ -391,9 +413,12 @@ export async function handleSkill(args) {
391
413
  return textResult(skill.content, { skill: BROADSIDE_SKILL_NAME, path: skill.path, postPipeline: false });
392
414
  }
393
415
  const state = await requireWorkspace(cwd);
394
- const nextPhase = getNextEligiblePhase(state);
395
- if (nextPhase) {
396
- throw new McpError(ErrorCode.InvalidRequest, `Cannot run skill: pipeline is not complete (next phase: ${nextPhase.id}). Finish the pipeline first.`);
416
+ const outcome = resolvePipelineOutcome(state);
417
+ if (outcome.kind === "eligible") {
418
+ throw new McpError(ErrorCode.InvalidRequest, `Cannot run skill: pipeline is not complete (next phase: ${outcome.phase.id}). Finish the pipeline first.`);
419
+ }
420
+ if (outcome.kind === "stuck") {
421
+ throw new McpError(ErrorCode.InvalidRequest, `Cannot run skill: the pipeline is not complete. ${describeStuckPipeline(outcome.blocked)}`);
397
422
  }
398
423
  // Resolve against the installed list only: the name is never joined onto a
399
424
  // path, so a traversal like `../findings/architecture` cannot splice a
@@ -883,8 +908,16 @@ export async function handleOpen(args) {
883
908
  throw new McpError(ErrorCode.InvalidRequest, "No existing CodeCartographer workspace found. Run codecarto_init first.");
884
909
  }
885
910
  const state = await requireWorkspace(cwd);
886
- const nextPhase = getNextEligiblePhase(state)?.id ?? "complete";
887
- return textResult(`Opened existing CodeCartographer workspace: ${getPipelineLabel(state.status.pipeline)}. Current phase: ${nextPhase}.`, { pipeline: getPipelineLabel(state.status.pipeline), currentPhase: nextPhase });
911
+ const outcome = resolvePipelineOutcome(state);
912
+ const nextPhase = outcome.kind === "eligible" ? outcome.phase.id : outcome.kind === "stuck" ? `${outcome.blocked[0].phaseId} (stuck)` : "complete";
913
+ const lines = [`Opened existing CodeCartographer workspace: ${getPipelineLabel(state.status.pipeline)}. Current phase: ${nextPhase}.`];
914
+ if (outcome.kind === "stuck")
915
+ lines.push(describeStuckPipeline(outcome.blocked));
916
+ return textResult(lines.join("\n"), {
917
+ pipeline: getPipelineLabel(state.status.pipeline),
918
+ currentPhase: nextPhase,
919
+ ...(outcome.kind === "stuck" ? { stuck: outcome.blocked } : {}),
920
+ });
888
921
  }
889
922
  export async function handleUsage(args) {
890
923
  const cwd = await validateCwd(args.cwd);
@@ -1007,18 +1040,40 @@ export async function handleBroadside(args) {
1007
1040
  if (!["submit", "collect", "status", "models"].includes(action)) {
1008
1041
  throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}. Valid actions: submit, collect, status, models.`);
1009
1042
  }
1010
- const config = await loadBroadsideConfig(broadsideDirFor(cwd));
1043
+ // A config.yaml that exists but cannot be read refuses every action that
1044
+ // would act on it (#232); status only reads recorded runs, so it answers
1045
+ // and says the file is unreadable. A corrupt state.json refuses even that:
1046
+ // there is nothing trustworthy to report and nothing may write over it (#233).
1047
+ let config;
1048
+ let configWarning = null;
1049
+ try {
1050
+ config = await loadBroadsideConfig(broadsideDirFor(cwd));
1051
+ }
1052
+ catch (error) {
1053
+ if (!(error instanceof BroadsideConfigError) || action !== "status") {
1054
+ throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
1055
+ }
1056
+ config = defaultBroadsideConfig();
1057
+ configWarning = error.message;
1058
+ }
1011
1059
  if (action === "status") {
1012
- const { state } = await runBroadsideStatus(cwd);
1013
- return textResult(statusText(state), { state });
1060
+ const { state } = await runBroadsideStatus(cwd).catch((error) => {
1061
+ throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
1062
+ });
1063
+ const lines = [statusText(state)];
1064
+ if (configWarning)
1065
+ lines.push(`Warning: ${configWarning}`);
1066
+ return textResult(lines.join("\n"), { state, ...(configWarning ? { configWarning } : {}) });
1014
1067
  }
1015
1068
  const apiKey = resolveBroadsideApiKey(args.api_key, config);
1016
1069
  // Every run knob resolves the same way: explicit parameter, else the repo's
1017
1070
  // config.yaml default, else the shipped default baked into loadBroadsideConfig.
1018
- const waitSeconds = typeof args.wait_seconds === "number" && args.wait_seconds > 0
1071
+ // An explicit 0 means "poll once and return": it used to fall through to
1072
+ // the config default and, as undefined, to the 25-minute budget (#230).
1073
+ const waitSeconds = typeof args.wait_seconds === "number" && args.wait_seconds >= 0
1019
1074
  ? args.wait_seconds
1020
1075
  : config.waitSeconds;
1021
- const waitMs = waitSeconds > 0 ? waitSeconds * 1000 : undefined;
1076
+ const waitMs = waitSeconds * 1000;
1022
1077
  const includeSynthesis = args.include_synthesis ?? config.includeSynthesis;
1023
1078
  const includeTriage = args.include_triage ?? config.includeTriage;
1024
1079
  const retryTruncated = args.retry_truncated ?? config.retryTruncated;
@@ -1047,7 +1102,9 @@ export async function handleBroadside(args) {
1047
1102
  else {
1048
1103
  lenses = config.defaultLenses;
1049
1104
  }
1050
- const maxCost = typeof args.max_cost === "number" && args.max_cost > 0 ? args.max_cost : config.maxCost;
1105
+ // An explicit 0 is "no limit" (#231); absent falls back to config.yaml,
1106
+ // whose own default is BROADSIDE_DEFAULT_MAX_COST.
1107
+ const maxCost = typeof args.max_cost === "number" && args.max_cost >= 0 ? args.max_cost : config.maxCost;
1051
1108
  const result = await runBroadsideSubmit(cwd, apiKey, {
1052
1109
  lenses,
1053
1110
  model: config.model,
@@ -1084,11 +1141,13 @@ export async function handleBroadside(args) {
1084
1141
  });
1085
1142
  }
1086
1143
  // action === "collect"
1144
+ const runId = typeof args.run_id === "string" && args.run_id.trim() ? args.run_id.trim() : undefined;
1087
1145
  const collect = await runBroadsideCollect(cwd, apiKey, {
1088
1146
  waitMs,
1089
1147
  includeSynthesis,
1090
1148
  includeTriage,
1091
1149
  retryTruncated,
1150
+ ...(runId && { runId }),
1092
1151
  }).catch((error) => {
1093
1152
  throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
1094
1153
  });
@@ -1156,7 +1215,13 @@ const TOOLS = [
1156
1215
  description: "Return the prompt text for the next eligible CodeCartographer phase. The host should feed this prompt back to the agent or display it to the user.",
1157
1216
  inputSchema: {
1158
1217
  type: "object",
1159
- properties: { cwd: { type: "string", description: "Absolute path to the target repository." } },
1218
+ properties: {
1219
+ cwd: { type: "string", description: "Absolute path to the target repository." },
1220
+ unattended: {
1221
+ type: "boolean",
1222
+ description: "Set when no user is in the loop to answer the prompt's questions (an autonomous run). The reimplementation-spec prompt then defaults to the language-agnostic variant and records selection: auto-default instead of asking which variant to build, and every interactive hook is suppressed. Same as Pi's /codecarto-next --auto.",
1223
+ },
1224
+ },
1160
1225
  required: ["cwd"],
1161
1226
  },
1162
1227
  },
@@ -1168,6 +1233,7 @@ const TOOLS = [
1168
1233
  properties: {
1169
1234
  cwd: { type: "string", description: "Absolute path to the target repository." },
1170
1235
  phase: { type: "string", description: "Phase id from the active pipeline." },
1236
+ unattended: { type: "boolean", description: "As for codecarto_next: no user is in the loop, so interactive hooks default and record their choice instead of asking." },
1171
1237
  },
1172
1238
  required: ["cwd", "phase"],
1173
1239
  },
@@ -1186,7 +1252,7 @@ const TOOLS = [
1186
1252
  },
1187
1253
  {
1188
1254
  name: "codecarto_complete",
1189
- description: "Mark a phase complete. Requires the phase output's validation to be PASS or PASS WITH GAPS. Atomically updates status.yaml under a file lock, appends to THREAD_LOG.md, and creates a closeout stub from the template if one does not yet exist. If phase is omitted, completes the next eligible phase.",
1255
+ description: "Mark a phase complete. Requires the phase output's validation to be PASS or PASS WITH GAPS and applies the phase handoff at scratch/handoffs/<phase>.yaml. Atomically updates status.yaml under a file lock, appends one THREAD_LOG.md entry, and writes the phase's closeout: the handoff's closeout_content (plus its decisions) overwrites the canonical <date>-<phase>.md when supplied; otherwise a stub is copied from the template only if no closeout exists yet. If phase is omitted, completes the next eligible phase.",
1190
1256
  inputSchema: {
1191
1257
  type: "object",
1192
1258
  properties: {
@@ -1412,11 +1478,15 @@ const TOOLS = [
1412
1478
  },
1413
1479
  api_key: {
1414
1480
  type: "string",
1415
- description: "OpenRouter API key. Prefer the OPENROUTER_API_KEY environment variable or .codecarto/broadside/config.yaml.",
1481
+ description: "OpenRouter API key. A value passed here is recorded wherever the host logs tool calls; prefer the OPENROUTER_API_KEY environment variable of the server process. (api_key in .codecarto/broadside/config.yaml also works, but that file is tracked, so a key there is committed with it.)",
1482
+ },
1483
+ run_id: {
1484
+ type: "string",
1485
+ description: "For collect: the run to collect, as listed by the status action. Defaults to the most recent run; pass this to collect an older run that is still in flight after a newer submit.",
1416
1486
  },
1417
1487
  wait_seconds: {
1418
1488
  type: "number",
1419
- description: "For submit: after submitting, poll up to this many seconds before returning. For collect: poll up to this many seconds before returning with partial state. Falls back to wait_seconds in .codecarto/broadside/config.yaml.",
1489
+ description: "For submit: after submitting, poll up to this many seconds before returning. For collect: poll up to this many seconds before returning with partial state. 0 polls each in-flight batch once and returns without waiting. Falls back to wait_seconds in .codecarto/broadside/config.yaml (default 0).",
1420
1490
  },
1421
1491
  include_synthesis: {
1422
1492
  type: "boolean",
@@ -1432,7 +1502,7 @@ const TOOLS = [
1432
1502
  },
1433
1503
  max_cost: {
1434
1504
  type: "number",
1435
- description: "Approximate run expense limit in USD. The submit action estimates the run cost from slice sizes and the configured model's per-token pricing (live OpenRouter lookup, cached 24h) and refuses to submit when the estimate exceeds the limit unless force is true. Falls back to max_cost in .codecarto/broadside/config.yaml.",
1505
+ description: "Approximate run expense limit in USD. The submit action estimates the run cost from slice sizes and the configured model's per-token pricing (live OpenRouter lookup, cached 24h) and refuses to submit when the estimate exceeds the limit unless force is true. Falls back to max_cost in .codecarto/broadside/config.yaml, whose default is $1.00; pass 0 for no limit.",
1436
1506
  },
1437
1507
  force: {
1438
1508
  type: "boolean",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codecartographer-pi",
3
- "version": "0.20.0",
3
+ "version": "0.21.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",