codecartographer-pi 0.16.0 → 0.17.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 +15 -2
- package/.codecarto/README.md +3 -0
- package/.codecarto/broadside/SKILL.md +143 -0
- package/.codecarto/broadside/config.yaml +104 -0
- package/.codecarto/findings/broadside-scout/README.md +20 -0
- package/.codecarto/findings/broadside-scout/SKILL.md +101 -0
- package/.codecarto/skills/spec-delta-application/SKILL.md +3 -1
- package/.codecarto/templates/backlog-project.md +51 -0
- package/.codecarto/templates/broadside-scout-brief.md +97 -0
- package/.codecarto/{THREAD_LOG.md → templates/thread-log.md} +2 -5
- package/.codecarto/workflow/pipeline-scout-first.yaml +271 -0
- package/.codecarto/workflow/scaffold-version.yaml +1 -1
- package/README.md +47 -2
- package/agent-skill/codecartographer/SKILL.md +3 -1
- package/agent-skill/codecartographer/references/broadside.md +115 -0
- package/agent-skill/codecartographer/references/library.md +1 -1
- package/agent-skill/codecartographer/references/pipeline-selection.md +14 -0
- package/dist/core/broadside.d.ts +421 -0
- package/dist/core/broadside.js +2349 -0
- package/dist/core/completion.js +20 -4
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/library.d.ts +22 -0
- package/dist/core/library.js +101 -1
- package/dist/core/orchestrator-config.js +5 -2
- package/dist/core/pipeline.js +1 -0
- package/dist/core/status.js +9 -1
- package/dist/core/utils.js +7 -1
- package/dist/core/workspace.d.ts +17 -0
- package/dist/core/workspace.js +68 -2
- package/dist/extensions/codecarto/agent-runner.js +6 -0
- package/dist/extensions/codecarto/broadside-flags.d.ts +21 -0
- package/dist/extensions/codecarto/broadside-flags.js +116 -0
- package/dist/extensions/codecarto/index.js +232 -4
- package/dist/mcp-server/server.d.ts +22 -0
- package/dist/mcp-server/server.js +218 -11
- package/package.json +10 -1
- 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, 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)) ?? {};
|
|
@@ -597,6 +642,30 @@ export default function codeCartographerExtension(pi) {
|
|
|
597
642
|
ctx.ui.notify(`Usage: /codecarto-skill <name>${hint}`, "warning");
|
|
598
643
|
return;
|
|
599
644
|
}
|
|
645
|
+
// Broad-Side is a reading guide for batch reconnaissance output, not a
|
|
646
|
+
// post-pipeline skill: it is read before or during the pipeline and works
|
|
647
|
+
// on a repository with scout state and no workspace. Same exemption the
|
|
648
|
+
// MCP surface makes in handleSkill.
|
|
649
|
+
if (skillName === BROADSIDE_SKILL_NAME) {
|
|
650
|
+
const skill = await readBroadsideSkill(ctx.cwd).catch(() => null);
|
|
651
|
+
if (!skill) {
|
|
652
|
+
ctx.ui.notify("Broad-Side reading guide not found. Reinstall codecartographer-pi.", "error");
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const message = [
|
|
656
|
+
"Read the Broad-Side reading guide below and apply it to the batch reconnaissance results in .codecarto/broadside/.",
|
|
657
|
+
"",
|
|
658
|
+
skill.content,
|
|
659
|
+
].join("\n");
|
|
660
|
+
if (ctx.isIdle()) {
|
|
661
|
+
pi.sendUserMessage(message);
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
pi.sendUserMessage(message, { deliverAs: "followUp" });
|
|
665
|
+
}
|
|
666
|
+
ctx.ui.notify("Queued the Broad-Side reading guide", "info");
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
600
669
|
const state = await ensureWorkspaceState(ctx);
|
|
601
670
|
if (!state)
|
|
602
671
|
return;
|
|
@@ -609,7 +678,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
609
678
|
if (!(await pathExists(skillFile))) {
|
|
610
679
|
const available = await listSkillNames(state.workspaceDir);
|
|
611
680
|
const hint = available.length > 0 ? ` (available: ${available.join(", ")})` : " (no skills installed)";
|
|
612
|
-
ctx.ui.notify(`Unknown skill: ${skillName}${hint}
|
|
681
|
+
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
682
|
return;
|
|
614
683
|
}
|
|
615
684
|
const prompt = await buildSkillPrompt(state, skillName);
|
|
@@ -624,6 +693,165 @@ export default function codeCartographerExtension(pi) {
|
|
|
624
693
|
ctx.ui.notify(`Queued CodeCartographer skill: ${skillName}`, "info");
|
|
625
694
|
},
|
|
626
695
|
});
|
|
696
|
+
pi.registerCommand("codecarto-broadside", {
|
|
697
|
+
description: "Batch reconnaissance (Broad-Side): /codecarto-broadside [submit|collect|status|models] [lenses…] [flags]",
|
|
698
|
+
getArgumentCompletions: (prefix) => {
|
|
699
|
+
const items = KNOWN_BROADSIDE_TOKENS
|
|
700
|
+
.filter((value) => value.startsWith(prefix))
|
|
701
|
+
.map((value) => ({ value, label: value }));
|
|
702
|
+
return items.length > 0 ? items : null;
|
|
703
|
+
},
|
|
704
|
+
handler: async (args, ctx) => {
|
|
705
|
+
const flags = parseBroadsideFlags(args);
|
|
706
|
+
if (flags.unknown.length > 0) {
|
|
707
|
+
ctx.ui.notify(`Unknown /codecarto-broadside argument: ${flags.unknown.join(" ")}. ` +
|
|
708
|
+
`Actions: submit, collect, status, models. Lenses: ${BROADSIDE_LENS_IDS.join(", ")}.`, "error");
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (flags.error) {
|
|
712
|
+
ctx.ui.notify(flags.error, "error");
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
// Broad-Side runs on any git repository, with or without a workspace —
|
|
716
|
+
// so this command never goes through ensureWorkspaceState.
|
|
717
|
+
const broadsideDir = broadsideDirFor(ctx.cwd);
|
|
718
|
+
const config = await loadBroadsideConfig(broadsideDir);
|
|
719
|
+
const finish = (lines, notice, level = "info") => {
|
|
720
|
+
lastFeedbackLines = lines;
|
|
721
|
+
if (codecartoModeActive) {
|
|
722
|
+
// A workspace session already has a widget; fold the result into it.
|
|
723
|
+
if (ctx.hasUI)
|
|
724
|
+
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
725
|
+
void refreshWorkspaceUi(ctx, lines);
|
|
726
|
+
}
|
|
727
|
+
else if (ctx.hasUI) {
|
|
728
|
+
// Scout-only repository: the Broad-Side widget is the only place
|
|
729
|
+
// the result can live, so it holds it instead of being cleared.
|
|
730
|
+
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, ["Broad-Side", ...lines]);
|
|
731
|
+
}
|
|
732
|
+
ctx.ui.notify(notice, level);
|
|
733
|
+
};
|
|
734
|
+
if (flags.action === "status") {
|
|
735
|
+
const { state } = await runBroadsideStatus(ctx.cwd);
|
|
736
|
+
const runs = state.runs.length;
|
|
737
|
+
finish(statusText(state).split("\n"), runs > 0 ? `Broad-Side: ${runs} recorded run${runs === 1 ? "" : "s"}` : "Broad-Side: no runs recorded yet");
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const apiKey = resolveBroadsideKey(config.apiKey);
|
|
741
|
+
if (!apiKey) {
|
|
742
|
+
ctx.ui.notify("No OpenRouter API key. Set OPENROUTER_API_KEY in the environment, or add api_key to " +
|
|
743
|
+
".codecarto/broadside/config.yaml. (A slash command takes no key: it would land in the transcript.)", "error");
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
if (flags.action === "models") {
|
|
747
|
+
ctx.ui.notify("Fetching the OpenRouter batch-model catalog…", "info");
|
|
748
|
+
try {
|
|
749
|
+
const { entries, benchmarks } = await listBatchModels(broadsideDir, config, apiKey, {
|
|
750
|
+
includeBenchmarks: flags.benchmarks,
|
|
751
|
+
});
|
|
752
|
+
finish(modelsText(entries, { benchmarks, defaultModel: config.model }).split("\n"), `Broad-Side: ${entries.length} batch model${entries.length === 1 ? "" : "s"} listed`);
|
|
753
|
+
}
|
|
754
|
+
catch (error) {
|
|
755
|
+
ctx.ui.notify(`Model catalog lookup failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
756
|
+
}
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
// Live per-lens progress. Poll callbacks fire often, so they render
|
|
760
|
+
// into a widget rather than a notification stream.
|
|
761
|
+
const progress = new Map();
|
|
762
|
+
const renderProgress = (heading) => {
|
|
763
|
+
if (!ctx.hasUI)
|
|
764
|
+
return;
|
|
765
|
+
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, [
|
|
766
|
+
"Broad-Side",
|
|
767
|
+
heading,
|
|
768
|
+
...[...progress.entries()].map(([lensId, line]) => ` ${lensId}: ${line}`),
|
|
769
|
+
]);
|
|
770
|
+
};
|
|
771
|
+
const onStatus = (lensId, status, counts) => {
|
|
772
|
+
progress.set(lensId, `${status} (${counts.completed ?? 0}/${counts.total ?? "?"})`);
|
|
773
|
+
renderProgress("Polling batches…");
|
|
774
|
+
};
|
|
775
|
+
const waitSeconds = flags.waitSeconds ?? config.waitSeconds;
|
|
776
|
+
const waitMs = waitSeconds > 0 ? waitSeconds * 1000 : undefined;
|
|
777
|
+
const includeSynthesis = flags.includeSynthesis ?? config.includeSynthesis;
|
|
778
|
+
const includeTriage = flags.includeTriage ?? config.includeTriage;
|
|
779
|
+
const retryTruncated = flags.retryTruncated ?? config.retryTruncated;
|
|
780
|
+
if (flags.action === "submit") {
|
|
781
|
+
const lenses = flags.lenses.length > 0 ? flags.lenses : config.defaultLenses;
|
|
782
|
+
renderProgress("Slicing the repository and pricing the run…");
|
|
783
|
+
let submit;
|
|
784
|
+
try {
|
|
785
|
+
submit = await runBroadsideSubmit(ctx.cwd, apiKey, {
|
|
786
|
+
lenses,
|
|
787
|
+
model: config.model,
|
|
788
|
+
maxCost: flags.maxCost ?? config.maxCost,
|
|
789
|
+
incremental: flags.incremental || config.incremental,
|
|
790
|
+
// Pi can ask, so it asks instead of refusing over max_cost the
|
|
791
|
+
// way MCP has to. An approval here IS the force flag.
|
|
792
|
+
confirm: (estimate) => ctx.ui.confirm(`Broad-Side will spend about $${estimate.totalCost.toFixed(4)}`, describeBroadsideEstimate(estimate)),
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
catch (error) {
|
|
796
|
+
if (ctx.hasUI)
|
|
797
|
+
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
798
|
+
if (error instanceof BroadsideCancelledError) {
|
|
799
|
+
ctx.ui.notify("Broad-Side cancelled. Nothing was submitted.", "info");
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
ctx.ui.notify(`Broad-Side submit failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
const lines = estimateSubmitText(submit, lenses.map(getLens)).split("\n");
|
|
806
|
+
if (!waitMs) {
|
|
807
|
+
lines.push("", "Batches are in flight. Run /codecarto-broadside collect when they finish.");
|
|
808
|
+
finish(lines, `Broad-Side submitted: run ${submit.runId} (~$${submit.estimatedTotalCost.toFixed(4)})`);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
ctx.ui.notify(`Broad-Side submitted run ${submit.runId}; polling for up to ${waitSeconds}s…`, "info");
|
|
812
|
+
try {
|
|
813
|
+
const collect = await runBroadsideCollect(ctx.cwd, apiKey, {
|
|
814
|
+
waitMs,
|
|
815
|
+
includeSynthesis,
|
|
816
|
+
includeTriage,
|
|
817
|
+
retryTruncated,
|
|
818
|
+
onStatus,
|
|
819
|
+
});
|
|
820
|
+
finish([...lines, "", ...collectResultText(collect).split("\n")], `Broad-Side ${collect.status}: run ${collect.runId}`);
|
|
821
|
+
}
|
|
822
|
+
catch (error) {
|
|
823
|
+
if (ctx.hasUI)
|
|
824
|
+
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
825
|
+
// The batches are submitted and paid for either way — say so, so
|
|
826
|
+
// nobody re-submits a run that is already in flight.
|
|
827
|
+
ctx.ui.notify(`Broad-Side submitted run ${submit.runId}, but collect failed: ` +
|
|
828
|
+
`${error instanceof Error ? error.message : String(error)}. Retry with /codecarto-broadside collect.`, "error");
|
|
829
|
+
}
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
// action === "collect"
|
|
833
|
+
renderProgress("Polling batches…");
|
|
834
|
+
try {
|
|
835
|
+
const collect = await runBroadsideCollect(ctx.cwd, apiKey, {
|
|
836
|
+
waitMs,
|
|
837
|
+
includeSynthesis,
|
|
838
|
+
includeTriage,
|
|
839
|
+
retryTruncated,
|
|
840
|
+
onStatus,
|
|
841
|
+
});
|
|
842
|
+
const lines = collectResultText(collect).split("\n");
|
|
843
|
+
const done = collect.status === "completed";
|
|
844
|
+
if (!done)
|
|
845
|
+
lines.push("", "Still in flight. Run /codecarto-broadside collect again to resume.");
|
|
846
|
+
finish(lines, `Broad-Side ${collect.status}: ${collect.resultCount} result${collect.resultCount === 1 ? "" : "s"} saved`, done ? "info" : "warning");
|
|
847
|
+
}
|
|
848
|
+
catch (error) {
|
|
849
|
+
if (ctx.hasUI)
|
|
850
|
+
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
851
|
+
ctx.ui.notify(`Broad-Side collect failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
852
|
+
}
|
|
853
|
+
},
|
|
854
|
+
});
|
|
627
855
|
pi.registerCommand("codecarto-publish", {
|
|
628
856
|
description: "Publish the completed reimplementation spec to the configured CodeCartographer library",
|
|
629
857
|
handler: async (_args, ctx) => {
|
|
@@ -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<{
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
// CodeCartographer MCP server. Exposes the framework
|
|
2
|
-
//
|
|
1
|
+
// CodeCartographer MCP server. Exposes the framework's workflow, library, and
|
|
2
|
+
// Broad-Side operations as JSON-RPC tools (the TOOLS array below is the
|
|
3
|
+
// authoritative list; the README's tool table maps each tool to its Pi
|
|
4
|
+
// equivalent or marks it MCP-only).
|
|
3
5
|
// Both wrappers import their primitives from ../core/index.ts so phase prompts,
|
|
4
6
|
// status normalization, validation, and atomic completion are byte-identical
|
|
5
7
|
// across surfaces.
|
|
@@ -13,9 +15,9 @@
|
|
|
13
15
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
14
16
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
17
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
16
|
-
import {
|
|
18
|
+
import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
17
19
|
import { basename, isAbsolute, join } from "node:path";
|
|
18
|
-
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, completeValidatedPhase, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, describeScaffoldStaleness, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isValidSlug, isWithinPathResolved, listEntries, listGuideTopics, readGuide, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, PACKAGE_VERSION, packagedWorkspaceDir, pathExists, PhasePreflightError, publishEntry, reindex as libraryReindex, refreshScaffold, resolvePhase, resolvePipelineChoice, seedOrchestratorFiles, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../core/index.js";
|
|
20
|
+
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, BROADSIDE_DIR, broadsideDirFor, BROADSIDE_LENS_IDS, BROADSIDE_SKILL_NAME, canonicalPath, copyPackagedWorkspace, collectResultText, completeValidatedPhase, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, describeScaffoldStaleness, estimateSubmitText, getLens, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isValidSlug, isWithinPathResolved, listBatchModels, listEntries, listGuideTopics, loadBroadsideConfig, modelsText, readGuide, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, PACKAGE_VERSION, packagedWorkspaceDir, pathExists, PhasePreflightError, publishEntry, reindex as libraryReindex, refreshScaffold, resolvePhase, resolvePipelineChoice, readBroadsideSkill, runBroadsideCollect, runBroadsideStatus, runBroadsideSubmit, seedOrchestratorFiles, statusText, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../core/index.js";
|
|
19
21
|
import { applyAmendment } from "../core/amendment.js";
|
|
20
22
|
import { appendUsageRun } from "../core/usage.js";
|
|
21
23
|
import { initLibrary } from "../core/library.js";
|
|
@@ -84,7 +86,16 @@ export async function handleInit(args) {
|
|
|
84
86
|
normalizeForComparison(await canonicalPath(targetWorkspaceDir)) ===
|
|
85
87
|
normalizeForComparison(await canonicalPath(packagedWorkspaceDir));
|
|
86
88
|
}
|
|
89
|
+
// Broad-Side (batch reconnaissance) creates .codecarto/broadside/ on any
|
|
90
|
+
// repo, workspace or not. A .codecarto/ holding only that directory is not
|
|
91
|
+
// an existing workspace — init must proceed and merge the template into it
|
|
92
|
+
// rather than demanding force and a backup of pure scout state.
|
|
93
|
+
let broadsideOnly = false;
|
|
87
94
|
if (targetExists && !sameWorkspace) {
|
|
95
|
+
const entries = (await readdir(targetWorkspaceDir)).filter((entry) => entry !== BROADSIDE_DIR);
|
|
96
|
+
broadsideOnly = entries.length === 0 && (await pathExists(join(targetWorkspaceDir, BROADSIDE_DIR)));
|
|
97
|
+
}
|
|
98
|
+
if (targetExists && !sameWorkspace && !broadsideOnly) {
|
|
88
99
|
if (!args.force) {
|
|
89
100
|
throw new McpError(ErrorCode.InvalidRequest, `A .codecarto/ directory already exists at ${targetWorkspaceDir}. Pass force: true to back it up and reinitialize. Warning: this moves all existing findings, handoffs, usage data, closeouts, and phase progress to a .codecarto-backup-TIMESTAMP/ directory.`);
|
|
90
101
|
}
|
|
@@ -93,7 +104,12 @@ export async function handleInit(args) {
|
|
|
93
104
|
}
|
|
94
105
|
if (!(await pathExists(targetWorkspaceDir))) {
|
|
95
106
|
await mkdir(cwd, { recursive: true });
|
|
96
|
-
await
|
|
107
|
+
await copyPackagedWorkspace(targetWorkspaceDir);
|
|
108
|
+
}
|
|
109
|
+
else if (broadsideOnly) {
|
|
110
|
+
// Merge the template into the scout-only .codecarto/, preserving the
|
|
111
|
+
// broadside state and results already on disk.
|
|
112
|
+
await copyPackagedWorkspace(targetWorkspaceDir);
|
|
97
113
|
}
|
|
98
114
|
const statusPath = join(targetWorkspaceDir, "workflow", "status.yaml");
|
|
99
115
|
const rawStatus = (await loadYamlFile(statusPath)) ?? {};
|
|
@@ -301,6 +317,15 @@ export async function handleSkill(args) {
|
|
|
301
317
|
throw new McpError(ErrorCode.InvalidParams, "name is required");
|
|
302
318
|
}
|
|
303
319
|
const cwd = await validateCwd(args.cwd);
|
|
320
|
+
// Broad-Side is a reading guide for batch reconnaissance output, not a
|
|
321
|
+
// post-pipeline skill: it is useful before the pipeline starts and on a
|
|
322
|
+
// repository with no workspace at all, so it is served ahead of both gates.
|
|
323
|
+
if (args.name.trim() === BROADSIDE_SKILL_NAME) {
|
|
324
|
+
const skill = await readBroadsideSkill(cwd).catch((error) => {
|
|
325
|
+
throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
|
|
326
|
+
});
|
|
327
|
+
return textResult(skill.content, { skill: BROADSIDE_SKILL_NAME, path: skill.path, postPipeline: false });
|
|
328
|
+
}
|
|
304
329
|
const state = await requireWorkspace(cwd);
|
|
305
330
|
const nextPhase = getNextEligiblePhase(state);
|
|
306
331
|
if (nextPhase) {
|
|
@@ -310,7 +335,7 @@ export async function handleSkill(args) {
|
|
|
310
335
|
if (!(await pathExists(skillFile))) {
|
|
311
336
|
const available = await listSkillNames(state.workspaceDir);
|
|
312
337
|
const hint = available.length > 0 ? ` Available: ${available.join(", ")}.` : " No skills installed.";
|
|
313
|
-
throw new McpError(ErrorCode.InvalidParams, `Unknown skill: ${args.name}.${hint}
|
|
338
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown skill: ${args.name}.${hint} The Broad-Side reading guide is served as \`${BROADSIDE_SKILL_NAME}\` and is not pipeline-gated.`);
|
|
314
339
|
}
|
|
315
340
|
const prompt = await buildSkillPrompt(state, args.name);
|
|
316
341
|
return textResult(prompt, { skill: args.name });
|
|
@@ -508,7 +533,10 @@ export async function handlePublish(args) {
|
|
|
508
533
|
capabilities,
|
|
509
534
|
confidentiality,
|
|
510
535
|
generation,
|
|
511
|
-
}, {
|
|
536
|
+
}, {
|
|
537
|
+
forceNewVersion: args.force_new_version === true,
|
|
538
|
+
allowSourceRepoChange: args.allow_source_repo_change === true,
|
|
539
|
+
});
|
|
512
540
|
const lines = [
|
|
513
541
|
`Published ${result.namespace ? `${result.namespace}/` : ""}${result.slug} v${result.version} to ${libraryPath}`,
|
|
514
542
|
result.isNewVersion ? `New version: v${result.version}` : `Metadata-only update (content hash matched v${result.version}).`,
|
|
@@ -709,7 +737,13 @@ export async function handleListSkills(args) {
|
|
|
709
737
|
const lines = skills.length > 0
|
|
710
738
|
? [`Available skills (${skills.length}):`, ...skills.map((s) => ` - ${s}`)]
|
|
711
739
|
: ["No skills installed."];
|
|
712
|
-
|
|
740
|
+
// Broad-Side is listed apart from the post-pipeline set because it answers
|
|
741
|
+
// to codecarto_skill without the completion gate.
|
|
742
|
+
const broadsideAvailable = await readBroadsideSkill(cwd).then(() => true, () => false);
|
|
743
|
+
if (broadsideAvailable) {
|
|
744
|
+
lines.push("", `Also served by codecarto_skill (not pipeline-gated): ${BROADSIDE_SKILL_NAME} — how to read a Broad-Side batch reconnaissance run.`);
|
|
745
|
+
}
|
|
746
|
+
return textResult(lines.join("\n"), { skills, broadside: broadsideAvailable });
|
|
713
747
|
}
|
|
714
748
|
export async function handleRefreshScaffold(args) {
|
|
715
749
|
const cwd = await validateCwd(args.cwd);
|
|
@@ -761,6 +795,117 @@ export async function handleAmend(args) {
|
|
|
761
795
|
dashboardPath,
|
|
762
796
|
});
|
|
763
797
|
}
|
|
798
|
+
// ---------- broadside (batch reconnaissance) ----------
|
|
799
|
+
function resolveBroadsideApiKey(explicit, config) {
|
|
800
|
+
if (explicit && explicit.trim())
|
|
801
|
+
return explicit.trim();
|
|
802
|
+
const fromEnv = process.env.OPENROUTER_API_KEY?.trim();
|
|
803
|
+
if (fromEnv)
|
|
804
|
+
return fromEnv;
|
|
805
|
+
if (config.apiKey)
|
|
806
|
+
return config.apiKey;
|
|
807
|
+
throw new McpError(ErrorCode.InvalidParams, "No OpenRouter API key found. Pass api_key, set the OPENROUTER_API_KEY environment variable, or add api_key to .codecarto/broadside/config.yaml.");
|
|
808
|
+
}
|
|
809
|
+
export async function handleBroadside(args) {
|
|
810
|
+
const cwd = await validateCwd(args.cwd);
|
|
811
|
+
const action = args.action ?? "submit";
|
|
812
|
+
if (!["submit", "collect", "status", "models"].includes(action)) {
|
|
813
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}. Valid actions: submit, collect, status, models.`);
|
|
814
|
+
}
|
|
815
|
+
const config = await loadBroadsideConfig(broadsideDirFor(cwd));
|
|
816
|
+
if (action === "status") {
|
|
817
|
+
const { state } = await runBroadsideStatus(cwd);
|
|
818
|
+
return textResult(statusText(state), { state });
|
|
819
|
+
}
|
|
820
|
+
const apiKey = resolveBroadsideApiKey(args.api_key, config);
|
|
821
|
+
// Every run knob resolves the same way: explicit parameter, else the repo's
|
|
822
|
+
// config.yaml default, else the shipped default baked into loadBroadsideConfig.
|
|
823
|
+
const waitSeconds = typeof args.wait_seconds === "number" && args.wait_seconds > 0
|
|
824
|
+
? args.wait_seconds
|
|
825
|
+
: config.waitSeconds;
|
|
826
|
+
const waitMs = waitSeconds > 0 ? waitSeconds * 1000 : undefined;
|
|
827
|
+
const includeSynthesis = args.include_synthesis ?? config.includeSynthesis;
|
|
828
|
+
const includeTriage = args.include_triage ?? config.includeTriage;
|
|
829
|
+
const retryTruncated = args.retry_truncated ?? config.retryTruncated;
|
|
830
|
+
const incremental = args.incremental ?? config.incremental;
|
|
831
|
+
if (action === "models") {
|
|
832
|
+
const { entries, benchmarks } = await listBatchModels(broadsideDirFor(cwd), config, apiKey, {
|
|
833
|
+
includeBenchmarks: args.include_benchmarks === true,
|
|
834
|
+
}).catch((error) => {
|
|
835
|
+
throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
|
|
836
|
+
});
|
|
837
|
+
return textResult(modelsText(entries, { benchmarks, defaultModel: config.model }), {
|
|
838
|
+
models: entries,
|
|
839
|
+
defaultModel: config.model,
|
|
840
|
+
benchmarkMeta: benchmarks?.meta ?? null,
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
if (action === "submit") {
|
|
844
|
+
let lenses;
|
|
845
|
+
if (args.lenses && args.lenses.length > 0) {
|
|
846
|
+
const unknown = args.lenses.filter((l) => !BROADSIDE_LENS_IDS.includes(l));
|
|
847
|
+
if (unknown.length > 0) {
|
|
848
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown lens(es): ${unknown.join(", ")}. Valid: ${BROADSIDE_LENS_IDS.join(", ")}`);
|
|
849
|
+
}
|
|
850
|
+
lenses = args.lenses;
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
lenses = config.defaultLenses;
|
|
854
|
+
}
|
|
855
|
+
const maxCost = typeof args.max_cost === "number" && args.max_cost > 0 ? args.max_cost : config.maxCost;
|
|
856
|
+
const result = await runBroadsideSubmit(cwd, apiKey, {
|
|
857
|
+
lenses,
|
|
858
|
+
model: config.model,
|
|
859
|
+
maxCost,
|
|
860
|
+
force: args.force === true,
|
|
861
|
+
incremental,
|
|
862
|
+
}).catch((error) => {
|
|
863
|
+
throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
|
|
864
|
+
});
|
|
865
|
+
const lines = [estimateSubmitText(result, lenses.map(getLens))];
|
|
866
|
+
if (waitMs) {
|
|
867
|
+
lines.push("", "Waiting for batches to complete...");
|
|
868
|
+
const collect = await runBroadsideCollect(cwd, apiKey, {
|
|
869
|
+
waitMs,
|
|
870
|
+
includeSynthesis,
|
|
871
|
+
includeTriage,
|
|
872
|
+
retryTruncated,
|
|
873
|
+
onStatus: (lensId, status, counts) => lines.push(` ${lensId}: ${status} (${counts.completed ?? 0}/${counts.total ?? "?"})`),
|
|
874
|
+
});
|
|
875
|
+
lines.push("", collectResultText(collect));
|
|
876
|
+
}
|
|
877
|
+
return textResult(lines.join("\n"), {
|
|
878
|
+
runId: result.runId,
|
|
879
|
+
outputDir: result.outputDir,
|
|
880
|
+
batches: result.batches,
|
|
881
|
+
estimatedTotalCost: result.estimatedTotalCost,
|
|
882
|
+
pricing: result.pricing,
|
|
883
|
+
maxCost: result.maxCost,
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
// action === "collect"
|
|
887
|
+
const collect = await runBroadsideCollect(cwd, apiKey, {
|
|
888
|
+
waitMs,
|
|
889
|
+
includeSynthesis,
|
|
890
|
+
includeTriage,
|
|
891
|
+
retryTruncated,
|
|
892
|
+
}).catch((error) => {
|
|
893
|
+
throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : String(error));
|
|
894
|
+
});
|
|
895
|
+
return textResult(collectResultText(collect), {
|
|
896
|
+
runId: collect.runId,
|
|
897
|
+
status: collect.status,
|
|
898
|
+
totalCost: collect.totalCost,
|
|
899
|
+
resultCount: collect.resultCount,
|
|
900
|
+
truncatedCount: collect.truncatedCount,
|
|
901
|
+
retriedCount: collect.retriedCount,
|
|
902
|
+
lensOutcomes: collect.lensOutcomes,
|
|
903
|
+
synthesis: collect.synthesis,
|
|
904
|
+
triage: collect.triage,
|
|
905
|
+
topFindings: collect.topFindings,
|
|
906
|
+
topTriageItems: collect.topTriageItems,
|
|
907
|
+
});
|
|
908
|
+
}
|
|
764
909
|
// ---------- tool registry ----------
|
|
765
910
|
const TOOLS = [
|
|
766
911
|
{
|
|
@@ -853,12 +998,12 @@ const TOOLS = [
|
|
|
853
998
|
},
|
|
854
999
|
{
|
|
855
1000
|
name: "codecarto_skill",
|
|
856
|
-
description: "Return the prompt text for a post-pipeline skill (only callable after all phases are complete). Use codecarto_status to confirm completion first.",
|
|
1001
|
+
description: "Return the prompt text for a post-pipeline skill (only callable after all phases are complete). Use codecarto_status to confirm completion first. One name is exempt from the completion gate: \"broadside\" returns the reading guide for a Broad-Side batch reconnaissance run, which is meant to be read before or during the pipeline and works without a workspace.",
|
|
857
1002
|
inputSchema: {
|
|
858
1003
|
type: "object",
|
|
859
1004
|
properties: {
|
|
860
1005
|
cwd: { type: "string", description: "Absolute path to the target repository." },
|
|
861
|
-
name: { type: "string", description: "Skill name (a directory under .codecarto/skills/)." },
|
|
1006
|
+
name: { type: "string", description: "Skill name (a directory under .codecarto/skills/), or \"broadside\" for the Broad-Side reading guide." },
|
|
862
1007
|
},
|
|
863
1008
|
required: ["cwd", "name"],
|
|
864
1009
|
},
|
|
@@ -897,6 +1042,10 @@ const TOOLS = [
|
|
|
897
1042
|
},
|
|
898
1043
|
},
|
|
899
1044
|
force_new_version: { type: "boolean" },
|
|
1045
|
+
allow_source_repo_change: {
|
|
1046
|
+
type: "boolean",
|
|
1047
|
+
description: "Permit publishing when the target entry already records a different source_repo. Off by default, because a mismatch usually means two projects derived the same slug and the spec would land in the wrong version history. Set only when the repository itself moved.",
|
|
1048
|
+
},
|
|
900
1049
|
},
|
|
901
1050
|
required: ["source_repo", "headline"],
|
|
902
1051
|
},
|
|
@@ -1004,7 +1153,7 @@ const TOOLS = [
|
|
|
1004
1153
|
},
|
|
1005
1154
|
{
|
|
1006
1155
|
name: "codecarto_list_skills",
|
|
1007
|
-
description: "List available post-pipeline skills installed in the workspace.",
|
|
1156
|
+
description: "List available post-pipeline skills installed in the workspace, plus the Broad-Side reading guide when it is present (that one is not pipeline-gated).",
|
|
1008
1157
|
inputSchema: {
|
|
1009
1158
|
type: "object",
|
|
1010
1159
|
properties: { cwd: { type: "string", description: "Absolute path to the target repository." } },
|
|
@@ -1032,6 +1181,63 @@ const TOOLS = [
|
|
|
1032
1181
|
required: ["cwd"],
|
|
1033
1182
|
},
|
|
1034
1183
|
},
|
|
1184
|
+
{
|
|
1185
|
+
name: "codecarto_broadside",
|
|
1186
|
+
description: "Broad-Side: fire a cheap batch reconnaissance scan at a repository via the OpenRouter Batch API. Six lenses (architecture, api, security, defect, conventions, porting) run as asynchronous single-turn prompts with structured JSON schemas; results land in .codecarto/broadside/<run>/ as JSON plus markdown, with an optional cross-lens synthesis report. Works on any git repository — no CodeCartographer workspace required. Requires an OpenRouter API key (api_key param, OPENROUTER_API_KEY env var, or .codecarto/broadside/config.yaml). Findings are unverified scouting signals from a batch model, not validated claims — they tell the interactive pipeline where to look. Actions: submit (fire batches, returns batch ids and cost estimate), collect (poll to completion, save results, optionally synthesize), status (show recorded runs), models (list batch-capable models with pricing, context, output caps, structured-output support, and optional coding benchmarks).",
|
|
1187
|
+
inputSchema: {
|
|
1188
|
+
type: "object",
|
|
1189
|
+
properties: {
|
|
1190
|
+
cwd: { type: "string", description: "Absolute path to the target repository." },
|
|
1191
|
+
action: {
|
|
1192
|
+
type: "string",
|
|
1193
|
+
enum: ["submit", "collect", "status", "models"],
|
|
1194
|
+
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.",
|
|
1195
|
+
},
|
|
1196
|
+
lenses: {
|
|
1197
|
+
type: "array",
|
|
1198
|
+
items: { type: "string", enum: [...BROADSIDE_LENS_IDS] },
|
|
1199
|
+
description: "Lenses to run (submit only). Defaults to all six.",
|
|
1200
|
+
},
|
|
1201
|
+
api_key: {
|
|
1202
|
+
type: "string",
|
|
1203
|
+
description: "OpenRouter API key. Prefer the OPENROUTER_API_KEY environment variable or .codecarto/broadside/config.yaml.",
|
|
1204
|
+
},
|
|
1205
|
+
wait_seconds: {
|
|
1206
|
+
type: "number",
|
|
1207
|
+
description: "For submit: after submitting, poll up to this many seconds before returning. For collect: poll up to this many seconds before returning with partial state. Falls back to wait_seconds in .codecarto/broadside/config.yaml.",
|
|
1208
|
+
},
|
|
1209
|
+
include_synthesis: {
|
|
1210
|
+
type: "boolean",
|
|
1211
|
+
description: "Run the cross-lens synthesis pass once all lens batches complete. Falls back to include_synthesis in .codecarto/broadside/config.yaml (default true).",
|
|
1212
|
+
},
|
|
1213
|
+
include_triage: {
|
|
1214
|
+
type: "boolean",
|
|
1215
|
+
description: "Run the triage pass once all lens batches complete: turns the findings into a prioritized work order (impact × difficulty, P0-P3, effort estimates). Falls back to include_triage in .codecarto/broadside/config.yaml (default true).",
|
|
1216
|
+
},
|
|
1217
|
+
retry_truncated: {
|
|
1218
|
+
type: "boolean",
|
|
1219
|
+
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).",
|
|
1220
|
+
},
|
|
1221
|
+
max_cost: {
|
|
1222
|
+
type: "number",
|
|
1223
|
+
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. Falls back to max_cost in .codecarto/broadside/config.yaml.",
|
|
1224
|
+
},
|
|
1225
|
+
force: {
|
|
1226
|
+
type: "boolean",
|
|
1227
|
+
description: "Submit even when the cost estimate exceeds max_cost (default false).",
|
|
1228
|
+
},
|
|
1229
|
+
incremental: {
|
|
1230
|
+
type: "boolean",
|
|
1231
|
+
description: "Diff against the previous run's git HEAD and scan only the modules whose files changed (falls back to a full scan on a dirty tree or when no prior run exists). Falls back to incremental in .codecarto/broadside/config.yaml (default false).",
|
|
1232
|
+
},
|
|
1233
|
+
include_benchmarks: {
|
|
1234
|
+
type: "boolean",
|
|
1235
|
+
description: "For action 'models': annotate each model with its Artificial Analysis coding index (extra API call; default false).",
|
|
1236
|
+
},
|
|
1237
|
+
},
|
|
1238
|
+
required: ["cwd", "action"],
|
|
1239
|
+
},
|
|
1240
|
+
},
|
|
1035
1241
|
];
|
|
1036
1242
|
const HANDLERS = {
|
|
1037
1243
|
codecarto_amend: handleAmend,
|
|
@@ -1055,6 +1261,7 @@ const HANDLERS = {
|
|
|
1055
1261
|
codecarto_dashboard: handleDashboard,
|
|
1056
1262
|
codecarto_list_skills: handleListSkills,
|
|
1057
1263
|
codecarto_guide: handleGuide,
|
|
1264
|
+
codecarto_broadside: handleBroadside,
|
|
1058
1265
|
};
|
|
1059
1266
|
export async function handleGuide(args) {
|
|
1060
1267
|
const topics = await listGuideTopics();
|