codecartographer-pi 0.20.0 → 0.22.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 (40) hide show
  1. package/.codecarto/GUIDE.md +1 -1
  2. package/.codecarto/broadside/SKILL.md +15 -4
  3. package/.codecarto/broadside/config.yaml +26 -10
  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 +5 -5
  17. package/agent-skill/codecartographer/references/broadside.md +10 -0
  18. package/dist/core/amendment.js +9 -4
  19. package/dist/core/broadside.d.ts +98 -0
  20. package/dist/core/broadside.js +330 -63
  21. package/dist/core/completion.js +7 -1
  22. package/dist/core/library.js +5 -3
  23. package/dist/core/pipeline.d.ts +39 -3
  24. package/dist/core/pipeline.js +61 -7
  25. package/dist/core/prompts.js +10 -3
  26. package/dist/core/status.d.ts +8 -0
  27. package/dist/core/status.js +39 -17
  28. package/dist/core/utils.d.ts +7 -0
  29. package/dist/core/utils.js +7 -0
  30. package/dist/core/workspace.js +2 -2
  31. package/dist/core/yaml.js +8 -1
  32. package/dist/extensions/codecarto/auto-runner.d.ts +1 -1
  33. package/dist/extensions/codecarto/auto-runner.js +18 -3
  34. package/dist/extensions/codecarto/broadside-flags.d.ts +7 -1
  35. package/dist/extensions/codecarto/broadside-flags.js +51 -0
  36. package/dist/extensions/codecarto/index.js +103 -35
  37. package/dist/extensions/codecarto/phase-compaction.js +5 -1
  38. package/dist/mcp-server/server.d.ts +5 -1
  39. package/dist/mcp-server/server.js +140 -32
  40. package/package.json +1 -1
@@ -1,5 +1,4 @@
1
1
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
- import { homedir } from "node:os";
3
2
  import { basename, dirname, join, resolve } from "node:path";
4
3
  import { autoCompletePhase, buildAutoSummary, isPhaseRunning, runAuto, runSinglePhase } from "./auto-runner.js";
5
4
  import { disposeAgentsWidget } from "./agent-widget.js";
@@ -10,7 +9,7 @@ import { parseNextFlags } from "./next-flags.js";
10
9
  import { buildPiGuideMessage } from "./guide-framing.js";
11
10
  import { isCtxLive, notifyCtx } from "./notify.js";
12
11
  import { phaseCompactionExtension } from "./phase-compaction.js";
