codecartographer-pi 0.24.1 → 0.26.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 (55) hide show
  1. package/.codecarto/broadside/SKILL.md +20 -1
  2. package/.codecarto/workflow/scaffold-version.yaml +1 -1
  3. package/README.md +5 -4
  4. package/agent-skill/codecartographer/references/broadside.md +5 -1
  5. package/dist/core/broadside/client.d.ts +56 -0
  6. package/dist/core/broadside/client.js +200 -0
  7. package/dist/core/broadside/collect.d.ts +68 -0
  8. package/dist/core/broadside/collect.js +676 -0
  9. package/dist/core/broadside/constants.d.ts +51 -0
  10. package/dist/core/broadside/constants.js +74 -0
  11. package/dist/core/broadside/lenses.d.ts +31 -0
  12. package/dist/core/broadside/lenses.js +312 -0
  13. package/dist/core/broadside/models.d.ts +46 -0
  14. package/dist/core/broadside/models.js +321 -0
  15. package/dist/core/broadside/render.d.ts +20 -0
  16. package/dist/core/broadside/render.js +285 -0
  17. package/dist/core/broadside/repo.d.ts +58 -0
  18. package/dist/core/broadside/repo.js +592 -0
  19. package/dist/core/broadside/requests.d.ts +23 -0
  20. package/dist/core/broadside/requests.js +71 -0
  21. package/dist/core/broadside/results.d.ts +36 -0
  22. package/dist/core/broadside/results.js +163 -0
  23. package/dist/core/broadside/schemas.d.ts +2 -0
  24. package/dist/core/broadside/schemas.js +342 -0
  25. package/dist/core/broadside/state.d.ts +99 -0
  26. package/dist/core/broadside/state.js +384 -0
  27. package/dist/core/broadside/submit.d.ts +30 -0
  28. package/dist/core/broadside/submit.js +350 -0
  29. package/dist/core/broadside/types.d.ts +491 -0
  30. package/dist/core/broadside/types.js +107 -0
  31. package/dist/core/{broadside-verify.d.ts → broadside/verify.d.ts} +23 -2
  32. package/dist/core/{broadside-verify.js → broadside/verify.js} +43 -5
  33. package/dist/core/broadside.d.ts +14 -890
  34. package/dist/core/broadside.js +25 -3564
  35. package/dist/core/completion.js +91 -72
  36. package/dist/core/dashboard-writer.js +9 -1
  37. package/dist/core/index.d.ts +0 -1
  38. package/dist/core/index.js +0 -1
  39. package/dist/core/library.d.ts +24 -1
  40. package/dist/core/library.js +46 -15
  41. package/dist/core/orchestrator-config.js +22 -8
  42. package/dist/core/status.d.ts +42 -23
  43. package/dist/core/status.js +163 -137
  44. package/dist/core/workspace.d.ts +2 -0
  45. package/dist/core/workspace.js +49 -25
  46. package/dist/core/yaml.js +9 -3
  47. package/dist/extensions/codecarto/auto-runner.d.ts +7 -0
  48. package/dist/extensions/codecarto/auto-runner.js +54 -23
  49. package/dist/extensions/codecarto/broadside-flags.d.ts +3 -1
  50. package/dist/extensions/codecarto/broadside-flags.js +13 -0
  51. package/dist/extensions/codecarto/index.js +13 -7
  52. package/dist/extensions/codecarto/phase-compaction.js +6 -2
  53. package/dist/mcp-server/server.d.ts +1 -0
  54. package/dist/mcp-server/server.js +28 -5
  55. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
2
  import { basename, dirname, join, resolve } from "node:path";
3
- import { autoCompletePhase, buildAutoSummary, isPhaseRunning, runAuto, runSinglePhase } from "./auto-runner.js";
3
+ import { autoCompletePhase, buildAutoSummary, describeAutoOutcome, isPhaseRunning, runAuto, runSinglePhase } from "./auto-runner.js";
4
4
  import { disposeAgentsWidget } from "./agent-widget.js";
5
5
  import { parseDashboardFlags } from "./dashboard-flags.js";
6
6
  import { narrateDashboard } from "./dashboard-narrator.js";
@@ -638,7 +638,7 @@ export default function codeCartographerExtension(pi) {
638
638
  });
639
639
  lastFeedbackLines = [`Auto pipeline ${result.outcome}: ${result.reason}`];
