codecartographer-pi 0.19.6 → 0.20.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 +14 -0
  3. package/.codecarto/broadside/config.yaml +18 -0
  4. package/.codecarto/workflow/VALIDATE.md +2 -1
  5. package/.codecarto/workflow/scaffold-version.yaml +1 -1
  6. package/README.md +10 -6
  7. package/dist/core/broadside.d.ts +72 -2
  8. package/dist/core/broadside.js +348 -63
  9. package/dist/core/completion.js +81 -22
  10. package/dist/core/dashboard-writer.d.ts +8 -0
  11. package/dist/core/dashboard-writer.js +159 -0
  12. package/dist/core/index.d.ts +2 -0
  13. package/dist/core/index.js +2 -0
  14. package/dist/core/library.js +4 -1
  15. package/dist/core/orchestrator-config.d.ts +32 -7
  16. package/dist/core/orchestrator-config.js +124 -44
  17. package/dist/core/pipeline.d.ts +37 -0
  18. package/dist/core/pipeline.js +80 -10
  19. package/dist/core/prompts.d.ts +20 -0
  20. package/dist/core/prompts.js +43 -10
  21. package/dist/core/secrets.d.ts +16 -0
  22. package/dist/core/secrets.js +98 -0
  23. package/dist/core/status.d.ts +8 -0
  24. package/dist/core/status.js +8 -3
  25. package/dist/core/synthesis.js +5 -2
  26. package/dist/core/workspace.d.ts +55 -8
  27. package/dist/core/workspace.js +115 -7
  28. package/dist/core/yaml.js +173 -14
  29. package/dist/extensions/codecarto/agent-rewriter.js +21 -14
  30. package/dist/extensions/codecarto/agent-runner.d.ts +6 -2
  31. package/dist/extensions/codecarto/agent-runner.js +27 -9
  32. package/dist/extensions/codecarto/agent-state.d.ts +0 -2
  33. package/dist/extensions/codecarto/auto-runner.js +10 -6
  34. package/dist/extensions/codecarto/dashboard-narrator.js +9 -2
  35. package/dist/extensions/codecarto/dashboard-writer.d.ts +1 -8
  36. package/dist/extensions/codecarto/dashboard-writer.js +5 -154
  37. package/dist/extensions/codecarto/index.js +65 -18
  38. package/dist/mcp-server/server.js +107 -49
  39. package/package.json +3 -2
@@ -17,12 +17,11 @@ 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, canonicalPath, copyPackagedWorkspace, collectResultText, completeValidatedPhase, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, describeScaffoldStaleness, detectProvenanceConflicts, estimateSubmitText, getLens, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isValidSlug, isWithinPathResolved, listBatchModels, listEntries, listGuideTopics, loadBroadsideConfig, modelsText, readGuide, listSkillNames, 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, } from "../core/index.js";
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";
21
21
  import { applyAmendment } from "../core/amendment.js";
22
22
  import { appendUsageRun } from "../core/usage.js";
23
23
  import { initLibrary } from "../core/library.js";
24
- import { loadUserConfig, resolveUserConfigPath } from "../core/orchestrator-config.js";
25
- import { writeDashboard } from "../extensions/codecarto/dashboard-writer.js";
24
+ import { describeConfigProblems, loadUserConfig, resolveUserConfigPath } from "../core/orchestrator-config.js";
26
25
  // ---------- input helpers ----------
