codecartographer-pi 0.24.0 → 0.25.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.
@@ -132,8 +132,9 @@ export async function runSinglePhase(ctx, pi, state, phase, options) {
132
132
  return { status: "error", activity, error: message };
133
133
  }
134
134
  finally {
135
- // Linger 30s so /codecarto-status can show that the phase ran.
136
- setTimeout(() => clearPhase(phase.id), 30_000);
135
+ // Linger 30s so /codecarto-status can show that the phase ran. Tied
136
+ // to this run's entry: a re-run inside the window keeps its own (#343).
137
+ setTimeout(() => clearPhase(phase.id, activity), 30_000).unref?.();
137
138
  }
138
139
  }
139
140
  /**
@@ -304,6 +305,19 @@ export async function runAuto(ctx, pi, initialState, options) {
304
305
  // ----------------------------------------------------------------------------
305
306
  // buildAutoSummary — the codecarto-auto-summary message body
306
307
  // ----------------------------------------------------------------------------
308
+ /**
309
+ * The one-line notification for an auto run's end. A run that stopped short
310
+ * carries its reason: the auto-summary message and the widget carry it too,
311
+ * but under `pi -p` neither is rendered, and "stopped: 0/1 phases" alone
312
+ * sent a reader back to the code to find out why (#347).
313
+ */
314
+ export function describeAutoOutcome(result) {
315
+ const line = `Auto pipeline ${result.outcome}: ${result.phasesRun.length}/${result.totalPhases} phases.`;
316
+ if (result.outcome === "complete")
317
+ return line;
318
+ const reason = result.reason.trim();
319
+ return reason ? `${line.replace(/\.$/, "")} — ${reason}` : line;
320
+ }
307
321
  export function buildAutoSummary(result, availableSkills = []) {
308
322
  const totalTokens = result.totalTokens.input + result.totalTokens.output;
309
323
  const wallTime = formatMillis(result.endedAt - result.startedAt);
@@ -24,11 +24,13 @@ export interface BroadsideFlags {
24
24
  lensModels?: Partial<Record<BroadsideLensId, string>>;
25
25
  /** For verify: how many findings to read (#143). */
26
26
  top?: number;
27
+ /** For collect: reset the settled post-passes and run them again (#338). */
28
+ regeneratePostPasses?: boolean;
27
29
  benchmarks: boolean;
28
30
  unknown: string[];
29
31
  /** Set on an invalid combination. The caller surfaces it as an error. */
30
32
  error?: string;
31
33
  }
32
34
  /** Every token the completer offers, in the order it offers them. */
33
- export declare const KNOWN_BROADSIDE_TOKENS: readonly ["submit", "collect", "status", "models", "verify", "architecture", "api", "security", "defect", "conventions", "porting", "--incremental", "--no-incremental", "--max-cost=", "--wait=", "--run=", "--model=", "--lens-model=", "--top=", "--no-synthesis", "--no-triage", "--no-retry-truncated", "--benchmarks"];
35
+ export declare const KNOWN_BROADSIDE_TOKENS: readonly ["submit", "collect", "status", "models", "verify", "architecture", "api", "security", "defect", "conventions", "porting", "--incremental", "--no-incremental", "--max-cost=", "--wait=", "--run=", "--model=", "--lens-model=", "--top=", "--regenerate", "--no-synthesis", "--no-triage", "--no-retry-truncated", "--benchmarks"];
34
36
  export declare function parseBroadsideFlags(args: string): BroadsideFlags;
@@ -7,6 +7,7 @@
7
7
  // /codecarto-broadside status
8
8
  // /codecarto-broadside models --benchmarks
9
9
  // /codecarto-broadside verify --top=10 → read the top findings against the source
10
+ // /codecarto-broadside collect --regenerate → rebuild synthesis and triage from the verdicts
10
11
  //
11
12
  // Flags mirror the codecarto_broadside tool parameters, with the negative
12
13
  // forms spelled out because a slash command has no place to pass `false`:
@@ -18,6 +19,7 @@
18
19
  // --model=ID (submit: the run's batch model, as listed by models; verify: the sync model to read with)
19
20
  // --lens-model=LENS:ID (submit only, repeatable: one lens on its own model)
20
21
  // --top=N (verify only: how many findings to read, most severe first)
22
+ // --regenerate (collect only: reset the settled post-passes and run them again, verdicts included)
21
23
  //
22
24
  // A model id itself contains a colon (`vendor/name:batch`), so --lens-model
23
25
  // splits on the first colon only: `security:deepseek/deepseek-v4-pro:batch`.
@@ -47,6 +49,7 @@ export const KNOWN_BROADSIDE_TOKENS = [
47
49
  "--model=",
48
50
  "--lens-model=",
49
51
  "--top=",
52
+ "--regenerate",
50
53
  "--no-synthesis",
51
54
  "--no-triage",
52
55
  "--no-retry-truncated",
@@ -107,6 +110,10 @@ export function parseBroadsideFlags(args) {
107
110
  result.retryTruncated = false;
108
111
  continue;
109
112
  }
113
+ if (token === "--regenerate") {
114
+ result.regeneratePostPasses = true;
115
+ continue;
116
+ }
110
117
  if (token === "--benchmarks") {
111
118
  result.benchmarks = true;
112
119
  continue;
@@ -188,6 +195,12 @@ export function parseBroadsideFlags(args) {
188
195
  if (result.top !== undefined && result.action !== "verify") {
189
196
  result.error ??= `--top is only meaningful for verify (got action "${result.action}").`;
190
197
  }
198
+ if (result.regeneratePostPasses && result.action !== "collect") {
199
+ result.error ??= `--regenerate is only meaningful for collect (got action "${result.action}").`;
200
+ }
201
+ if (result.regeneratePostPasses && result.includeSynthesis === false && result.includeTriage === false) {
202
+ result.error ??= "--regenerate with both --no-synthesis and --no-triage leaves nothing to regenerate.";
203
+ }
191
204
  if (result.action === "verify" && result.waitSeconds !== undefined) {
192
205
  result.error ??= "--wait is only meaningful for submit and collect; verify runs to completion.";
193
206
  }
@@ -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";
@@ -11,7 +11,7 @@ import { buildPiGuideMessage } from "./guide-framing.js";
11
11
  import { isCtxLive, notifyCtx } from "./notify.js";
12
12
  import { phaseCompactionExtension } from "./phase-compaction.js";
13
13
  import { applyAmendment, buildPhasePrompt, buildSkillPrompt, buildValidationSummary, backupWorkspaceState, canonicalPath, copyPackagedWorkspace, computePerPhaseTotals, computeTotals, ConfidentialityMismatchError, createEmptyStatus, DEFAULT_PIPELINE_PATH, describeScaffoldStaleness, deriveSlug, discoverLibrary, describeDanglingCarryForward, describeMissingCompletedOutputs, describeStuckPipeline, expandTilde, getPipelineLabel, getWorkspaceState, isWithinPath, resolveExistingPrefix, BROADSIDE_LENS_IDS, BROADSIDE_SKILL_NAME, BroadsideConfigError, defaultBroadsideConfig, BroadsideCancelledError, broadsideDirFor, collectResultText, estimateSubmitText, getLens, listAmendmentNames, listBatchModels, listGuideTopics, listScaffoldRefreshFiles, listMissingCompletedOutputs, listSkillNames, resolvePipelineOutcome, resolveSkillName, loadAmendmentFile, loadBroadsideConfig, modelsText, runBroadsideCollect, runBroadsideStatus, runBroadsideSubmit, runBroadsideVerify, verifyResultText, statusText, describeConfigProblems, describeIncrementalFallback, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, packagedWorkspaceDir, pathExists, PACKAGE_VERSION, readBroadsideSkill, readGuide, refreshScaffold, PhasePreflightError, PIPELINE_ALIASES, publishEntry, resolvePhase, resolvePipelineChoice, resolvePublishSourceRepo, SourceRepoMismatchError, runPhasePreflight, SCAFFOLD_REFRESH_PROTECTED, seedOrchestratorFiles, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, writeDashboard, } from "../../core/index.js";
14
- import { initLibrary } from "../../core/library.js";
14
+ import { initLibrary, isValidSlug } from "../../core/library.js";
15
15
  import { resolveUserConfigPath } from "../../core/orchestrator-config.js";
16
16
  const STATUS_WIDGET_ID = "codecarto-widget";
17
17
  // Broad-Side gets its own widget id: a scout run is legal on a repository with
@@ -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";
@@ -1354,12 +1355,41 @@ export default function codeCartographerExtension(pi) {
1354
1355
  pi.registerCommand("codecarto-library-init", {
1355
1356
  description: "Initialize a CodeCartographer library and configure it: /codecarto-library-init <path> [--namespace <name>]",
1356
1357
  handler: async (args, ctx) => {
1357
- const parts = args.trim().split(/\s+/);
1358
- const pathArg = parts[0];
1358
+ const usage = "Usage: /codecarto-library-init <path> [--namespace <name>]";
1359
+ const parts = args.trim() === "" ? [] : args.trim().split(/\s+/);
1360
+ // `--namespace` with nothing after it used to read as "no namespace"
1361
+ // and initialize an unnamespaced library without a word (Broad-Side
1362
+ // verify, 2026-09-13 run): the flag is either complete or refused.
1359
1363
  const namespaceIdx = parts.indexOf("--namespace");
1360
- const namespace = namespaceIdx >= 0 ? parts[namespaceIdx + 1] : null;
1364
+ let namespace = null;
1365
+ if (namespaceIdx >= 0) {
1366
+ const value = parts[namespaceIdx + 1];
1367
+ if (value === undefined || value.startsWith("--")) {
1368
+ notifyCtx(ctx, `--namespace needs a name. ${usage}`, "warning");
1369
+ return;
1370
+ }
1371
+ // The rule publish applies to it later, applied before it is
1372
+ // written into the config: lowercase ASCII, starts with a
1373
+ // letter, at most 64 characters.
1374
+ if (!isValidSlug(value)) {
1375
+ notifyCtx(ctx, `Invalid namespace "${value}" (lowercase ASCII, starts with a letter, max 64 chars). ${usage}`, "warning");
1376
+ return;
1377
+ }
1378
+ namespace = value;
1379
+ parts.splice(namespaceIdx, 2);
1380
+ }
1381
+ const stray = parts.find((part) => part.startsWith("--"));
1382
+ if (stray) {
1383
+ notifyCtx(ctx, `Unknown flag ${stray}. ${usage}`, "warning");
1384
+ return;
1385
+ }
1386
+ const [pathArg, ...extra] = parts;
1361
1387
  if (!pathArg) {
1362
- notifyCtx(ctx, "Usage: /codecarto-library-init <path> [--namespace <name>]", "warning");
1388
+ notifyCtx(ctx, usage, "warning");
1389
+ return;
1390
+ }
1391
+ if (extra.length > 0) {
1392
+ notifyCtx(ctx, `One path, please — got ${parts.length}. ${usage}`, "warning");
1363
1393
  return;
1364
1394
  }
1365
1395
  // The same expansion the config loader applies, so `~user/x` and a
@@ -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";
@@ -808,6 +808,11 @@ export async function handleLibraryInit(args) {
808
808
  }
809
809
  const libraryPath = args.library_path;
810
810
  const namespaced = !!args.namespace;
811
+ // The rule codecarto_publish applies to the namespace later, applied
812
+ // before it is written into the config.
813
+ if (namespaced && !isValidSlug(args.namespace)) {
814
+ throw new McpError(ErrorCode.InvalidParams, `Invalid namespace "${args.namespace}" (lowercase ASCII, starts with a letter, max 64 chars).`);
815
+ }
811
816
  const result = await initLibrary(libraryPath, {
812
817
  name: args.name,
813
818
  namespaced,
@@ -1207,6 +1212,12 @@ export async function handleBroadside(args) {
1207
1212
  });
1208
1213
  }
1209
1214
  // action === "collect"
1215
+ if (args.regenerate_post_passes !== undefined && typeof args.regenerate_post_passes !== "boolean") {
1216
+ throw new McpError(ErrorCode.InvalidParams, "regenerate_post_passes must be a boolean.");
1217
+ }
1218
+ if (args.regenerate_post_passes && !includeSynthesis && !includeTriage) {
1219
+ throw new McpError(ErrorCode.InvalidParams, "regenerate_post_passes needs at least one of include_synthesis and include_triage.");
1220
+ }
1210
1221
  const collect = await runBroadsideCollect(cwd, apiKey, {
1211
1222
  waitMs,
1212
1223
  includeSynthesis,
@@ -1214,6 +1225,7 @@ export async function handleBroadside(args) {
1214
1225
  retryTruncated,
1215
1226
  signal: serverLifetime?.signal,
1216
1227
  ...(runId && { runId }),
1228
+ ...(args.regenerate_post_passes && { regeneratePostPasses: true }),
1217
1229
  }).catch((error) => {
1218
1230
  throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
1219
1231
  });
@@ -1227,6 +1239,7 @@ export async function handleBroadside(args) {
1227
1239
  lensOutcomes: collect.lensOutcomes,
1228
1240
  synthesis: collect.synthesis,
1229
1241
  triage: collect.triage,
1242
+ ...(collect.regenerated && { regenerated: collect.regenerated }),
1230
1243
  topFindings: collect.topFindings,
1231
1244
  topTriageItems: collect.topTriageItems,
1232
1245
  });
@@ -1535,7 +1548,7 @@ const TOOLS = [
1535
1548
  action: {
1536
1549
  type: "string",
1537
1550
  enum: ["submit", "collect", "status", "models", "verify"],
1538
- 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.",
1551
+ 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.",
1539
1552
  },
1540
1553
  lenses: {
1541
1554
  type: "array",
@@ -1570,6 +1583,10 @@ const TOOLS = [
1570
1583
  type: "boolean",
1571
1584
  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).",
1572
1585
  },
1586
+ regenerate_post_passes: {
1587
+ type: "boolean",
1588
+ 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.",
1589
+ },
1573
1590
  max_cost: {
1574
1591
  type: "number",
1575
1592
  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.0",
3
+ "version": "0.25.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",