codecartographer-pi 0.16.0 → 0.17.1
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 +16 -3
- package/.codecarto/README.md +3 -0
- package/.codecarto/broadside/SKILL.md +143 -0
- package/.codecarto/broadside/config.yaml +104 -0
- package/.codecarto/findings/architecture/SKILL.md +1 -0
- package/.codecarto/findings/broadside-scout/README.md +20 -0
- package/.codecarto/findings/broadside-scout/SKILL.md +101 -0
- package/.codecarto/findings/contracts/SKILL.md +1 -0
- package/.codecarto/findings/defect-scan/SKILL.md +15 -1
- package/.codecarto/findings/defect-scan/passes/01-logic-and-correctness.md +1 -1
- package/.codecarto/findings/defect-scan/passes/02-error-handling.md +1 -1
- package/.codecarto/findings/defect-scan/passes/03-concurrency-and-resources.md +1 -1
- package/.codecarto/findings/defect-scan/passes/04-security-and-trust.md +1 -1
- package/.codecarto/findings/defect-scan/passes/05-api-contract-violations.md +2 -1
- package/.codecarto/findings/defect-scan/passes/06-config-and-environment.md +1 -1
- package/.codecarto/findings/defect-scan-mechanical/SKILL.md +3 -2
- package/.codecarto/findings/defect-scan-semantic/SKILL.md +2 -0
- package/.codecarto/findings/porting/SKILL.md +2 -1
- package/.codecarto/findings/protocols/SKILL.md +1 -0
- package/.codecarto/findings/reimplementation-spec/SKILL.md +1 -1
- package/.codecarto/skills/spec-delta-application/SKILL.md +3 -1
- package/.codecarto/templates/architecture-map.md +1 -1
- package/.codecarto/templates/backlog-project.md +51 -0
- package/.codecarto/templates/broadside-scout-brief.md +97 -0
- package/.codecarto/templates/defect-report.md +23 -0
- package/.codecarto/templates/mechanical-defects.md +22 -0
- package/.codecarto/templates/reverse-engineering-bundle.md +2 -2
- package/.codecarto/templates/semantic-defects.md +26 -0
- package/.codecarto/{THREAD_LOG.md → templates/thread-log.md} +2 -5
- package/.codecarto/workflow/VALIDATE.md +1 -1
- package/.codecarto/workflow/pipeline-defect-scan.yaml +2 -0
- package/.codecarto/workflow/pipeline-full-with-audit.yaml +3 -1
- package/.codecarto/workflow/pipeline-full-with-deep-audit.yaml +5 -1
- package/.codecarto/workflow/pipeline-scout-first.yaml +275 -0
- package/.codecarto/workflow/scaffold-version.yaml +1 -1
- package/README.md +51 -6
- package/agent-skill/codecartographer/SKILL.md +3 -1
- package/agent-skill/codecartographer/references/broadside.md +115 -0
- package/agent-skill/codecartographer/references/deep-audit-synthesis.md +4 -1
- package/agent-skill/codecartographer/references/library.md +2 -2
- package/agent-skill/codecartographer/references/orchestration.md +1 -1
- package/agent-skill/codecartographer/references/pipeline-selection.md +14 -0
- package/dist/core/amendment.js +2 -2
- package/dist/core/broadside.d.ts +421 -0
- package/dist/core/broadside.js +2349 -0
- package/dist/core/completion.d.ts +5 -0
- package/dist/core/completion.js +38 -6
- package/dist/core/dashboard.js +5 -3
- package/dist/core/findings.d.ts +59 -0
- package/dist/core/findings.js +145 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/library.d.ts +88 -1
- package/dist/core/library.js +260 -7
- package/dist/core/orchestrator-config.js +5 -2
- package/dist/core/pipeline.js +16 -0
- package/dist/core/prompts.js +1 -1
- package/dist/core/status.js +23 -7
- package/dist/core/types.d.ts +6 -0
- package/dist/core/utils.d.ts +14 -0
- package/dist/core/utils.js +37 -1
- package/dist/core/workspace.d.ts +17 -0
- package/dist/core/workspace.js +79 -20
- package/dist/core/yaml.js +19 -4
- package/dist/extensions/codecarto/agent-runner.js +6 -0
- package/dist/extensions/codecarto/auto-runner.d.ts +2 -0
- package/dist/extensions/codecarto/broadside-flags.d.ts +26 -0
- package/dist/extensions/codecarto/broadside-flags.js +129 -0
- package/dist/extensions/codecarto/dashboard-narrator.js +1 -1
- package/dist/extensions/codecarto/index.js +270 -18
- package/dist/extensions/codecarto/phase-compaction.js +4 -0
- package/dist/mcp-server/server.d.ts +22 -0
- package/dist/mcp-server/server.js +282 -17
- package/package.json +11 -2
- package/.codecarto/BACKLOG.md +0 -184
- package/.codecarto/CHANGELOG-2026-05-02-feedback-pass.md +0 -118
- package/.codecarto/closeouts/2026-05-02-framework-feedback-pass.md +0 -111
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { basename, join, resolve } from "node:path";
|
|
4
4
|
import { autoCompletePhase, buildAutoSummary, isPhaseRunning, runAuto, runSinglePhase } from "./auto-runner.js";
|
|
@@ -6,12 +6,16 @@ import { disposeAgentsWidget } from "./agent-widget.js";
|
|
|
6
6
|
import { parseDashboardFlags } from "./dashboard-flags.js";
|
|
7
7
|
import { narrateDashboard } from "./dashboard-narrator.js";
|
|
8
8
|
import { writeDashboard } from "./dashboard-writer.js";
|
|
9
|
+
import { parseBroadsideFlags, KNOWN_BROADSIDE_TOKENS } from "./broadside-flags.js";
|
|
9
10
|
import { parseNextFlags } from "./next-flags.js";
|
|
10
11
|
import { phaseCompactionExtension } from "./phase-compaction.js";
|
|
11
|
-
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, describeScaffoldStaleness, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPathResolved, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, packagedWorkspaceDir, pathExists, PACKAGE_VERSION, PhasePreflightError, PIPELINE_ALIASES, publishEntry, resolvePhase, resolvePipelineChoice, runPhasePreflight, seedOrchestratorFiles, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../../core/index.js";
|
|
12
|
+
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, copyPackagedWorkspace, computePerPhaseTotals, computeTotals, ConfidentialityMismatchError, createEmptyStatus, DEFAULT_PIPELINE_PATH, describeScaffoldStaleness, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPathResolved, BROADSIDE_LENS_IDS, BROADSIDE_SKILL_NAME, BroadsideCancelledError, broadsideDirFor, collectResultText, estimateSubmitText, getLens, listBatchModels, listSkillNames, loadBroadsideConfig, modelsText, runBroadsideCollect, runBroadsideStatus, runBroadsideSubmit, statusText, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, packagedWorkspaceDir, pathExists, PACKAGE_VERSION, readBroadsideSkill, PhasePreflightError, PIPELINE_ALIASES, publishEntry, resolvePhase, resolvePipelineChoice, runPhasePreflight, seedOrchestratorFiles, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../../core/index.js";
|
|
12
13
|
import { initLibrary } from "../../core/library.js";
|
|
13
14
|
import { resolveUserConfigPath } from "../../core/orchestrator-config.js";
|
|
14
15
|
const STATUS_WIDGET_ID = "codecarto-widget";
|
|
16
|
+
// Broad-Side gets its own widget id: a scout run is legal on a repository with
|
|
17
|
+
// no workspace, where the phase widget has nothing to render.
|
|
18
|
+
const BROADSIDE_WIDGET_ID = "codecarto-broadside";
|
|
15
19
|
const STATUS_LINE_ID = "codecarto-status";
|
|
16
20
|
const SAFE_TOOL_NAMES = ["read", "grep", "find", "ls", "edit", "write"];
|
|
17
21
|
function derivePublishHeadline(spec, cwd) {
|
|
@@ -92,6 +96,47 @@ function setUiState(ctx, state, extraLines = []) {
|
|
|
92
96
|
ctx.ui.setStatus(STATUS_LINE_ID, `${theme.fg("accent", "CC")} ${theme.fg("dim", currentPhase)}`);
|
|
93
97
|
ctx.ui.setWidget(STATUS_WIDGET_ID, buildStatusLines(state, extraLines));
|
|
94
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the OpenRouter key for a Broad-Side run. Deliberately no slash-command
|
|
101
|
+
* parameter: a key typed as a command argument lands in the session transcript.
|
|
102
|
+
*/
|
|
103
|
+
function resolveBroadsideKey(configuredKey) {
|
|
104
|
+
const fromEnv = process.env.OPENROUTER_API_KEY?.trim();
|
|
105
|
+
if (fromEnv)
|
|
106
|
+
return fromEnv;
|
|
107
|
+
return configuredKey.trim() || null;
|
|
108
|
+
}
|
|
109
|
+
/** The spend decision, rendered for a human about to approve it. */
|
|
110
|
+
function describeBroadsideEstimate(estimate) {
|
|
111
|
+
const lines = [
|
|
112
|
+
`Model: ${estimate.model}${estimate.mixedModels ? " (some lenses overridden — see below)" : ""} (pricing: ${estimate.pricing.source})`,
|
|
113
|
+
`Rates: $${estimate.pricing.inputPerM.toFixed(4)}/M in · $${estimate.pricing.outputPerM.toFixed(4)}/M out`,
|
|
114
|
+
"",
|
|
115
|
+
"Per lens:",
|
|
116
|
+
...estimate.lenses.map(({ name, slices, cost, model }) => {
|
|
117
|
+
// Naming the model only when it differs keeps the common case quiet
|
|
118
|
+
// and makes a mixed-model run impossible to approve without noticing.
|
|
119
|
+
const override = estimate.mixedModels && model !== estimate.model ? ` on ${model}` : "";
|
|
120
|
+
return ` ${name}: ${slices} slice${slices === 1 ? "" : "s"} — ~$${cost.toFixed(4)}${override}`;
|
|
121
|
+
}),
|
|
122
|
+
"",
|
|
123
|
+
`Estimated total: ~$${estimate.totalCost.toFixed(4)} ` +
|
|
124
|
+
`(~${Math.round(estimate.inputTokens / 1000)}k in, ~${Math.round(estimate.outputTokens / 1000)}k out)`,
|
|
125
|
+
];
|
|
126
|
+
if (estimate.maxCost > 0) {
|
|
127
|
+
lines.push(estimate.exceedsLimit
|
|
128
|
+
? `This EXCEEDS the configured max_cost of $${estimate.maxCost.toFixed(2)}. Approving here overrides it for this run.`
|
|
129
|
+
: `Within the configured max_cost of $${estimate.maxCost.toFixed(2)}.`);
|
|
130
|
+
}
|
|
131
|
+
if (estimate.baseHead) {
|
|
132
|
+
lines.push(`Incremental: only modules changed since ${estimate.baseHead.slice(0, 8)} are included.`);
|
|
133
|
+
}
|
|
134
|
+
else if (estimate.sourceDirty) {
|
|
135
|
+
lines.push("Incremental was requested but the tree is dirty — this is a full scan.");
|
|
136
|
+
}
|
|
137
|
+
lines.push("", "The estimate is a pre-flight prediction from file sizes; OpenRouter bills actual usage.");
|
|
138
|
+
return lines.join("\n");
|
|
139
|
+
}
|
|
95
140
|
export default function codeCartographerExtension(pi) {
|
|
96
141
|
phaseCompactionExtension(pi);
|
|
97
142
|
let lastFeedbackLines = [];
|
|
@@ -268,7 +313,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
268
313
|
}
|
|
269
314
|
if (!(await pathExists(targetWorkspaceDir))) {
|
|
270
315
|
await mkdir(ctx.cwd, { recursive: true });
|
|
271
|
-
await
|
|
316
|
+
await copyPackagedWorkspace(targetWorkspaceDir);
|
|
272
317
|
}
|
|
273
318
|
const rawStatusPath = join(targetWorkspaceDir, "workflow", "status.yaml");
|
|
274
319
|
const rawStatus = (await loadYamlFile(rawStatusPath)) ?? {};
|
|
@@ -573,7 +618,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
573
618
|
ctx.ui.notify(`Cannot complete ${validation.phaseId}: ${validation.overall}`, "error");
|
|
574
619
|
return;
|
|
575
620
|
}
|
|
576
|
-
const { updatedState, closeoutNotice } = await autoCompletePhase(ctx, validation);
|
|
621
|
+
const { updatedState, closeoutNotice, warnings } = await autoCompletePhase(ctx, validation);
|
|
577
622
|
lastFeedbackLines = [
|
|
578
623
|
`Completed phase: ${validation.phaseId}`,
|
|
579
624
|
`Validation: ${validation.overall}`,
|
|
@@ -581,10 +626,15 @@ export default function codeCartographerExtension(pi) {
|
|
|
581
626
|
];
|
|
582
627
|
if (closeoutNotice)
|
|
583
628
|
lastFeedbackLines.push(closeoutNotice);
|
|
629
|
+
const notes = [...(validation.warnings ?? []), ...warnings];
|
|
630
|
+
for (const note of notes)
|
|
631
|
+
lastFeedbackLines.push(`NOTE: ${note} Non-gating.`);
|
|
584
632
|
setUiState(ctx, updatedState, lastFeedbackLines);
|
|
585
|
-
ctx.ui.notify(`Marked ${validation.phaseId} complete`, validation.overall === "PASS WITH GAPS" ? "warning" : "info");
|
|
633
|
+
ctx.ui.notify(`Marked ${validation.phaseId} complete`, validation.overall === "PASS WITH GAPS" || notes.length > 0 ? "warning" : "info");
|
|
586
634
|
if (closeoutNotice)
|
|
587
635
|
ctx.ui.notify(closeoutNotice, "info");
|
|
636
|
+
for (const note of notes)
|
|
637
|
+
ctx.ui.notify(note, "warning");
|
|
588
638
|
},
|
|
589
639
|
});
|
|
590
640
|
pi.registerCommand("codecarto-skill", {
|
|
@@ -597,6 +647,30 @@ export default function codeCartographerExtension(pi) {
|
|
|
597
647
|
ctx.ui.notify(`Usage: /codecarto-skill <name>${hint}`, "warning");
|
|
598
648
|
return;
|
|
599
649
|
}
|
|
650
|
+
// Broad-Side is a reading guide for batch reconnaissance output, not a
|
|
651
|
+
// post-pipeline skill: it is read before or during the pipeline and works
|
|
652
|
+
// on a repository with scout state and no workspace. Same exemption the
|
|
653
|
+
// MCP surface makes in handleSkill.
|
|
654
|
+
if (skillName === BROADSIDE_SKILL_NAME) {
|
|
655
|
+
const skill = await readBroadsideSkill(ctx.cwd).catch(() => null);
|
|
656
|
+
if (!skill) {
|
|
657
|
+
ctx.ui.notify("Broad-Side reading guide not found. Reinstall codecartographer-pi.", "error");
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const message = [
|
|
661
|
+
"Read the Broad-Side reading guide below and apply it to the batch reconnaissance results in .codecarto/broadside/.",
|
|
662
|
+
"",
|
|
663
|
+
skill.content,
|
|
664
|
+
].join("\n");
|
|
665
|
+
if (ctx.isIdle()) {
|
|
666
|
+
pi.sendUserMessage(message);
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
pi.sendUserMessage(message, { deliverAs: "followUp" });
|
|
670
|
+
}
|
|
671
|
+
ctx.ui.notify("Queued the Broad-Side reading guide", "info");
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
600
674
|
const state = await ensureWorkspaceState(ctx);
|
|
601
675
|
if (!state)
|
|
602
676
|
return;
|
|
@@ -609,7 +683,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
609
683
|
if (!(await pathExists(skillFile))) {
|
|
610
684
|
const available = await listSkillNames(state.workspaceDir);
|
|
611
685
|
const hint = available.length > 0 ? ` (available: ${available.join(", ")})` : " (no skills installed)";
|
|
612
|
-
ctx.ui.notify(`Unknown skill: ${skillName}${hint}
|
|
686
|
+
ctx.ui.notify(`Unknown skill: ${skillName}${hint}. The Broad-Side reading guide is served as \`${BROADSIDE_SKILL_NAME}\` and is not pipeline-gated.`, "error");
|
|
613
687
|
return;
|
|
614
688
|
}
|
|
615
689
|
const prompt = await buildSkillPrompt(state, skillName);
|
|
@@ -624,6 +698,167 @@ export default function codeCartographerExtension(pi) {
|
|
|
624
698
|
ctx.ui.notify(`Queued CodeCartographer skill: ${skillName}`, "info");
|
|
625
699
|
},
|
|
626
700
|
});
|
|
701
|
+
pi.registerCommand("codecarto-broadside", {
|
|
702
|
+
description: "Batch reconnaissance (Broad-Side): /codecarto-broadside [submit|collect|status|models] [lenses…] [flags]",
|
|
703
|
+
getArgumentCompletions: (prefix) => {
|
|
704
|
+
const items = KNOWN_BROADSIDE_TOKENS
|
|
705
|
+
.filter((value) => value.startsWith(prefix))
|
|
706
|
+
.map((value) => ({ value, label: value }));
|
|
707
|
+
return items.length > 0 ? items : null;
|
|
708
|
+
},
|
|
709
|
+
handler: async (args, ctx) => {
|
|
710
|
+
const flags = parseBroadsideFlags(args);
|
|
711
|
+
if (flags.unknown.length > 0) {
|
|
712
|
+
ctx.ui.notify(`Unknown /codecarto-broadside argument: ${flags.unknown.join(" ")}. ` +
|
|
713
|
+
`Actions: submit, collect, status, models. Lenses: ${BROADSIDE_LENS_IDS.join(", ")}.`, "error");
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (flags.error) {
|
|
717
|
+
ctx.ui.notify(flags.error, "error");
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
// Broad-Side runs on any git repository, with or without a workspace —
|
|
721
|
+
// so this command never goes through ensureWorkspaceState.
|
|
722
|
+
const broadsideDir = broadsideDirFor(ctx.cwd);
|
|
723
|
+
const config = await loadBroadsideConfig(broadsideDir);
|
|
724
|
+
const finish = (lines, notice, level = "info") => {
|
|
725
|
+
lastFeedbackLines = lines;
|
|
726
|
+
if (codecartoModeActive) {
|
|
727
|
+
// A workspace session already has a widget; fold the result into it.
|
|
728
|
+
if (ctx.hasUI)
|
|
729
|
+
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
730
|
+
void refreshWorkspaceUi(ctx, lines);
|
|
731
|
+
}
|
|
732
|
+
else if (ctx.hasUI) {
|
|
733
|
+
// Scout-only repository: the Broad-Side widget is the only place
|
|
734
|
+
// the result can live, so it holds it instead of being cleared.
|
|
735
|
+
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, ["Broad-Side", ...lines]);
|
|
736
|
+
}
|
|
737
|
+
ctx.ui.notify(notice, level);
|
|
738
|
+
};
|
|
739
|
+
if (flags.action === "status") {
|
|
740
|
+
const { state } = await runBroadsideStatus(ctx.cwd);
|
|
741
|
+
const runs = state.runs.length;
|
|
742
|
+
finish(statusText(state).split("\n"), runs > 0 ? `Broad-Side: ${runs} recorded run${runs === 1 ? "" : "s"}` : "Broad-Side: no runs recorded yet");
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const apiKey = resolveBroadsideKey(config.apiKey);
|
|
746
|
+
if (!apiKey) {
|
|
747
|
+
ctx.ui.notify("No OpenRouter API key. Set OPENROUTER_API_KEY in the environment, or add api_key to " +
|
|
748
|
+
".codecarto/broadside/config.yaml. (A slash command takes no key: it would land in the transcript.)", "error");
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (flags.action === "models") {
|
|
752
|
+
ctx.ui.notify("Fetching the OpenRouter batch-model catalog…", "info");
|
|
753
|
+
try {
|
|
754
|
+
const { entries, benchmarks } = await listBatchModels(broadsideDir, config, apiKey, {
|
|
755
|
+
includeBenchmarks: flags.benchmarks,
|
|
756
|
+
});
|
|
757
|
+
finish(modelsText(entries, { benchmarks, defaultModel: config.model }).split("\n"), `Broad-Side: ${entries.length} batch model${entries.length === 1 ? "" : "s"} listed`);
|
|
758
|
+
}
|
|
759
|
+
catch (error) {
|
|
760
|
+
ctx.ui.notify(`Model catalog lookup failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
761
|
+
}
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
// Live per-lens progress. Poll callbacks fire often, so they render
|
|
765
|
+
// into a widget rather than a notification stream.
|
|
766
|
+
const progress = new Map();
|
|
767
|
+
const renderProgress = (heading) => {
|
|
768
|
+
if (!ctx.hasUI)
|
|
769
|
+
return;
|
|
770
|
+
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, [
|
|
771
|
+
"Broad-Side",
|
|
772
|
+
heading,
|
|
773
|
+
...[...progress.entries()].map(([lensId, line]) => ` ${lensId}: ${line}`),
|
|
774
|
+
]);
|
|
775
|
+
};
|
|
776
|
+
const onStatus = (lensId, status, counts) => {
|
|
777
|
+
progress.set(lensId, `${status} (${counts.completed ?? 0}/${counts.total ?? "?"})`);
|
|
778
|
+
renderProgress("Polling batches…");
|
|
779
|
+
};
|
|
780
|
+
const waitSeconds = flags.waitSeconds ?? config.waitSeconds;
|
|
781
|
+
const waitMs = waitSeconds > 0 ? waitSeconds * 1000 : undefined;
|
|
782
|
+
const includeSynthesis = flags.includeSynthesis ?? config.includeSynthesis;
|
|
783
|
+
const includeTriage = flags.includeTriage ?? config.includeTriage;
|
|
784
|
+
const retryTruncated = flags.retryTruncated ?? config.retryTruncated;
|
|
785
|
+
if (flags.action === "submit") {
|
|
786
|
+
const lenses = flags.lenses.length > 0 ? flags.lenses : config.defaultLenses;
|
|
787
|
+
renderProgress("Slicing the repository and pricing the run…");
|
|
788
|
+
let submit;
|
|
789
|
+
try {
|
|
790
|
+
submit = await runBroadsideSubmit(ctx.cwd, apiKey, {
|
|
791
|
+
lenses,
|
|
792
|
+
model: config.model,
|
|
793
|
+
maxCost: flags.maxCost ?? config.maxCost,
|
|
794
|
+
// `??`, not `||`: --no-incremental parses to false and must beat a
|
|
795
|
+
// config-set true, exactly as MCP's `incremental: false` does (#163).
|
|
796
|
+
incremental: flags.incremental ?? config.incremental,
|
|
797
|
+
// Pi can ask, so it asks instead of refusing over max_cost the
|
|
798
|
+
// way MCP has to. An approval here IS the force flag.
|
|
799
|
+
confirm: (estimate) => ctx.ui.confirm(`Broad-Side will spend about $${estimate.totalCost.toFixed(4)}`, describeBroadsideEstimate(estimate)),
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
catch (error) {
|
|
803
|
+
if (ctx.hasUI)
|
|
804
|
+
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
805
|
+
if (error instanceof BroadsideCancelledError) {
|
|
806
|
+
ctx.ui.notify("Broad-Side cancelled. Nothing was submitted.", "info");
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
ctx.ui.notify(`Broad-Side submit failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const lines = estimateSubmitText(submit, lenses.map(getLens)).split("\n");
|
|
813
|
+
if (!waitMs) {
|
|
814
|
+
lines.push("", "Batches are in flight. Run /codecarto-broadside collect when they finish.");
|
|
815
|
+
finish(lines, `Broad-Side submitted: run ${submit.runId} (~$${submit.estimatedTotalCost.toFixed(4)})`);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
ctx.ui.notify(`Broad-Side submitted run ${submit.runId}; polling for up to ${waitSeconds}s…`, "info");
|
|
819
|
+
try {
|
|
820
|
+
const collect = await runBroadsideCollect(ctx.cwd, apiKey, {
|
|
821
|
+
waitMs,
|
|
822
|
+
includeSynthesis,
|
|
823
|
+
includeTriage,
|
|
824
|
+
retryTruncated,
|
|
825
|
+
onStatus,
|
|
826
|
+
});
|
|
827
|
+
finish([...lines, "", ...collectResultText(collect).split("\n")], `Broad-Side ${collect.status}: run ${collect.runId}`);
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
if (ctx.hasUI)
|
|
831
|
+
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
832
|
+
// The batches are submitted and paid for either way — say so, so
|
|
833
|
+
// nobody re-submits a run that is already in flight.
|
|
834
|
+
ctx.ui.notify(`Broad-Side submitted run ${submit.runId}, but collect failed: ` +
|
|
835
|
+
`${error instanceof Error ? error.message : String(error)}. Retry with /codecarto-broadside collect.`, "error");
|
|
836
|
+
}
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
// action === "collect"
|
|
840
|
+
renderProgress("Polling batches…");
|
|
841
|
+
try {
|
|
842
|
+
const collect = await runBroadsideCollect(ctx.cwd, apiKey, {
|
|
843
|
+
waitMs,
|
|
844
|
+
includeSynthesis,
|
|
845
|
+
includeTriage,
|
|
846
|
+
retryTruncated,
|
|
847
|
+
onStatus,
|
|
848
|
+
});
|
|
849
|
+
const lines = collectResultText(collect).split("\n");
|
|
850
|
+
const done = collect.status === "completed";
|
|
851
|
+
if (!done)
|
|
852
|
+
lines.push("", "Still in flight. Run /codecarto-broadside collect again to resume.");
|
|
853
|
+
finish(lines, `Broad-Side ${collect.status}: ${collect.resultCount} result${collect.resultCount === 1 ? "" : "s"} saved`, done ? "info" : "warning");
|
|
854
|
+
}
|
|
855
|
+
catch (error) {
|
|
856
|
+
if (ctx.hasUI)
|
|
857
|
+
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
858
|
+
ctx.ui.notify(`Broad-Side collect failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
});
|
|
627
862
|
pi.registerCommand("codecarto-publish", {
|
|
628
863
|
description: "Publish the completed reimplementation spec to the configured CodeCartographer library",
|
|
629
864
|
handler: async (_args, ctx) => {
|
|
@@ -667,19 +902,36 @@ export default function codeCartographerExtension(pi) {
|
|
|
667
902
|
].join("\n");
|
|
668
903
|
if (config.library.publish_confirm && !(await ctx.ui.confirm("Publish reimplementation spec", preview)))
|
|
669
904
|
return;
|
|
905
|
+
const input = {
|
|
906
|
+
slug,
|
|
907
|
+
namespace,
|
|
908
|
+
source_repo: ctx.cwd,
|
|
909
|
+
analyzed_at: new Date().toISOString(),
|
|
910
|
+
pipeline: state.status.pipeline,
|
|
911
|
+
codecarto_version: PACKAGE_VERSION,
|
|
912
|
+
headline,
|
|
913
|
+
tags: [],
|
|
914
|
+
capabilities: [],
|
|
915
|
+
generation: piGeneration(ctx),
|
|
916
|
+
};
|
|
670
917
|
try {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
918
|
+
let result;
|
|
919
|
+
try {
|
|
920
|
+
result = await publishEntry(config.library.path, spec, input);
|
|
921
|
+
}
|
|
922
|
+
catch (error) {
|
|
923
|
+
if (!(error instanceof ConfidentialityMismatchError))
|
|
924
|
+
throw error;
|
|
925
|
+
// Nothing was written. Pi declares no confidentiality, so the entry
|
|
926
|
+
// sits at the internal default; whether it may go into a wider
|
|
927
|
+
// library is the user's call, and a yes is the override.
|
|
928
|
+
const publishAnyway = await ctx.ui.confirm("Confidentiality mismatch — publish anyway?", `This spec's confidentiality is "${error.entryConfidentiality}" (CodeCartographer's default; /codecarto-publish declares none), but the library "${marker.name}" has visibility "${error.libraryVisibility}". Publishing would expose it to everyone that library reaches. Publish anyway?`);
|
|
929
|
+
if (!publishAnyway) {
|
|
930
|
+
ctx.ui.notify("Publish cancelled. Nothing was written.", "info");
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
result = await publishEntry(config.library.path, spec, input, { allowConfidentialityMismatch: true });
|
|
934
|
+
}
|
|
683
935
|
lastFeedbackLines = [`Published ${result.namespace ? `${result.namespace}/` : ""}${result.slug} v${result.version}`, result.isNewVersion ? "New content version." : "Metadata-only update (content unchanged)."];
|
|
684
936
|
await writeDashboard(ctx.cwd, PACKAGE_VERSION);
|
|
685
937
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
@@ -81,6 +81,10 @@ export function phaseCompactionExtension(pi) {
|
|
|
81
81
|
return undefined;
|
|
82
82
|
try {
|
|
83
83
|
const state = await getWorkspaceState(ctx.cwd);
|
|
84
|
+
// No workspace under this cwd: nothing phase-aware to add, so the host
|
|
85
|
+
// default applies — not a failure worth a warning (#134).
|
|
86
|
+
if (!state)
|
|
87
|
+
return undefined;
|
|
84
88
|
const phase = state.pipeline.phases.find((candidate) => candidate.id === phaseId);
|
|
85
89
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
|
|
86
90
|
if (!auth.ok || !auth.apiKey)
|
|
@@ -230,6 +230,28 @@ export declare function handleAmend(args: {
|
|
|
230
230
|
text: string;
|
|
231
231
|
};
|
|
232
232
|
}>;
|
|
233
|
+
export declare function handleBroadside(args: {
|
|
234
|
+
cwd: string;
|
|
235
|
+
action: "submit" | "collect" | "status" | "models";
|
|
236
|
+
lenses?: string[];
|
|
237
|
+
api_key?: string;
|
|
238
|
+
wait_seconds?: number;
|
|
239
|
+
include_synthesis?: boolean;
|
|
240
|
+
include_triage?: boolean;
|
|
241
|
+
retry_truncated?: boolean;
|
|
242
|
+
max_cost?: number;
|
|
243
|
+
force?: boolean;
|
|
244
|
+
include_benchmarks?: boolean;
|
|
245
|
+
incremental?: boolean;
|
|
246
|
+
}): Promise<{
|
|
247
|
+
content: {
|
|
248
|
+
type: "text";
|
|
249
|
+
text: string;
|
|
250
|
+
}[];
|
|
251
|
+
structuredContent: {
|
|
252
|
+
text: string;
|
|
253
|
+
};
|
|
254
|
+
}>;
|
|
233
255
|
export declare function handleGuide(args: {
|
|
234
256
|
topic?: string;
|
|
235
257
|
}): Promise<{
|