codecartographer-pi 0.19.6 → 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.
- package/.codecarto/GUIDE.md +2 -2
- package/.codecarto/broadside/SKILL.md +21 -3
- package/.codecarto/broadside/config.yaml +35 -9
- package/.codecarto/findings/contracts/SKILL.md +4 -1
- package/.codecarto/findings/defect-scan/SKILL.md +10 -0
- package/.codecarto/findings/defect-scan-mechanical/SKILL.md +6 -0
- package/.codecarto/findings/defect-scan-semantic/SKILL.md +8 -1
- package/.codecarto/findings/porting/SKILL.md +4 -0
- package/.codecarto/findings/protocols/SKILL.md +4 -0
- package/.codecarto/templates/mechanical-defects.md +15 -0
- package/.codecarto/templates/reimplementation-spec.md +5 -3
- package/.codecarto/templates/reverse-engineering-bundle.md +10 -1
- package/.codecarto/templates/semantic-defects.md +15 -0
- package/.codecarto/workflow/VALIDATE.md +3 -2
- package/.codecarto/workflow/scaffold-version.yaml +1 -1
- package/README.md +13 -9
- package/dist/core/amendment.js +9 -4
- package/dist/core/broadside.d.ts +121 -2
- package/dist/core/broadside.js +478 -92
- package/dist/core/completion.js +88 -23
- package/dist/core/dashboard-writer.d.ts +8 -0
- package/dist/core/dashboard-writer.js +159 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/library.js +9 -4
- package/dist/core/orchestrator-config.d.ts +32 -7
- package/dist/core/orchestrator-config.js +124 -44
- package/dist/core/pipeline.d.ts +73 -0
- package/dist/core/pipeline.js +134 -10
- package/dist/core/prompts.d.ts +20 -0
- package/dist/core/prompts.js +53 -13
- package/dist/core/secrets.d.ts +16 -0
- package/dist/core/secrets.js +98 -0
- package/dist/core/status.d.ts +16 -0
- package/dist/core/status.js +47 -20
- package/dist/core/synthesis.js +5 -2
- package/dist/core/utils.d.ts +7 -0
- package/dist/core/utils.js +7 -0
- package/dist/core/workspace.d.ts +55 -8
- package/dist/core/workspace.js +116 -8
- package/dist/core/yaml.js +181 -15
- package/dist/extensions/codecarto/agent-rewriter.js +21 -14
- package/dist/extensions/codecarto/agent-runner.d.ts +6 -2
- package/dist/extensions/codecarto/agent-runner.js +27 -9
- package/dist/extensions/codecarto/agent-state.d.ts +0 -2
- package/dist/extensions/codecarto/auto-runner.d.ts +1 -1
- package/dist/extensions/codecarto/auto-runner.js +27 -8
- package/dist/extensions/codecarto/broadside-flags.d.ts +3 -1
- package/dist/extensions/codecarto/broadside-flags.js +12 -0
- package/dist/extensions/codecarto/dashboard-narrator.js +9 -2
- package/dist/extensions/codecarto/dashboard-writer.d.ts +1 -8
- package/dist/extensions/codecarto/dashboard-writer.js +5 -154
- package/dist/extensions/codecarto/index.js +158 -47
- package/dist/extensions/codecarto/phase-compaction.js +5 -1
- package/dist/mcp-server/server.d.ts +3 -1
- package/dist/mcp-server/server.js +205 -77
- package/package.json +3 -2
|
@@ -1,17 +1,15 @@
|
|
|
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";
|
|
6
5
|
import { parseDashboardFlags } from "./dashboard-flags.js";
|
|
7
6
|
import { narrateDashboard } from "./dashboard-narrator.js";
|
|
8
|
-
import { writeDashboard } from "./dashboard-writer.js";
|
|
9
7
|
import { parseBroadsideFlags, KNOWN_BROADSIDE_TOKENS } from "./broadside-flags.js";
|
|
10
8
|
import { parseNextFlags } from "./next-flags.js";
|
|
11
9
|
import { buildPiGuideMessage } from "./guide-framing.js";
|
|
12
10
|
import { isCtxLive, notifyCtx } from "./notify.js";
|
|
13
11
|
import { phaseCompactionExtension } from "./phase-compaction.js";
|
|
14
|
-
import { applyAmendment, buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, copyPackagedWorkspace, computePerPhaseTotals, computeTotals, ConfidentialityMismatchError, createEmptyStatus, DEFAULT_PIPELINE_PATH, describeScaffoldStaleness, deriveSlug, discoverLibrary,
|
|
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";
|
|
15
13
|
import { initLibrary } from "../../core/library.js";
|
|
16
14
|
import { resolveUserConfigPath } from "../../core/orchestrator-config.js";
|
|
17
15
|
const STATUS_WIDGET_ID = "codecarto-widget";
|
|
@@ -57,9 +55,23 @@ function formatUsageDuration(ms) {
|
|
|
57
55
|
const seconds = Math.floor((ms % 60_000) / 1000);
|
|
58
56
|
return `${minutes}m${seconds.toString().padStart(2, "0")}s`;
|
|
59
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
|
+
}
|
|
60
71
|
function buildStatusLines(state, extraLines = []) {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
72
|
+
const outcome = resolvePipelineOutcome(state);
|
|
73
|
+
const nextPhase = outcome.kind === "eligible" ? outcome.phase : null;
|
|
74
|
+
const currentPhase = currentPhaseLabel(state);
|
|
63
75
|
const pipelineLabel = getPipelineLabel(state.status.pipeline);
|
|
64
76
|
const completedCount = state.pipeline.phase_order.filter((phaseId) => state.status.phases[phaseId]?.status === "complete").length;
|
|
65
77
|
const terminalOpenQuestions = Object.values(state.status.phases).reduce((sum, phase) => sum + (phase.open_questions?.length ?? 0), 0);
|
|
@@ -69,7 +81,7 @@ function buildStatusLines(state, extraLines = []) {
|
|
|
69
81
|
const lines = [
|
|
70
82
|
"CodeCartographer",
|
|
71
83
|
`Phase: ${currentPhase}`,
|
|
72
|
-
`Pipeline state: ${
|
|
84
|
+
`Pipeline state: ${outcome.kind === "eligible" ? "in progress" : outcome.kind}`,
|
|
73
85
|
`Pipeline: ${pipelineLabel}`,
|
|
74
86
|
`Progress: ${completedCount}/${state.pipeline.phase_order.length} complete`,
|
|
75
87
|
`Open questions (terminal unresolved): ${terminalOpenQuestions}`,
|
|
@@ -77,6 +89,8 @@ function buildStatusLines(state, extraLines = []) {
|
|
|
77
89
|
`Post-pipeline work: ${postPipelinePending} pending`,
|
|
78
90
|
`Next: ${nextAction}`,
|
|
79
91
|
];
|
|
92
|
+
if (outcome.kind === "stuck")
|
|
93
|
+
lines.push(describeStuckPipeline(outcome.blocked));
|
|
80
94
|
const scaffoldNotice = describeScaffoldStaleness(state);
|
|
81
95
|
if (scaffoldNotice)
|
|
82
96
|
lines.push(`Scaffold: ${scaffoldNotice}`);
|
|
@@ -94,7 +108,7 @@ function setUiState(ctx, state, extraLines = []) {
|
|
|
94
108
|
return;
|
|
95
109
|
}
|
|
96
110
|
const theme = ctx.ui.theme;
|
|
97
|
-
const currentPhase =
|
|
111
|
+
const currentPhase = currentPhaseLabel(state);
|
|
98
112
|
ctx.ui.setStatus(STATUS_LINE_ID, `${theme.fg("accent", "CC")} ${theme.fg("dim", currentPhase)}`);
|
|
99
113
|
ctx.ui.setWidget(STATUS_WIDGET_ID, buildStatusLines(state, extraLines));
|
|
100
114
|
}
|
|
@@ -292,7 +306,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
292
306
|
const state = await readWorkspaceState(ctx, false);
|
|
293
307
|
setUiState(ctx, state, extraLines ?? lastFeedbackLines);
|
|
294
308
|
if (state) {
|
|
295
|
-
const phaseId =
|
|
309
|
+
const phaseId = currentPhaseLabel(state);
|
|
296
310
|
if (phaseId)
|
|
297
311
|
pi.setSessionName(`CodeCartographer: ${phaseId}`);
|
|
298
312
|
}
|
|
@@ -437,11 +451,21 @@ export default function codeCartographerExtension(pi) {
|
|
|
437
451
|
const targetExists = await pathExists(targetWorkspaceDir);
|
|
438
452
|
if (targetExists) {
|
|
439
453
|
const sameWorkspace = normalizeForComparison(await canonicalPath(targetWorkspaceDir)) === normalizeForComparison(await canonicalPath(sourceWorkspaceDir));
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
454
|
+
// The packaged template itself (a checkout install) is an existing
|
|
455
|
+
// workspace like any other: ask, and back up. It cannot be renamed
|
|
456
|
+
// away — it is what init copies from — so its session state is
|
|
457
|
+
// moved out file by file instead (#245).
|
|
458
|
+
const overwrite = await ctx.ui.confirm("CodeCartographer already exists — data will be lost", sameWorkspace
|
|
459
|
+
? "This .codecarto/ is CodeCartographer's own packaged template (a checkout install), and it holds workspace state. Re-initializing will move that state — status, findings, handoffs, usage data, closeouts, dashboard — to .codecarto-backup-TIMESTAMP/ and reset; the framework files stay in place. Consider /codecarto-open to reattach without resetting. Continue?"
|
|
460
|
+
: "A .codecarto/ directory already exists in this repository. Re-initializing will back up the existing workspace to .codecarto-backup-TIMESTAMP/ and create a fresh one. All phase findings, handoffs, usage data, closeouts, and progress will be moved to the backup. Consider /codecarto-open to reattach without resetting. Continue?");
|
|
461
|
+
if (!overwrite)
|
|
462
|
+
return;
|
|
463
|
+
const backupDir = join(ctx.cwd, `.codecarto-backup-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
464
|
+
if (sameWorkspace) {
|
|
465
|
+
const moved = await backupWorkspaceState(targetWorkspaceDir, backupDir);
|
|
466
|
+
notifyCtx(ctx, `Moved ${moved.length} workspace state file${moved.length === 1 ? "" : "s"} to ${basename(backupDir)}/`, "info");
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
445
469
|
await rename(targetWorkspaceDir, backupDir);
|
|
446
470
|
notifyCtx(ctx, `Backed up existing workspace to ${basename(backupDir)}/`, "info");
|
|
447
471
|
}
|
|
@@ -489,10 +513,22 @@ export default function codeCartographerExtension(pi) {
|
|
|
489
513
|
const state = await ensureWorkspaceState(ctx);
|
|
490
514
|
if (!state)
|
|
491
515
|
return;
|
|
492
|
-
const
|
|
493
|
-
|
|
516
|
+
const outcome = resolvePipelineOutcome(state);
|
|
517
|
+
const nextPhase = currentPhaseLabel(state);
|
|
518
|
+
// Same check codecarto_status makes: a phase complete in status.yaml
|
|
519
|
+
// whose report is not on disk (#259).
|
|
520
|
+
const missingOutputs = describeMissingCompletedOutputs(await listMissingCompletedOutputs(state));
|
|
521
|
+
lastFeedbackLines = [`Current phase: ${nextPhase}`, `Pipeline: ${getPipelineLabel(state.status.pipeline)}`, ...missingOutputs];
|
|
494
522
|
setUiState(ctx, state, lastFeedbackLines);
|
|
495
|
-
|
|
523
|
+
if (outcome.kind === "stuck") {
|
|
524
|
+
notifyCtx(ctx, `CodeCartographer phase: ${nextPhase}. ${describeStuckPipeline(outcome.blocked)}`, "error");
|
|
525
|
+
}
|
|
526
|
+
else if (missingOutputs.length > 0) {
|
|
527
|
+
notifyCtx(ctx, `CodeCartographer phase: ${nextPhase}. ${missingOutputs[0]}`, "warning");
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
notifyCtx(ctx, `CodeCartographer phase: ${nextPhase}`, "info");
|
|
531
|
+
}
|
|
496
532
|
},
|
|
497
533
|
});
|
|
498
534
|
pi.registerCommand("codecarto-switch-pipeline", {
|
|
@@ -533,6 +569,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
533
569
|
lines.push(`New phases: ${result.newPhases.join(", ")}`);
|
|
534
570
|
if (result.dropped.length > 0)
|
|
535
571
|
lines.push(`Phases not in new pipeline: ${result.dropped.join(", ")} (findings remain on disk)`);
|
|
572
|
+
lines.push(...describeDanglingCarryForward(result.dangling));
|
|
536
573
|
lastFeedbackLines = lines;
|
|
537
574
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
538
575
|
notifyCtx(ctx, `Switched to pipeline: ${getPipelineLabel(pipelineChoice)}`, "info");
|
|
@@ -590,7 +627,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
590
627
|
// the readout tracks progress live instead of staying frozen
|
|
591
628
|
// at the initial phase until the whole auto run finishes.
|
|
592
629
|
setUiState(ctx, advancedState, [`Auto pipeline${flags.strict ? " (strict)" : ""} running…`]);
|
|
593
|
-
const phaseId =
|
|
630
|
+
const phaseId = currentPhaseLabel(advancedState);
|
|
594
631
|
if (phaseId)
|
|
595
632
|
pi.setSessionName(`CodeCartographer: ${phaseId}`);
|
|
596
633
|
},
|
|
@@ -606,13 +643,21 @@ export default function codeCartographerExtension(pi) {
|
|
|
606
643
|
notifyCtx(ctx, `Auto pipeline ${result.outcome}: ${result.phasesRun.length}/${result.totalPhases} phases.`, result.outcome === "complete" ? "info" : "warning");
|
|
607
644
|
return;
|
|
608
645
|
}
|
|
609
|
-
const
|
|
610
|
-
if (
|
|
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") {
|
|
611
655
|
lastFeedbackLines = ["All phases complete."];
|
|
612
656
|
setUiState(ctx, state, lastFeedbackLines);
|
|
613
657
|
notifyCtx(ctx, "All CodeCartographer phases are complete.", "info");
|
|
614
658
|
return;
|
|
615
659
|
}
|
|
660
|
+
const phase = outcome.phase;
|
|
616
661
|
let preflight;
|
|
617
662
|
try {
|
|
618
663
|
preflight = await runPhasePreflight(state, phase);
|
|
@@ -631,6 +676,10 @@ export default function codeCartographerExtension(pi) {
|
|
|
631
676
|
return;
|
|
632
677
|
}
|
|
633
678
|
const config = await loadCodecartoConfig(state.workspaceDir);
|
|
679
|
+
// A bad config file must not block a phase run, but it must not be
|
|
680
|
+
// silent either: the toggle read here may be the one it dropped.
|
|
681
|
+
if (config.problems.length > 0)
|
|
682
|
+
notifyCtx(ctx, describeConfigProblems(config).join("\n"), "warning");
|
|
634
683
|
const llmSteerEnabled = flags.llmSteerOverride ?? config.orchestrator.llm_steer_next_phase;
|
|
635
684
|
lastFeedbackLines = [`Running ${phase.id} phase as sub-agent`];
|
|
636
685
|
setUiState(ctx, state, lastFeedbackLines);
|
|
@@ -846,28 +895,36 @@ export default function codeCartographerExtension(pi) {
|
|
|
846
895
|
const state = await ensureWorkspaceState(ctx);
|
|
847
896
|
if (!state)
|
|
848
897
|
return;
|
|
849
|
-
const
|
|
850
|
-
if (
|
|
851
|
-
notifyCtx(ctx, `Cannot run skill: pipeline is not complete (next phase: ${
|
|
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");
|
|
852
901
|
return;
|
|
853
902
|
}
|
|
854
|
-
|
|
855
|
-
|
|
903
|
+
if (outcome.kind === "stuck") {
|
|
904
|
+
notifyCtx(ctx, `Cannot run skill: the pipeline is not complete. ${describeStuckPipeline(outcome.blocked)}`, "error");
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
// Resolve against the installed list only: the name is never joined
|
|
908
|
+
// onto a path, so a traversal like `../findings/architecture` cannot
|
|
909
|
+
// splice a phase SKILL.md into the post-pipeline prompt. Same rule as
|
|
910
|
+
// the MCP surface's handleSkill.
|
|
911
|
+
const resolved = await resolveSkillName(state.workspaceDir, skillName);
|
|
912
|
+
if (!resolved) {
|
|
856
913
|
const available = await listSkillNames(state.workspaceDir);
|
|
857
914
|
const hint = available.length > 0 ? ` (available: ${available.join(", ")})` : " (no skills installed)";
|
|
858
915
|
notifyCtx(ctx, `Unknown skill: ${skillName}${hint}. The Broad-Side reading guide is served as \`${BROADSIDE_SKILL_NAME}\` and is not pipeline-gated.`, "error");
|
|
859
916
|
return;
|
|
860
917
|
}
|
|
861
|
-
const prompt = await buildSkillPrompt(state,
|
|
918
|
+
const prompt = await buildSkillPrompt(state, resolved);
|
|
862
919
|
if (ctx.isIdle()) {
|
|
863
920
|
pi.sendUserMessage(prompt);
|
|
864
921
|
}
|
|
865
922
|
else {
|
|
866
923
|
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
867
924
|
}
|
|
868
|
-
lastFeedbackLines = [`Queued post-pipeline skill: ${
|
|
925
|
+
lastFeedbackLines = [`Queued post-pipeline skill: ${resolved}`];
|
|
869
926
|
setUiState(ctx, state, lastFeedbackLines);
|
|
870
|
-
notifyCtx(ctx, `Queued CodeCartographer skill: ${
|
|
927
|
+
notifyCtx(ctx, `Queued CodeCartographer skill: ${resolved}`, "info");
|
|
871
928
|
},
|
|
872
929
|
});
|
|
873
930
|
pi.registerCommand("codecarto-list-skills", {
|
|
@@ -882,11 +939,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
882
939
|
const lines = skills.length > 0
|
|
883
940
|
? [`Available skills (${skills.length}):`, ...skills.map((name) => ` - ${name}`)]
|
|
884
941
|
: ["No skills installed."];
|
|
885
|
-
const
|
|
942
|
+
const outcome = resolvePipelineOutcome(state);
|
|
886
943
|
if (skills.length > 0) {
|
|
887
|
-
lines.push(
|
|
888
|
-
? `Post-pipeline skills unlock when the pipeline completes (next phase: ${
|
|
889
|
-
:
|
|
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>.");
|
|
890
949
|
}
|
|
891
950
|
// Broad-Side is listed apart from the post-pipeline set because it
|
|
892
951
|
// answers to /codecarto-skill without the completion gate.
|
|
@@ -962,7 +1021,23 @@ export default function codeCartographerExtension(pi) {
|
|
|
962
1021
|
// Broad-Side runs on any git repository, with or without a workspace —
|
|
963
1022
|
// so this command never goes through ensureWorkspaceState.
|
|
964
1023
|
const broadsideDir = broadsideDirFor(ctx.cwd);
|
|
965
|
-
|
|
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
|
+
}
|
|
966
1041
|
const finish = (lines, notice, level = "info") => {
|
|
967
1042
|
lastFeedbackLines = lines;
|
|
968
1043
|
if (codecartoModeActive) {
|
|
@@ -979,9 +1054,18 @@ export default function codeCartographerExtension(pi) {
|
|
|
979
1054
|
notifyCtx(ctx, notice, level);
|
|
980
1055
|
};
|
|
981
1056
|
if (flags.action === "status") {
|
|
982
|
-
|
|
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
|
+
}
|
|
983
1067
|
const runs = state.runs.length;
|
|
984
|
-
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");
|
|
985
1069
|
return;
|
|
986
1070
|
}
|
|
987
1071
|
const apiKey = resolveBroadsideKey(config.apiKey);
|
|
@@ -1019,8 +1103,10 @@ export default function codeCartographerExtension(pi) {
|
|
|
1019
1103
|
progress.set(lensId, `${status} (${counts.completed ?? 0}/${counts.total ?? "?"})`);
|
|
1020
1104
|
renderProgress("Polling batches…");
|
|
1021
1105
|
};
|
|
1106
|
+
// An explicit --wait=0 polls once and returns; it used to become
|
|
1107
|
+
// undefined and, in core, the 25-minute budget (#230).
|
|
1022
1108
|
const waitSeconds = flags.waitSeconds ?? config.waitSeconds;
|
|
1023
|
-
const waitMs = waitSeconds
|
|
1109
|
+
const waitMs = waitSeconds * 1000;
|
|
1024
1110
|
const includeSynthesis = flags.includeSynthesis ?? config.includeSynthesis;
|
|
1025
1111
|
const includeTriage = flags.includeTriage ?? config.includeTriage;
|
|
1026
1112
|
const retryTruncated = flags.retryTruncated ?? config.retryTruncated;
|
|
@@ -1087,6 +1173,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1087
1173
|
includeTriage,
|
|
1088
1174
|
retryTruncated,
|
|
1089
1175
|
onStatus,
|
|
1176
|
+
...(flags.runId && { runId: flags.runId }),
|
|
1090
1177
|
});
|
|
1091
1178
|
const lines = collectResultText(collect).split("\n");
|
|
1092
1179
|
const done = collect.status === "completed";
|
|
@@ -1108,6 +1195,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
1108
1195
|
if (!state)
|
|
1109
1196
|
return;
|
|
1110
1197
|
const config = await loadCodecartoConfig(state.workspaceDir);
|
|
1198
|
+
// The library path, namespace, and confirm gate all come from the
|
|
1199
|
+
// config, so a file that could not be used is a refusal, not a silent
|
|
1200
|
+
// fallback to the other layer (#242). Same rule as codecarto_publish.
|
|
1201
|
+
if (config.problems.length > 0) {
|
|
1202
|
+
notifyCtx(ctx, ["Publish refused: the configuration has problems. Fix or remove the offending file, then retry.", ...describeConfigProblems(config)].join("\n"), "error");
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1111
1205
|
if (!config.library.path) {
|
|
1112
1206
|
notifyCtx(ctx, "No library.path is configured. Create a library directory with a .codecarto-library marker, then set library.path in ~/.codecarto/config.yaml or .codecarto/workflow/config.yaml.", "error");
|
|
1113
1207
|
return;
|
|
@@ -1222,21 +1316,26 @@ export default function codeCartographerExtension(pi) {
|
|
|
1222
1316
|
notifyCtx(ctx, "Usage: /codecarto-library-init <path> [--namespace <name>]", "warning");
|
|
1223
1317
|
return;
|
|
1224
1318
|
}
|
|
1225
|
-
|
|
1319
|
+
// The same expansion the config loader applies, so `~user/x` and a
|
|
1320
|
+
// bare `~` mean the same thing everywhere (self-audit mech 6.14).
|
|
1321
|
+
const libraryPath = resolve(expandTilde(pathArg));
|
|
1226
1322
|
try {
|
|
1227
|
-
const result = await initLibrary(libraryPath, {
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
//
|
|
1323
|
+
const result = await initLibrary(libraryPath, { namespaced: !!namespace });
|
|
1324
|
+
// Write library.path (and library.namespace when given) to the
|
|
1325
|
+
// user-global config and nothing else — not publish_confirm, so
|
|
1326
|
+
// initializing a library does not change how publish behaves on
|
|
1327
|
+
// MCP (#244). A config file that cannot be parsed is left alone;
|
|
1328
|
+
// the error lands in the catch below.
|
|
1232
1329
|
const configPath = resolveUserConfigPath();
|
|
1233
1330
|
await writeLibraryConfig(configPath, libraryPath, namespace);
|
|
1331
|
+
const written = namespace ? "library.path and library.namespace" : "library.path";
|
|
1234
1332
|
const msg = result.alreadyExisted
|
|
1235
1333
|
? `Library already exists at ${libraryPath} (marker preserved). Config updated.`
|
|
1236
1334
|
: `Created library at ${libraryPath} with marker "${result.marker.name}".`;
|
|
1237
|
-
|
|
1335
|
+
const configLine = `Wrote ${written} to ${configPath}; other keys untouched`;
|
|
1336
|
+
lastFeedbackLines = [msg, configLine];
|
|
1238
1337
|
notifyCtx(ctx, msg, "info");
|
|
1239
|
-
notifyCtx(ctx,
|
|
1338
|
+
notifyCtx(ctx, configLine, "info");
|
|
1240
1339
|
}
|
|
1241
1340
|
catch (error) {
|
|
1242
1341
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1263,10 +1362,18 @@ export default function codeCartographerExtension(pi) {
|
|
|
1263
1362
|
const marker = await discoverLibrary(config.library.path);
|
|
1264
1363
|
lines.push(` Library marker: ${marker ? `found ("${marker.name}", namespaced: ${marker.namespaced})` : "MISSING — run /codecarto-library-init"}`);
|
|
1265
1364
|
}
|
|
1365
|
+
// A file that could not be used is the one thing this command exists
|
|
1366
|
+
// to surface; /codecarto-publish refuses while any is listed (#242).
|
|
1367
|
+
lines.push(...describeConfigProblems(config));
|
|
1266
1368
|
lastFeedbackLines = lines;
|
|
1267
1369
|
if (state)
|
|
1268
1370
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1269
|
-
|
|
1371
|
+
if (config.problems.length > 0) {
|
|
1372
|
+
notifyCtx(ctx, describeConfigProblems(config).join("\n"), "warning");
|
|
1373
|
+
}
|
|
1374
|
+
else {
|
|
1375
|
+
notifyCtx(ctx, "Configuration shown in status widget.", "info");
|
|
1376
|
+
}
|
|
1270
1377
|
},
|
|
1271
1378
|
});
|
|
1272
1379
|
pi.registerCommand("codecarto-usage", {
|
|
@@ -1412,9 +1519,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
1412
1519
|
notifyCtx(ctx, error instanceof Error ? error.message : String(error), "error");
|
|
1413
1520
|
return;
|
|
1414
1521
|
}
|
|
1415
|
-
const
|
|
1416
|
-
if (
|
|
1417
|
-
notifyCtx(ctx, `Cannot amend: the pipeline is not complete
|
|
1522
|
+
const outcome = resolvePipelineOutcome(state);
|
|
1523
|
+
if (outcome.kind === "stuck") {
|
|
1524
|
+
notifyCtx(ctx, `Cannot amend: the pipeline is not complete. ${describeStuckPipeline(outcome.blocked)}`, "error");
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
if (outcome.kind === "eligible") {
|
|
1528
|
+
notifyCtx(ctx, `Cannot amend: the pipeline is not complete (next phase: ${outcome.phase.id}). `
|
|
1418
1529
|
+ "Resolve open questions and routed items through that phase's handoff (open_question_closures / carry_forward_closures) instead.", "error");
|
|
1419
1530
|
return;
|
|
1420
1531
|
}
|
|
@@ -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
|
-
|
|
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;
|