640
640
  await refreshWorkspaceUi(ctx, lastFeedbackLines);
641
- notifyCtx(ctx, `Auto pipeline ${result.outcome}: ${result.phasesRun.length}/${result.totalPhases} phases.`, result.outcome === "complete" ? "info" : "warning");
641
+ notifyCtx(ctx, describeAutoOutcome(result), result.outcome === "complete" ? "info" : "warning");
642
642
  return;
643
643
  }
644
644
  const outcome = resolvePipelineOutcome(state);
@@ -995,7 +995,7 @@ export default function codeCartographerExtension(pi) {
995
995
  },
996
996
  });
997
997
  pi.registerCommand("codecarto-broadside", {
998
- description: "Batch reconnaissance (Broad-Side): /codecarto-broadside [submit|collect|status|models|verify] [lenses…] [--model=ID] [--lens-model=LENS:ID] [--top=N] [flags]",
998
+ description: "Batch reconnaissance (Broad-Side): /codecarto-broadside [submit|collect|status|models|verify] [lenses…] [--model=ID] [--lens-model=LENS:ID] [--top=N] [--regenerate] [flags]",
999
999
  // Completes the token under the cursor, so lens names and flags are
1000
1000
  // offered after the action too, and keeps everything typed before it.
1001
1001
  getArgumentCompletions: (prefix) => completeLastToken(prefix, KNOWN_BROADSIDE_TOKENS.map((value) => ({ value }))),
@@ -1220,6 +1220,7 @@ export default function codeCartographerExtension(pi) {
1220
1220
  retryTruncated,
1221
1221
  onStatus,
1222
1222
  ...(flags.runId && { runId: flags.runId }),
1223
+ ...(flags.regeneratePostPasses && { regeneratePostPasses: true }),
1223
1224
  });
1224
1225
  const lines = collectResultText(collect).split("\n");
1225
1226
  const done = collect.status === "completed";
@@ -1506,10 +1507,15 @@ export default function codeCartographerExtension(pi) {
1506
1507
  notifyCtx(ctx, `LLM narration skipped (${result.skipReason}); rendering deterministic dashboard.`, "warning");
1507
1508
  }
1508
1509
  }
1509
- await writeDashboard(ctx.cwd, PACKAGE_VERSION);
1510
- lastFeedbackLines = ["Dashboard regenerated: .codecarto/dashboard.html"];
1510
+ // writeDashboard is best-effort and says so with its result: a
1511
+ // swallowed write failure used to be reported as success (#369).
1512
+ const written = await writeDashboard(ctx.cwd, PACKAGE_VERSION);
1513
+ const line = written
1514
+ ? "Dashboard regenerated: .codecarto/dashboard.html"
1515
+ : "Dashboard not regenerated: the write to .codecarto/dashboard.html failed (permissions or disk?); the previous file, if any, stands.";
1516
+ lastFeedbackLines = [line];
1511
1517
  setUiState(ctx, state, lastFeedbackLines);
1512
- notifyCtx(ctx, "Dashboard regenerated: .codecarto/dashboard.html", "info");
1518
+ notifyCtx(ctx, line, written ? "info" : "error");
1513
1519
  },
1514
1520
  });