13
- import { applyAmendment, buildPhasePrompt, buildSkillPrompt, buildValidationSummary, backupWorkspaceState, canonicalPath, copyPackagedWorkspace, computePerPhaseTotals, computeTotals, ConfidentialityMismatchError, createEmptyStatus, DEFAULT_PIPELINE_PATH, describeScaffoldStaleness, deriveSlug, discoverLibrary, describeDanglingCarryForward, describeMissingCompletedOutputs, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPath, resolveExistingPrefix, BROADSIDE_LENS_IDS, BROADSIDE_SKILL_NAME, BroadsideCancelledError, broadsideDirFor, collectResultText, estimateSubmitText, getLens, listAmendmentNames, listBatchModels, listGuideTopics, listScaffoldRefreshFiles, listMissingCompletedOutputs, listSkillNames, resolveSkillName, loadAmendmentFile, loadBroadsideConfig, modelsText, runBroadsideCollect, runBroadsideStatus, runBroadsideSubmit, statusText, describeConfigProblems, 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";
12
+ 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, statusText, describeConfigProblems, 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
13
  import { initLibrary } from "../../core/library.js";
15
14
  import { resolveUserConfigPath } from "../../core/orchestrator-config.js";
16
15
  const STATUS_WIDGET_ID = "codecarto-widget";
@@ -56,9 +55,23 @@ function formatUsageDuration(ms) {
56
55
  const seconds = Math.floor((ms % 60_000) / 1000);
57
56
  return `${minutes}m${seconds.toString().padStart(2, "0")}s`;
58
57
  }
58
+ /**
59
+ * The phase the status line and session name show: the eligible phase, the
60
+ * first blocked phase when the pipeline is stuck, or "complete" — never
61
+ * "complete" for a pipeline that cannot finish (#228).
62
+ */
63
+ function currentPhaseLabel(state) {
64
+ const outcome = resolvePipelineOutcome(state);
65
+ if (outcome.kind === "eligible")
66
+ return outcome.phase.id;
67
+ if (outcome.kind === "stuck")
68
+ return outcome.blocked[0]?.phaseId ?? "stuck";
69
+ return "complete";
70
+ }
59
71
  function buildStatusLines(state, extraLines = []) {
60
- const nextPhase = getNextEligiblePhase(state);
61
- const currentPhase = nextPhase?.id ?? state.status.current_phase ?? "complete";
72
+ const outcome = resolvePipelineOutcome(state);
73
+ const nextPhase = outcome.kind === "eligible" ? outcome.phase : null;
74
+ const currentPhase = currentPhaseLabel(state);
62
75
  const pipelineLabel = getPipelineLabel(state.status.pipeline);
63
76
  const completedCount = state.pipeline.phase_order.filter((phaseId) => state.status.phases[phaseId]?.status === "complete").length;
64
77
  const terminalOpenQuestions = Object.values(state.status.phases).reduce((sum, phase) => sum + (phase.open_questions?.length ?? 0), 0);
@@ -68,7 +81,7 @@ function buildStatusLines(state, extraLines = []) {
68
81
  const lines = [
69
82
  "CodeCartographer",
70
83
  `Phase: ${currentPhase}`,
71
- `Pipeline state: ${currentPhase === "complete" ? "complete" : "in progress"}`,
84
+ `Pipeline state: ${outcome.kind === "eligible" ? "in progress" : outcome.kind}`,
72
85
  `Pipeline: ${pipelineLabel}`,
73
86
  `Progress: ${completedCount}/${state.pipeline.phase_order.length} complete`,
74
87
  `Open questions (terminal unresolved): ${terminalOpenQuestions}`,
@@ -76,6 +89,8 @@ function buildStatusLines(state, extraLines = []) {
76
89
  `Post-pipeline work: ${postPipelinePending} pending`,
77
90
  `Next: ${nextAction}`,
78
91
  ];
92
+ if (outcome.kind === "stuck")
93
+ lines.push(describeStuckPipeline(outcome.blocked));
79
94
  const scaffoldNotice = describeScaffoldStaleness(state);
80
95
  if (scaffoldNotice)
81
96
  lines.push(`Scaffold: ${scaffoldNotice}`);
@@ -93,7 +108,7 @@ function setUiState(ctx, state, extraLines = []) {
93
108
  return;
94
109
  }
95
110
  const theme = ctx.ui.theme;
96
- const currentPhase = getNextEligiblePhase(state)?.id ?? state.status.current_phase ?? "complete";
111
+ const currentPhase = currentPhaseLabel(state);
97
112
  ctx.ui.setStatus(STATUS_LINE_ID, `${theme.fg("accent", "CC")} ${theme.fg("dim", currentPhase)}`);
98
113
  ctx.ui.setWidget(STATUS_WIDGET_ID, buildStatusLines(state, extraLines));
99
114
  }
@@ -291,7 +306,7 @@ export default function codeCartographerExtension(pi) {
291
306
  const state = await readWorkspaceState(ctx, false);
292
307
  setUiState(ctx, state, extraLines ?? lastFeedbackLines);
293
308
  if (state) {
294
- const phaseId = getNextEligiblePhase(state)?.id ?? state.status.current_phase;
309
+ const phaseId = currentPhaseLabel(state);
295
310
  if (phaseId)
296
311
  pi.setSessionName(`CodeCartographer: ${phaseId}`);
297
312
  }
@@ -498,13 +513,17 @@ export default function codeCartographerExtension(pi) {
498
513
  const state = await ensureWorkspaceState(ctx);
499
514
  if (!state)
500
515
  return;
501
- const nextPhase = getNextEligiblePhase(state)?.id ?? "complete";
516
+ const outcome = resolvePipelineOutcome(state);
517
+ const nextPhase = currentPhaseLabel(state);
502
518
  // Same check codecarto_status makes: a phase complete in status.yaml
503
519
  // whose report is not on disk (#259).
504
520
  const missingOutputs = describeMissingCompletedOutputs(await listMissingCompletedOutputs(state));
505
521
  lastFeedbackLines = [`Current phase: ${nextPhase}`, `Pipeline: ${getPipelineLabel(state.status.pipeline)}`, ...missingOutputs];
506
522
  setUiState(ctx, state, lastFeedbackLines);
507
- if (missingOutputs.length > 0) {
523
+ if (outcome.kind === "stuck") {
524
+ notifyCtx(ctx, `CodeCartographer phase: ${nextPhase}. ${describeStuckPipeline(outcome.blocked)}`, "error");
525
+ }
526
+ else if (missingOutputs.length > 0) {
508
527
  notifyCtx(ctx, `CodeCartographer phase: ${nextPhase}. ${missingOutputs[0]}`, "warning");
509
528
  }
510
529
  else {
@@ -608,7 +627,7 @@ export default function codeCartographerExtension(pi) {
608
627
  // the readout tracks progress live instead of staying frozen
609
628
  // at the initial phase until the whole auto run finishes.
610
629
  setUiState(ctx, advancedState, [`Auto pipeline${flags.strict ? " (strict)" : ""} running…`]);
611
- const phaseId = getNextEligiblePhase(advancedState)?.id ?? advancedState.status.current_phase;
630
+ const phaseId = currentPhaseLabel(advancedState);
612
631
  if (phaseId)
613
632
  pi.setSessionName(`CodeCartographer: ${phaseId}`);
614
633
  },
@@ -624,13 +643,21 @@ export default function codeCartographerExtension(pi) {
624
643
  notifyCtx(ctx, `Auto pipeline ${result.outcome}: ${result.phasesRun.length}/${result.totalPhases} phases.`, result.outcome === "complete" ? "info" : "warning");
625
644
  return;
626
645
  }
627
- const phase = getNextEligiblePhase(state);
628
- if (!phase) {
646
+ const outcome = resolvePipelineOutcome(state);
647
+ if (outcome.kind === "stuck") {
648
+ const message = describeStuckPipeline(outcome.blocked);
649
+ lastFeedbackLines = [message];
650
+ setUiState(ctx, state, lastFeedbackLines);
651
+ notifyCtx(ctx, message, "error");
652
+ return;
653
+ }
654
+ if (outcome.kind === "complete") {
629
655
  lastFeedbackLines = ["All phases complete."];
630
656
  setUiState(ctx, state, lastFeedbackLines);
631
657
  notifyCtx(ctx, "All CodeCartographer phases are complete.", "info");
632
658
  return;
633
659
  }
660
+ const phase = outcome.phase;
634
661
  let preflight;
635
662
  try {
636
663
  preflight = await runPhasePreflight(state, phase);
@@ -868,9 +895,13 @@ export default function codeCartographerExtension(pi) {
868
895
  const state = await ensureWorkspaceState(ctx);
869
896
  if (!state)
870
897
  return;
871
- const nextPhase = getNextEligiblePhase(state);
872
- if (nextPhase) {
873
- notifyCtx(ctx, `Cannot run skill: pipeline is not complete (next phase: ${nextPhase.id}). Finish the pipeline before running post-pipeline skills.`, "error");
898
+ const outcome = resolvePipelineOutcome(state);
899
+ if (outcome.kind === "eligible") {
900
+ notifyCtx(ctx, `Cannot run skill: pipeline is not complete (next phase: ${outcome.phase.id}). Finish the pipeline before running post-pipeline skills.`, "error");
901
+ return;
902
+ }
903
+ if (outcome.kind === "stuck") {
904
+ notifyCtx(ctx, `Cannot run skill: the pipeline is not complete. ${describeStuckPipeline(outcome.blocked)}`, "error");
874
905
  return;
875
906
  }
876
907
  // Resolve against the installed list only: the name is never joined
@@ -908,11 +939,13 @@ export default function codeCartographerExtension(pi) {
908
939
  const lines = skills.length > 0
909
940
  ? [`Available skills (${skills.length}):`, ...skills.map((name) => ` - ${name}`)]
910
941
  : ["No skills installed."];
911
- const nextPhase = getNextEligiblePhase(state);
942
+ const outcome = resolvePipelineOutcome(state);
912
943
  if (skills.length > 0) {
913
- lines.push(nextPhase
914
- ? `Post-pipeline skills unlock when the pipeline completes (next phase: ${nextPhase.id}).`
915
- : "Run one with /codecarto-skill <name>.");
944
+ lines.push(outcome.kind === "eligible"
945
+ ? `Post-pipeline skills unlock when the pipeline completes (next phase: ${outcome.phase.id}).`
946
+ : outcome.kind === "stuck"
947
+ ? `Post-pipeline skills unlock when the pipeline completes, and it cannot: ${describeStuckPipeline(outcome.blocked)}`
948
+ : "Run one with /codecarto-skill <name>.");
916
949
  }
917
950
  // Broad-Side is listed apart from the post-pipeline set because it
918
951
  // answers to /codecarto-skill without the completion gate.
@@ -967,7 +1000,7 @@ export default function codeCartographerExtension(pi) {
967
1000
  },
968
1001
  });
969
1002
  pi.registerCommand("codecarto-broadside", {
970
- description: "Batch reconnaissance (Broad-Side): /codecarto-broadside [submit|collect|status|models] [lenses…] [flags]",
1003
+ description: "Batch reconnaissance (Broad-Side): /codecarto-broadside [submit|collect|status|models] [lenses…] [--model=ID] [--lens-model=LENS:ID] [flags]",
971
1004
  getArgumentCompletions: (prefix) => {
972
1005
  const items = KNOWN_BROADSIDE_TOKENS
973
1006
  .filter((value) => value.startsWith(prefix))
@@ -988,7 +1021,23 @@ export default function codeCartographerExtension(pi) {
988
1021
  // Broad-Side runs on any git repository, with or without a workspace —
989
1022
  // so this command never goes through ensureWorkspaceState.
990
1023
  const broadsideDir = broadsideDirFor(ctx.cwd);
991
- const config = await loadBroadsideConfig(broadsideDir);
1024
+ // A config.yaml that exists but cannot be read refuses every action
1025
+ // that would act on it (#232); status only reads recorded runs, so it
1026
+ // answers and says the file is unreadable.
1027
+ let config;
1028
+ let configWarning = null;
1029
+ try {
1030
+ config = await loadBroadsideConfig(broadsideDir);
1031
+ }
1032
+ catch (error) {
1033
+ const message = error instanceof Error ? error.message : String(error);
1034
+ if (!(error instanceof BroadsideConfigError) || flags.action !== "status") {
1035
+ notifyCtx(ctx, `Broad-Side ${flags.action} refused: ${message}`, "error");
1036
+ return;
1037
+ }
1038
+ config = defaultBroadsideConfig();
1039
+ configWarning = message;
1040
+ }
992
1041
  const finish = (lines, notice, level = "info") => {
993
1042
  lastFeedbackLines = lines;
994
1043
  if (codecartoModeActive) {
@@ -1005,9 +1054,18 @@ export default function codeCartographerExtension(pi) {
1005
1054
  notifyCtx(ctx, notice, level);
1006
1055
  };
1007
1056
  if (flags.action === "status") {
1008
- const { state } = await runBroadsideStatus(ctx.cwd);
1057
+ let state;
1058
+ try {
1059
+ ({ state } = await runBroadsideStatus(ctx.cwd));
1060
+ }
1061
+ catch (error) {
1062
+ // A corrupt state.json: nothing trustworthy to report, and the
1063
+ // error names the preserved copy (#233).
1064
+ notifyCtx(ctx, `Broad-Side status failed: ${error instanceof Error ? error.message : String(error)}`, "error");
1065
+ return;
1066
+ }
1009
1067
  const runs = state.runs.length;
1010
- finish(statusText(state).split("\n"), runs > 0 ? `Broad-Side: ${runs} recorded run${runs === 1 ? "" : "s"}` : "Broad-Side: no runs recorded yet");
1068
+ finish([...statusText(state).split("\n"), ...(configWarning ? [`Warning: ${configWarning}`] : [])], runs > 0 ? `Broad-Side: ${runs} recorded run${runs === 1 ? "" : "s"}` : "Broad-Side: no runs recorded yet", configWarning ? "warning" : "info");
1011
1069
  return;
1012
1070
  }
1013
1071
  const apiKey = resolveBroadsideKey(config.apiKey);
@@ -1019,10 +1077,10 @@ export default function codeCartographerExtension(pi) {
1019
1077
  if (flags.action === "models") {
1020
1078
  notifyCtx(ctx, "Fetching the OpenRouter batch-model catalog…", "info");
1021
1079
  try {
1022
- const { entries, benchmarks } = await listBatchModels(broadsideDir, config, apiKey, {
1080
+ const { entries, benchmarks, endpoints } = await listBatchModels(broadsideDir, config, apiKey, {
1023
1081
  includeBenchmarks: flags.benchmarks,
1024
1082
  });
1025
- finish(modelsText(entries, { benchmarks, defaultModel: config.model }).split("\n"), `Broad-Side: ${entries.length} batch model${entries.length === 1 ? "" : "s"} listed`);
1083
+ finish(modelsText(entries, { benchmarks, defaultModel: config.model, endpoints }).split("\n"), `Broad-Side: ${entries.length} batch model${entries.length === 1 ? "" : "s"} listed`);
1026
1084
  }
1027
1085
  catch (error) {
1028
1086
  notifyCtx(ctx, `Model catalog lookup failed: ${error instanceof Error ? error.message : String(error)}`, "error");
@@ -1045,8 +1103,10 @@ export default function codeCartographerExtension(pi) {
1045
1103
  progress.set(lensId, `${status} (${counts.completed ?? 0}/${counts.total ?? "?"})`);
1046
1104
  renderProgress("Polling batches…");
1047
1105
  };
1106
+ // An explicit --wait=0 polls once and returns; it used to become
1107
+ // undefined and, in core, the 25-minute budget (#230).
1048
1108
  const waitSeconds = flags.waitSeconds ?? config.waitSeconds;
1049
- const waitMs = waitSeconds > 0 ? waitSeconds * 1000 : undefined;
1109
+ const waitMs = waitSeconds * 1000;
1050
1110
  const includeSynthesis = flags.includeSynthesis ?? config.includeSynthesis;
1051
1111
  const includeTriage = flags.includeTriage ?? config.includeTriage;
1052
1112
  const retryTruncated = flags.retryTruncated ?? config.retryTruncated;
@@ -1057,7 +1117,11 @@ export default function codeCartographerExtension(pi) {
1057
1117
  try {
1058
1118
  submit = await runBroadsideSubmit(ctx.cwd, apiKey, {
1059
1119
  lenses,
1060
- model: config.model,
1120
+ // --model= and --lens-model= select for this run; the file's
1121
+ // values are the fallback, and core pre-flights either the
1122
+ // same way (#141).
1123
+ model: flags.model ?? config.model,
1124
+ lensModels: flags.lensModels,
1061
1125
  maxCost: flags.maxCost ?? config.maxCost,
1062
1126
  // `??`, not `||`: --no-incremental parses to false and must beat a
1063
1127
  // config-set true, exactly as MCP's `incremental: false` does (#163).
@@ -1113,6 +1177,7 @@ export default function codeCartographerExtension(pi) {
1113
1177
  includeTriage,
1114
1178
  retryTruncated,
1115
1179
  onStatus,
1180
+ ...(flags.runId && { runId: flags.runId }),
1116
1181
  });
1117
1182
  const lines = collectResultText(collect).split("\n");
1118
1183
  const done = collect.status === "completed";
@@ -1255,12 +1320,11 @@ export default function codeCartographerExtension(pi) {
1255
1320
  notifyCtx(ctx, "Usage: /codecarto-library-init <path> [--namespace <name>]", "warning");
1256
1321
  return;
1257
1322
  }
1258
- const libraryPath = pathArg.startsWith("~") ? join(homedir(), pathArg.slice(1)) : resolve(pathArg);
1323
+ // The same expansion the config loader applies, so `~user/x` and a
1324
+ // bare `~` mean the same thing everywhere (self-audit mech 6.14).
1325
+ const libraryPath = resolve(expandTilde(pathArg));
1259
1326
  try {
1260
- const result = await initLibrary(libraryPath, {
1261
- namespaced: !!namespace,
1262
- ...(namespace ? {} : {}),
1263
- });
1327
+ const result = await initLibrary(libraryPath, { namespaced: !!namespace });
1264
1328
  // Write library.path (and library.namespace when given) to the
1265
1329
  // user-global config and nothing else — not publish_confirm, so
1266
1330
  // initializing a library does not change how publish behaves on
@@ -1459,9 +1523,13 @@ export default function codeCartographerExtension(pi) {
1459
1523
  notifyCtx(ctx, error instanceof Error ? error.message : String(error), "error");
1460
1524
  return;
1461
1525
  }
1462
- const nextPhase = getNextEligiblePhase(state);
1463
- if (nextPhase) {
1464
- notifyCtx(ctx, `Cannot amend: the pipeline is not complete (next phase: ${nextPhase.id}). `
1526
+ const outcome = resolvePipelineOutcome(state);
1527
+ if (outcome.kind === "stuck") {
1528
+ notifyCtx(ctx, `Cannot amend: the pipeline is not complete. ${describeStuckPipeline(outcome.blocked)}`, "error");
1529
+ return;
1530
+ }
1531
+ if (outcome.kind === "eligible") {
1532
+ notifyCtx(ctx, `Cannot amend: the pipeline is not complete (next phase: ${outcome.phase.id}). `
1465
1533
  + "Resolve open questions and routed items through that phase's handoff (open_question_closures / carry_forward_closures) instead.", "error");
1466
1534
  return;
1467
1535
  }
@@ -7,7 +7,11 @@ export function phaseIdFromSessionName(sessionName) {
7
7
  if (!sessionName?.startsWith(PHASE_SESSION_PREFIX))
8
8
  return null;
9
9
  const phaseId = sessionName.slice(PHASE_SESSION_PREFIX.length).trim();
10
- return /^[a-z0-9][a-z0-9-]*$/.test(phaseId) ? phaseId : null;
10
+ // The same alphabet assertSafePhaseId admits (core/status.ts). A narrower
11
+ // match here silently dropped the bash block, write confinement, and
12
+ // checkpointing for a custom pipeline whose ids used `.`, `_`, or a capital
13
+ // (self-audit mech 6.11).
14
+ return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(phaseId) ? phaseId : null;
11
15
  }
12
16
  export function buildPhaseCompactionInstructions(phaseId, primaryOutput) {
13
17
  const output = primaryOutput ? `.codecarto/${primaryOutput}` : "the phase's declared primary output";
@@ -37,6 +37,7 @@ export declare function handleSwitchPipeline(args: {
37
37
  }>;
38
38
  export declare function handleNext(args: {
39
39
  cwd: string;
40
+ unattended?: boolean;
40
41
  }): Promise<{
41
42
  content: {
42
43
  type: "text";
@@ -49,6 +50,7 @@ export declare function handleNext(args: {
49
50
  export declare function handlePhase(args: {
50
51
  cwd: string;
51
52
  phase: string;
53
+ unattended?: boolean;
52
54
  }): Promise<{
53
55
  content: {
54
56
  type: "text";
@@ -130,7 +132,6 @@ export declare function handleLibraryInit(args: {
130
132
  library_path: string;
131
133
  name?: string;
132
134
  namespace?: string;
133
- cwd?: string;
134
135
  }): Promise<{
135
136
  content: {
136
137
  type: "text";
@@ -236,6 +237,7 @@ export declare function handleBroadside(args: {
236
237
  lenses?: string[];
237
238
  api_key?: string;
238
239
  wait_seconds?: number;
240
+ run_id?: string;
239
241
  include_synthesis?: boolean;
240
242
  include_triage?: boolean;
241
243
  retry_truncated?: boolean;
@@ -243,6 +245,8 @@ export declare function handleBroadside(args: {
243
245
  force?: boolean;
244
246
  include_benchmarks?: boolean;
245
247
  incremental?: boolean;
248
+ model?: string;
249
+ lens_models?: Record<string, string>;
246
250
  }): Promise<{
247
251
  content: {
248
252
  type: "text";