27
26
  /**
28
27
  * Normalize an optional `phase` argument. A client can send any JSON, and
@@ -50,6 +49,25 @@ async function validateCwd(cwd) {
50
49
  }
51
50
  return cwd;
52
51
  }
52
+ /**
53
+ * The library tools take `cwd` optionally: absent means "no workspace, use
54
+ * the user-global config alone". When it is given it becomes a containment
55
+ * root for spec_path and the source of the workspace config that gates the
56
+ * write, so it is validated exactly as a required cwd is — absolute and
57
+ * existing — before anything reads through it (#241). A relative path would
58
+ * resolve against the MCP server process's working directory, not the
59
+ * caller's, and widen the root to whatever `.codecarto/` sits there.
60
+ */
61
+ async function optionalCwd(cwd) {
62
+ if (cwd === undefined || cwd === null)
63
+ return null;
64
+ if (typeof cwd !== "string") {
65
+ throw new McpError(ErrorCode.InvalidParams, "cwd must be a string when given");
66
+ }
67
+ if (cwd.trim() === "")
68
+ return null;
69
+ return validateCwd(cwd.trim());
70
+ }
53
71
  async function requireWorkspace(cwd) {
54
72
  const state = await getWorkspaceState(cwd).catch((error) => {
55
73
  throw new McpError(ErrorCode.InternalError, error instanceof Error ? error.message : String(error));
@@ -109,12 +127,21 @@ export async function handleInit(args) {
109
127
  const entries = (await readdir(targetWorkspaceDir)).filter((entry) => entry !== BROADSIDE_DIR);
110
128
  broadsideOnly = entries.length === 0 && (await pathExists(join(targetWorkspaceDir, BROADSIDE_DIR)));
111
129
  }
112
- if (targetExists && !sameWorkspace && !broadsideOnly) {
130
+ if (targetExists && !broadsideOnly) {
113
131
  if (!args.force) {
114
- throw new McpError(ErrorCode.InvalidRequest, `A .codecarto/ directory already exists at ${targetWorkspaceDir}. Pass force: true to back it up and reinitialize. Warning: this moves all existing findings, handoffs, usage data, closeouts, and phase progress to a .codecarto-backup-TIMESTAMP/ directory.`);
132
+ throw new McpError(ErrorCode.InvalidRequest, sameWorkspace
133
+ ? `The .codecarto/ at ${targetWorkspaceDir} is CodeCartographer's own packaged template (a checkout install), and it holds workspace state. Pass force: true to move that state — status, findings, handoffs, usage data, closeouts, dashboard — to a .codecarto-backup-TIMESTAMP/ directory and reinitialize; the framework files stay in place. Consider codecarto_open to reattach without resetting.`
134
+ : `A .codecarto/ directory already exists at ${targetWorkspaceDir}. Pass force: true to back it up and reinitialize. Warning: this moves all existing findings, handoffs, usage data, closeouts, and phase progress to a .codecarto-backup-TIMESTAMP/ directory.`);
115
135
  }
116
136
  const backupDir = join(cwd, `.codecarto-backup-${new Date().toISOString().replace(/[:.]/g, "-")}`);
117
- await rename(targetWorkspaceDir, backupDir);
137
+ if (sameWorkspace) {
138
+ // The template cannot be renamed away — it is what init copies from —
139
+ // so its session state is moved out file by file instead (#245).
140
+ await backupWorkspaceState(targetWorkspaceDir, backupDir);
141
+ }
142
+ else {
143
+ await rename(targetWorkspaceDir, backupDir);
144
+ }
118
145
  }
119
146
  if (!(await pathExists(targetWorkspaceDir))) {
120
147
  await mkdir(cwd, { recursive: true });
@@ -169,6 +196,7 @@ export async function handleStatus(args) {
169
196
  const terminalOpenQuestions = Object.values(state.status.phases).reduce((sum, phase) => sum + (phase.open_questions?.length ?? 0), 0);
170
197
  const postPipelinePending = state.status.post_pipeline.filter((entry) => entry.status !== "resolved").length;
171
198
  const scaffoldNotice = describeScaffoldStaleness(state);
199
+ const missingOutputs = await listMissingCompletedOutputs(state);
172
200
  const summaryLines = [
173
201
  `Phase: ${currentPhase}`,
174
202
  `Pipeline state: ${currentPhase === "complete" ? "complete" : "in progress"}`,
@@ -186,9 +214,11 @@ export async function handleStatus(args) {
186
214
  ];
187
215
  if (scaffoldNotice)
188
216
  summaryLines.push(`Scaffold: ${scaffoldNotice}`);
217
+ summaryLines.push(...describeMissingCompletedOutputs(missingOutputs));
189
218
  const summary = summaryLines.join("\n");
190
219
  return textResult(summary, {
191
220
  ...(scaffoldNotice ? { scaffoldNotice } : {}),
221
+ ...(missingOutputs.length > 0 ? { missingOutputs } : {}),
192
222
  currentPhase,
193
223
  pipeline: state.status.pipeline,
194
224
  pipelineLabel: getPipelineLabel(state.status.pipeline),
@@ -212,6 +242,11 @@ export async function handleSwitchPipeline(args) {
212
242
  return textResult(`Already on pipeline: ${getPipelineLabel(pipelineChoice)}`, { pipeline: getPipelineLabel(pipelineChoice) });
213
243
  }
214
244
  const result = await switchPipeline(cwd, pipelineChoice);
245
+ // The dashboard is rendered from the pipeline the switch just replaced;
246
+ // Pi re-rendered here and MCP did not, so the file showed the old phase
247
+ // list until the next completion (#254). Same best-effort call as
248
+ // completion and amendment make.
249
+ const dashboardPath = (await writeDashboard(cwd, PACKAGE_VERSION)) ? ".codecarto/dashboard.html" : undefined;
215
250
  const lines = [`Switched pipeline: ${getPipelineLabel(pipelineChoice)}`];
216
251
  if (result.carried.length > 0)
217
252
  lines.push(`Phases preserved (completed): ${result.carried.join(", ")}`);
@@ -219,11 +254,18 @@ export async function handleSwitchPipeline(args) {
219
254
  lines.push(`New phases: ${result.newPhases.join(", ")}`);
220
255
  if (result.dropped.length > 0)
221
256
  lines.push(`Phases not in new pipeline: ${result.dropped.join(", ")} (findings remain on disk)`);
257
+ lines.push(...describeDanglingCarryForward(result.dangling));
258
+ lines.push(`Current phase: ${result.state.status.current_phase}`);
259
+ if (dashboardPath)
260
+ lines.push(`Dashboard refreshed: ${dashboardPath}`);
222
261
  return textResult(lines.join("\n"), {
223
262
  pipeline: getPipelineLabel(pipelineChoice),
263
+ currentPhase: result.state.status.current_phase,
224
264
  carried: result.carried,
225
265
  newPhases: result.newPhases,
226
266
  dropped: result.dropped,
267
+ dangling: result.dangling,
268
+ dashboardPath,
227
269
  });
228
270
  }
229
271
  export async function handleNext(args) {
@@ -353,17 +395,20 @@ export async function handleSkill(args) {
353
395
  if (nextPhase) {
354
396
  throw new McpError(ErrorCode.InvalidRequest, `Cannot run skill: pipeline is not complete (next phase: ${nextPhase.id}). Finish the pipeline first.`);
355
397
  }
356
- const skillFile = join(state.workspaceDir, "skills", args.name, "SKILL.md");
357
- if (!(await pathExists(skillFile))) {
398
+ // Resolve against the installed list only: the name is never joined onto a
399
+ // path, so a traversal like `../findings/architecture` cannot splice a
400
+ // phase SKILL.md (or anything else) into the post-pipeline prompt.
401
+ const skillName = await resolveSkillName(state.workspaceDir, args.name);
402
+ if (!skillName) {
358
403
  const available = await listSkillNames(state.workspaceDir);
359
404
  const hint = available.length > 0 ? ` Available: ${available.join(", ")}.` : " No skills installed.";
360
- throw new McpError(ErrorCode.InvalidParams, `Unknown skill: ${args.name}.${hint} The Broad-Side reading guide is served as \`${BROADSIDE_SKILL_NAME}\` and is not pipeline-gated.`);
405
+ throw new McpError(ErrorCode.InvalidParams, `Unknown skill: ${args.name.trim()}.${hint} The Broad-Side reading guide is served as \`${BROADSIDE_SKILL_NAME}\` and is not pipeline-gated.`);
361
406
  }
362
- const prompt = await buildSkillPrompt(state, args.name);
363
- return textResult(prompt, { skill: args.name });
407
+ const prompt = await buildSkillPrompt(state, skillName);
408
+ return textResult(prompt, { skill: skillName });
364
409
  }
365
410
  // ---------- library helpers ----------
366
- async function resolveLibraryPath(args) {
411
+ function resolveLibraryPath(args, config) {
367
412
  const explicit = typeof args.library_path === "string" && args.library_path.trim() !== ""
368
413
  ? args.library_path.trim()
369
414
  : null;
@@ -373,18 +418,8 @@ async function resolveLibraryPath(args) {
373
418
  }
374
419
  return explicit;
375
420
  }
376
- if (typeof args.cwd === "string" && args.cwd.trim() !== "") {
377
- const cwd = args.cwd.trim();
378
- if (!isAbsolute(cwd)) {
379
- throw new McpError(ErrorCode.InvalidParams, `cwd must be absolute, got: ${cwd}`);
380
- }
381
- // loadCodecartoConfig merges user-global under per-workspace and tolerates
382
- // a missing workspace file, so a single call covers both cases.
383
- const workspaceDir = join(cwd, ".codecarto");
384
- const config = await loadCodecartoConfig(workspaceDir);
385
- if (config.library.path)
386
- return config.library.path;
387
- }
421
+ if (config.library.path)
422
+ return config.library.path;
388
423
  throw new McpError(ErrorCode.InvalidParams, "library_path is required (pass it explicitly, or pass cwd and configure library.path in ~/.codecarto/config.yaml or .codecarto/workflow/config.yaml).");
389
424
  }
390
425
  /**
@@ -394,17 +429,22 @@ async function resolveLibraryPath(args) {
394
429
  * the publish tool enforces.
395
430
  */
396
431
  async function loadEffectiveConfig(cwd) {
397
- if (typeof cwd !== "string" || cwd.trim() === "")
432
+ // This config decides whether publish_confirm gates the write, so the cwd
433
+ // it is read through is the one optionalCwd validated, never a raw argument.
434
+ if (!cwd)
398
435
  return loadUserConfig();
399
- // A relative path here resolves against the server process's working
400
- // directory, not the caller's, so it would quietly read some other
401
- // workspace's config — and this config decides whether publish_confirm
402
- // gates the write. Refuse rather than answer from the wrong file.
403
- const trimmed = cwd.trim();
404
- if (!isAbsolute(trimmed)) {
405
- throw new McpError(ErrorCode.InvalidParams, `cwd must be an absolute path, got: ${trimmed}`);
406
- }
407
- return loadCodecartoConfig(join(trimmed, ".codecarto"));
436
+ return loadCodecartoConfig(join(cwd, ".codecarto"));
437
+ }
438
+ /**
439
+ * The library tools answer from the config — its path, its namespace, its
440
+ * confirm gate — so a config file that could not be used in full is a
441
+ * refusal, not a silent fallback to whatever the other layer says (#242).
442
+ * The message names each file and key; codecarto_config shows the same list.
443
+ */
444
+ function refuseOnConfigProblems(config, tool) {
445
+ if (config.problems.length === 0)
446
+ return;
447
+ throw new McpError(ErrorCode.InvalidRequest, [`${tool} refused: the configuration has problems. Fix or remove the offending file, then retry.`, ...describeConfigProblems(config)].join("\n"));
408
448
  }
409
449
  function asStringArray(value, fieldName) {
410
450
  if (!Array.isArray(value)) {
@@ -491,12 +531,12 @@ async function resolveDefaultsFromWorkspace(cwd, overrides) {
491
531
  let namespace = typeof overrides.namespace === "string" && overrides.namespace.trim() !== ""
492
532
  ? overrides.namespace.trim()
493
533
  : null;
494
- if (typeof cwd === "string" && cwd.trim() !== "" && isAbsolute(cwd)) {
495
- const workspaceDir = join(cwd.trim(), ".codecarto");
534
+ if (cwd) {
535
+ const workspaceDir = join(cwd, ".codecarto");
496
536
  if (await pathExists(workspaceDir)) {
497
537
  if (pipeline === "unknown") {
498
538
  try {
499
- const state = await getWorkspaceState(cwd.trim());
539
+ const state = await getWorkspaceState(cwd);
500
540
  if (state?.status.pipeline)
501
541
  pipeline = state.status.pipeline;
502
542
  }
@@ -542,16 +582,20 @@ function provenanceConflictLines(conflicts) {
542
582
  }
543
583
  // ---------- library handlers ----------
544
584
  export async function handlePublish(args) {
545
- const libraryPath = await resolveLibraryPath(args);
585
+ // First, before anything is read through it: cwd is a containment root
586
+ // for spec_path below and the source of the config that gates the write.
587
+ const cwd = await optionalCwd(args.cwd);
588
+ const config = await loadEffectiveConfig(cwd);
589
+ refuseOnConfigProblems(config, "codecarto_publish");
590
+ const libraryPath = resolveLibraryPath(args, config);
546
591
  const marker = await discoverLibrary(libraryPath);
547
592
  if (!marker) {
548
593
  throw new McpError(ErrorCode.InvalidParams, `No CodeCartographer library at ${libraryPath} (missing .codecarto-library marker). Create one before publishing.`);
549
594
  }
550
595
  // Build allowed roots for spec_path containment: workspace .codecarto/ and library path
551
596
  const allowedRoots = [libraryPath];
552
- if (typeof args.cwd === "string" && args.cwd.trim() !== "") {
553
- allowedRoots.push(join(args.cwd.trim(), ".codecarto"));
554
- }
597
+ if (cwd)
598
+ allowedRoots.push(join(cwd, ".codecarto"));
555
599
  const spec = await readSpecArg(args, allowedRoots);
556
600
  if (typeof args.source_repo !== "string" || args.source_repo.trim() === "") {
557
601
  throw new McpError(ErrorCode.InvalidParams, "source_repo is required");
@@ -567,7 +611,7 @@ export async function handlePublish(args) {
567
611
  if (!isValidSlug(slug)) {
568
612
  throw new McpError(ErrorCode.InvalidParams, `Resolved slug "${slug}" is invalid. Provide an explicit slug (lowercase ASCII, starts with a letter, max 64 chars).`);
569
613
  }
570
- const defaults = await resolveDefaultsFromWorkspace(args.cwd, {
614
+ const defaults = await resolveDefaultsFromWorkspace(cwd, {
571
615
  pipeline: args.pipeline,
572
616
  namespace: args.namespace,
573
617
  });
@@ -596,7 +640,6 @@ export async function handlePublish(args) {
596
640
  // opt-out, and a host that never configured the key keeps the behavior it
597
641
  // had. Runs after every argument check so the preview names the resolved
598
642
  // slug and namespace, and before publishEntry so nothing is written.
599
- const config = await loadEffectiveConfig(args.cwd);
600
643
  if (config.library.publish_confirm && config.library.publish_confirm_configured && args.confirm !== true) {
601
644
  const preview = await previewPublishVersion(libraryPath, spec, { slug, namespace }, { forceNewVersion });
602
645
  const label = `${namespace ? `${namespace}/` : ""}${slug}`;
@@ -665,7 +708,9 @@ export async function handlePublish(args) {
665
708
  });
666
709
  }
667
710
  export async function handleLibraryList(args) {
668
- const libraryPath = await resolveLibraryPath(args);
711
+ const config = await loadEffectiveConfig(await optionalCwd(args.cwd));
712
+ refuseOnConfigProblems(config, "codecarto_library_list");
713
+ const libraryPath = resolveLibraryPath(args, config);
669
714
  const marker = await discoverLibrary(libraryPath);
670
715
  if (!marker) {
671
716
  throw new McpError(ErrorCode.InvalidParams, `No CodeCartographer library at ${libraryPath} (missing .codecarto-library marker).`);
@@ -706,7 +751,9 @@ export async function handleLibraryList(args) {
706
751
  });
707
752
  }
708
753
  export async function handleLibraryReindex(args) {
709
- const libraryPath = await resolveLibraryPath(args);
754
+ const config = await loadEffectiveConfig(await optionalCwd(args.cwd));
755
+ refuseOnConfigProblems(config, "codecarto_library_reindex");
756
+ const libraryPath = resolveLibraryPath(args, config);
710
757
  const marker = await discoverLibrary(libraryPath);
711
758
  if (!marker) {
712
759
  throw new McpError(ErrorCode.InvalidParams, `No CodeCartographer library at ${libraryPath} (missing .codecarto-library marker).`);
@@ -742,10 +789,17 @@ export async function handleLibraryInit(args) {
742
789
  });
743
790
  // Write config to user-global location
744
791
  const configPath = resolveUserConfigPath();
745
- await writeLibraryConfig(configPath, libraryPath, args.namespace ?? null);
792
+ // Writes library.path (and library.namespace when given) and nothing else:
793
+ // in particular it does not set publish_confirm, so initializing a library
794
+ // does not switch the codecarto_publish confirm gate on (#244). A config
795
+ // file that cannot be parsed is left alone and reported.
796
+ await writeLibraryConfig(configPath, libraryPath, args.namespace ?? null).catch((error) => {
797
+ throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
798
+ });
799
+ const written = args.namespace ? "library.path and library.namespace" : "library.path";
746
800
  const msg = result.alreadyExisted
747
- ? `Library already exists at ${libraryPath} (marker preserved). Config written to ${configPath}.`
748
- : `Created library at ${libraryPath} with marker "${result.marker.name}". Config written to ${configPath}.`;
801
+ ? `Library already exists at ${libraryPath} (marker preserved). Wrote ${written} to ${configPath}; other keys untouched.`
802
+ : `Created library at ${libraryPath} with marker "${result.marker.name}". Wrote ${written} to ${configPath}; other keys untouched.`;
749
803
  return textResult(msg, {
750
804
  libraryPath,
751
805
  markerName: result.marker.name,
@@ -808,6 +862,9 @@ export async function handleConfig(args) {
808
862
  ` Library marker: ${markerStatus}`,
809
863
  ` User-global config: ${userConfigPath}`,
810
864
  ` Workspace config: ${workspaceConfigPath ?? "(no cwd provided)"}`,
865
+ // A file that could not be used is the one thing this tool exists
866
+ // to surface; the library tools refuse while any is listed (#242).
867
+ ...describeConfigProblems(config),
811
868
  ].join("\n"), {
812
869
  libraryPath: config.library.path,
813
870
  libraryNamespace: config.library.namespace,
@@ -815,6 +872,7 @@ export async function handleConfig(args) {
815
872
  llmSteerNextPhase: config.orchestrator.llm_steer_next_phase,
816
873
  userConfigPath,
817
874
  workspaceConfigPath,
875
+ problems: config.problems,
818
876
  });
819
877
  }
820
878
  // ---------- MCP parity handlers: open, usage, dashboard, list_skills ----------
@@ -1116,7 +1174,7 @@ const TOOLS = [
1116
1174
  },
1117
1175
  {
1118
1176
  name: "codecarto_validate",
1119
- description: "Validate a phase's primary output against the validation block in the produced markdown. Returns overall PASS/PASS WITH GAPS/FAIL/MISSING plus the parsed criteria rows. If phase is omitted, validates the next eligible phase.",
1177
+ description: "Read the ## Validation table a phase wrote at the end of its primary output — the phase's own PASS/PARTIAL/FAIL per completion criterion — apply two cross-checks (findings evidence/action pairing; declared secondary outputs present), and return PASS/PASS WITH GAPS/FAIL/MISSING plus the parsed rows. It does not judge the criteria itself. If phase is omitted, validates the next eligible phase.",
1120
1178
  inputSchema: {
1121
1179
  type: "object",
1122
1180
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codecartographer-pi",
3
- "version": "0.19.6",
3
+ "version": "0.20.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",
@@ -76,6 +76,7 @@
76
76
  "typescript": "^5.9.3"
77
77
  },
78
78
  "overrides": {
79
- "undici": "^8.10.0"
79
+ "undici": "^8.10.0",
80
+ "hono": "^4.13.5"
80
81
  }
81
82
  }