1515
1521
  pi.registerCommand("codecarto-refresh-scaffold", {
@@ -1539,7 +1545,7 @@ export default function codeCartographerExtension(pi) {
1539
1545
  lastFeedbackLines = [
1540
1546
  `Refreshed ${result.written.length} framework-owned file(s) from the packaged template (${transition}).`,
1541
1547
  "Project state, user config, findings outputs, scratch, closeouts, and orchestrator files were not touched.",
1542
- "THREAD_LOG.md: one scaffold-refresh entry appended.",
1548
+ result.threadLogEntryAppended ? "THREAD_LOG.md: one scaffold-refresh entry appended." : "THREAD_LOG.md: already carries this refresh; nothing appended.",
1543
1549
  ];
1544
1550
  // Re-read state so the widget's staleness line clears with the marker.
1545
1551
  await refreshWorkspaceUi(ctx, lastFeedbackLines);
@@ -66,8 +66,12 @@ export function phaseCompactionExtension(pi) {
66
66
  if (event.toolName === "edit" || event.toolName === "write") {
67
67
  const inputPath = typeof event.input.path === "string" ? event.input.path : "";
68
68
  const strippedPath = inputPath.startsWith("@") ? inputPath.slice(1) : inputPath;
69
- // Same containment as the parent extension's hook (#223): follow the
70
- // existing prefix through symlinks, then append the unborn tail.
69
+ // The same symlink-aware containment as the parent extension's hook
70
+ // (#223) — follow the existing prefix through symlinks, then append
71
+ // the unborn tail — over a narrower root on purpose: a phase writes
72
+ // findings, handoffs, and checkpoints under .codecarto/ and never
73
+ // publishes, so the configured library the orchestrator's hook admits
74
+ // is not admitted here (#364).
71
75
  const targetPath = await resolveExistingPrefix(strippedPath, ctx.cwd);
72
76
  const allowedRoot = await canonicalPath(join(ctx.cwd, ".codecarto"));
73
77
  if (!isWithinPath(targetPath, allowedRoot)) {
@@ -248,6 +248,7 @@ export declare function handleBroadside(args: {
248
248
  model?: string;
249
249
  lens_models?: Record<string, string>;
250
250
  top?: number;
251
+ regenerate_post_passes?: boolean;
251
252
  }): Promise<{
252
253
  content: {
253
254
  type: "text";
@@ -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, 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, runBroadsideVerify, verifyResultText, seedOrchestratorFiles, statusLineWriter, 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, listEntriesWithIndexState, 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, runBroadsideVerify, verifyResultText, seedOrchestratorFiles, statusLineWriter, 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";
@@ -545,7 +545,10 @@ export async function readSpecArg(args, allowedRoots) {
545
545
  if (!withinAllowed.some((result) => result)) {
546
546
  throw new McpError(ErrorCode.InvalidParams, `spec_path must be within the workspace (.codecarto/) or the configured library path. Got: ${args.spec_path}`);
547
547
  }
548
- return readFile(args.spec_path, "utf8");
548
+ // The path that was checked is the path that is read: the original
549
+ // string could be re-pointed through a symlink between the check and
550
+ // the read; the canonical one cannot (#362).
551
+ return readFile(resolvedSpecPath, "utf8");
549
552
  }
550
553
  throw new McpError(ErrorCode.InvalidParams, "Either spec (inline content) or spec_path (absolute file path) is required");
551
554
  }
@@ -749,7 +752,7 @@ export async function handleLibraryList(args) {
749
752
  filter.slug = args.slug;
750
753
  if (typeof args.source_repo === "string" && args.source_repo !== "")
751
754
  filter.source_repo = args.source_repo;
752
- const entries = await listEntries(libraryPath, filter);
755
+ const { entries, indexState } = await listEntriesWithIndexState(libraryPath, filter);
753
756
  // Computed over the listed entries only, read-only: one readdir per entry
754
757
  // plus one small metadata read per older version.
755
758
  const conflicts = await detectProvenanceConflicts(libraryPath, entries);
@@ -766,11 +769,17 @@ export async function handleLibraryList(args) {
766
769
  }),
767
770
  ...provenanceConflictLines(conflicts),
768
771
  ].join("\n");
769
- return textResult(summary, {
772
+ // A list never writes the index (#357): say when it answered from the
773
+ // entry directories instead, so the caller can run a reindex.
774
+ const indexNote = indexState === "indexed"
775
+ ? ""
776
+ : `\nindex.yaml is ${indexState}; this listing was built from the entry directories. Run codecarto_library_reindex to rebuild it.`;
777
+ return textResult(`${summary}${indexNote}`, {
770
778
  libraryPath,
771
779
  libraryName: marker.name,
772
780
  namespaced: marker.namespaced,
773
781
  count: entries.length,
782
+ indexState,
774
783
  entries,
775
784
  provenance_conflicts: conflicts,
776
785
  });
@@ -996,6 +1005,7 @@ export async function handleRefreshScaffold(args) {
996
1005
  written: result.written,
997
1006
  scaffoldVersionBefore: result.scaffoldVersionBefore,
998
1007
  scaffoldVersionAfter: result.scaffoldVersionAfter,
1008
+ threadLogEntryAppended: result.threadLogEntryAppended,
999
1009
  });
1000
1010
  }
1001
1011
  export async function handleAmend(args) {
@@ -1208,10 +1218,17 @@ export async function handleBroadside(args) {
1208
1218
  candidates: verified.candidates,
1209
1219
  totalCost: verified.totalCost,
1210
1220
  ...(verified.stoppedByCost && { stoppedByCost: true }),
1221
+ redactedValues: verified.redactedValues,
1211
1222
  findings: verified.findings,
1212
1223
  });
1213
1224
  }
1214
1225
  // action === "collect"
1226
+ if (args.regenerate_post_passes !== undefined && typeof args.regenerate_post_passes !== "boolean") {
1227
+ throw new McpError(ErrorCode.InvalidParams, "regenerate_post_passes must be a boolean.");
1228
+ }
1229
+ if (args.regenerate_post_passes && !includeSynthesis && !includeTriage) {
1230
+ throw new McpError(ErrorCode.InvalidParams, "regenerate_post_passes needs at least one of include_synthesis and include_triage.");
1231
+ }
1215
1232
  const collect = await runBroadsideCollect(cwd, apiKey, {
1216
1233
  waitMs,
1217
1234
  includeSynthesis,
@@ -1219,6 +1236,7 @@ export async function handleBroadside(args) {
1219
1236
  retryTruncated,
1220
1237
  signal: serverLifetime?.signal,
1221
1238
  ...(runId && { runId }),
1239
+ ...(args.regenerate_post_passes && { regeneratePostPasses: true }),
1222
1240
  }).catch((error) => {
1223
1241
  throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
1224
1242
  });
@@ -1232,6 +1250,7 @@ export async function handleBroadside(args) {
1232
1250
  lensOutcomes: collect.lensOutcomes,
1233
1251
  synthesis: collect.synthesis,
1234
1252
  triage: collect.triage,
1253
+ ...(collect.regenerated && { regenerated: collect.regenerated }),
1235
1254
  topFindings: collect.topFindings,
1236
1255
  topTriageItems: collect.topTriageItems,
1237
1256
  });
@@ -1540,7 +1559,7 @@ const TOOLS = [
1540
1559
  action: {
1541
1560
  type: "string",
1542
1561
  enum: ["submit", "collect", "status", "models", "verify"],
1543
- description: "submit fires all lens batches and returns batch ids; collect polls submitted batches, saves results, and optionally runs the synthesis pass; status shows recorded runs; models lists batch-capable models with pricing and capabilities; verify reads a collected run's top defect and security findings against the repository with read-only tools (one sync-priced call each, about a cent on the default model) and writes verified.md/verified.json beside triage.md with a verdict per finding: confirmed (a reachable failure, with the trigger), not-a-defect, discarded, or unclear.",
1562
+ description: "submit fires all lens batches and returns batch ids; collect polls submitted batches, saves results, and optionally runs the synthesis and triage passes — built from verified.json when a verify pass has already written it, so run verify first or collect again with regenerate_post_passes afterwards; status shows recorded runs; models lists batch-capable models with pricing and capabilities; verify reads a collected run's top defect and security findings against the repository with read-only tools (one sync-priced call each, about a cent on the default model) and writes verified.md/verified.json beside triage.md with a verdict per finding: confirmed (a reachable failure, with the trigger), not-a-defect, discarded, or unclear.",
1544
1563
  },
1545
1564
  lenses: {
1546
1565
  type: "array",
@@ -1575,6 +1594,10 @@ const TOOLS = [
1575
1594
  type: "boolean",
1576
1595
  description: "Re-submit lens results that came back truncated at the output token limit, once, with a doubled output cap. Falls back to retry_truncated in .codecarto/broadside/config.yaml (default true).",
1577
1596
  },
1597
+ regenerate_post_passes: {
1598
+ type: "boolean",
1599
+ description: "collect only: reset the run's settled synthesis and triage passes and run them again, so a verify pass's verdicts (verified.json) are built into the executive report and the work order — confirmed findings first, discarded ones dropped, not-a-defect ones listed apart. Costs another post-pass pair (a few cents); a pass still in flight is left to finish; a run whose lens batches are still running is refused.",
1600
+ },
1578
1601
  max_cost: {
1579
1602
  type: "number",
1580
1603
  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. For verify it is a running cap: the pass stops before the next finding once the calls so far have reached it. Falls back to max_cost in .codecarto/broadside/config.yaml, whose default is $1.00; pass 0 for no limit.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codecartographer-pi",
3
- "version": "0.24.1",
3
+ "version": "0.26.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",