codecartographer-pi 0.24.1 → 0.26.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/broadside/SKILL.md +20 -1
- package/.codecarto/workflow/scaffold-version.yaml +1 -1
- package/README.md +5 -4
- package/agent-skill/codecartographer/references/broadside.md +5 -1
- package/dist/core/broadside/client.d.ts +56 -0
- package/dist/core/broadside/client.js +200 -0
- package/dist/core/broadside/collect.d.ts +68 -0
- package/dist/core/broadside/collect.js +676 -0
- package/dist/core/broadside/constants.d.ts +51 -0
- package/dist/core/broadside/constants.js +74 -0
- package/dist/core/broadside/lenses.d.ts +31 -0
- package/dist/core/broadside/lenses.js +312 -0
- package/dist/core/broadside/models.d.ts +46 -0
- package/dist/core/broadside/models.js +321 -0
- package/dist/core/broadside/render.d.ts +20 -0
- package/dist/core/broadside/render.js +285 -0
- package/dist/core/broadside/repo.d.ts +58 -0
- package/dist/core/broadside/repo.js +592 -0
- package/dist/core/broadside/requests.d.ts +23 -0
- package/dist/core/broadside/requests.js +71 -0
- package/dist/core/broadside/results.d.ts +36 -0
- package/dist/core/broadside/results.js +163 -0
- package/dist/core/broadside/schemas.d.ts +2 -0
- package/dist/core/broadside/schemas.js +342 -0
- package/dist/core/broadside/state.d.ts +99 -0
- package/dist/core/broadside/state.js +384 -0
- package/dist/core/broadside/submit.d.ts +30 -0
- package/dist/core/broadside/submit.js +350 -0
- package/dist/core/broadside/types.d.ts +491 -0
- package/dist/core/broadside/types.js +107 -0
- package/dist/core/{broadside-verify.d.ts → broadside/verify.d.ts} +23 -2
- package/dist/core/{broadside-verify.js → broadside/verify.js} +43 -5
- package/dist/core/broadside.d.ts +14 -890
- package/dist/core/broadside.js +25 -3564
- package/dist/core/completion.js +91 -72
- package/dist/core/dashboard-writer.js +9 -1
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +0 -1
- package/dist/core/library.d.ts +24 -1
- package/dist/core/library.js +46 -15
- package/dist/core/orchestrator-config.js +22 -8
- package/dist/core/status.d.ts +42 -23
- package/dist/core/status.js +163 -137
- package/dist/core/workspace.d.ts +2 -0
- package/dist/core/workspace.js +49 -25
- package/dist/core/yaml.js +9 -3
- package/dist/extensions/codecarto/auto-runner.d.ts +7 -0
- package/dist/extensions/codecarto/auto-runner.js +54 -23
- package/dist/extensions/codecarto/broadside-flags.d.ts +3 -1
- package/dist/extensions/codecarto/broadside-flags.js +13 -0
- package/dist/extensions/codecarto/index.js +13 -7
- package/dist/extensions/codecarto/phase-compaction.js +6 -2
- package/dist/mcp-server/server.d.ts +1 -0
- package/dist/mcp-server/server.js +28 -5
- package/package.json +1 -1
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import { type BroadsideLensId } from "./constants.ts";
|
|
2
|
+
export type ModelPricing = {
|
|
3
|
+
/** USD per million input tokens. */
|
|
4
|
+
inputPerM: number;
|
|
5
|
+
/** USD per million output tokens. */
|
|
6
|
+
outputPerM: number;
|
|
7
|
+
/** Where the numbers came from — affects what the submit text claims. */
|
|
8
|
+
source: "built-in" | "config" | "live" | "cache";
|
|
9
|
+
};
|
|
10
|
+
/** The subset of the OpenRouter model catalog Broad-Side actually uses. */
|
|
11
|
+
export type CatalogEntry = {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
inputPerM: number;
|
|
15
|
+
outputPerM: number;
|
|
16
|
+
cachedInputPerM?: number;
|
|
17
|
+
contextLength?: number;
|
|
18
|
+
maxCompletionTokens?: number;
|
|
19
|
+
/** Empty array means unknown, not "supports nothing". */
|
|
20
|
+
supportedParameters: string[];
|
|
21
|
+
expirationDate?: string | null;
|
|
22
|
+
};
|
|
23
|
+
export type CodingBenchmarks = {
|
|
24
|
+
/** Base model slug (batch suffix stripped) → indices. */
|
|
25
|
+
byBaseSlug: Record<string, {
|
|
26
|
+
codingIndex?: number;
|
|
27
|
+
intelligenceIndex?: number;
|
|
28
|
+
}>;
|
|
29
|
+
/** Citation/attribution metadata from the benchmarks endpoint. */
|
|
30
|
+
meta: Record<string, unknown>;
|
|
31
|
+
};
|
|
32
|
+
export type BroadsideCatalogResult = {
|
|
33
|
+
model: string;
|
|
34
|
+
source: "built-in" | "config" | "live" | "cache";
|
|
35
|
+
/**
|
|
36
|
+
* Always resolved. `resolveCatalogEntry` either returns an entry — from
|
|
37
|
+
* config, cache, the live catalog, or the compile-time fallback — or throws
|
|
38
|
+
* naming the model it could not price. This was declared nullable, which is
|
|
39
|
+
* the only reason the single consumer needed a non-null assertion to read it.
|
|
40
|
+
*/
|
|
41
|
+
entry: CatalogEntry;
|
|
42
|
+
benchmarks?: CodingBenchmarks;
|
|
43
|
+
};
|
|
44
|
+
export type JsonSchemaDef = {
|
|
45
|
+
name: string;
|
|
46
|
+
strict: boolean;
|
|
47
|
+
schema: Record<string, unknown>;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Where the file list and the file contents both came from — one source, so
|
|
51
|
+
* a run's results correspond to one state of the repository (#248).
|
|
52
|
+
* `working-tree`: git's view of the checkout (tracked plus untracked files,
|
|
53
|
+
* ignore rules applied, files deleted on disk left out); `walk`: a bounded
|
|
54
|
+
* directory walk, for a target that is not a git repository.
|
|
55
|
+
*/
|
|
56
|
+
export type RepoSnapshotSource = "working-tree" | "walk";
|
|
57
|
+
export type RepoInfo = {
|
|
58
|
+
name: string;
|
|
59
|
+
path: string;
|
|
60
|
+
language: string;
|
|
61
|
+
manifest: {
|
|
62
|
+
path: string;
|
|
63
|
+
content: string;
|
|
64
|
+
} | null;
|
|
65
|
+
mainFile: string;
|
|
66
|
+
readmeFirst: string;
|
|
67
|
+
fileTree: string;
|
|
68
|
+
fileCounts: Record<string, number>;
|
|
69
|
+
sourceGlob: string;
|
|
70
|
+
sourceExts: string[];
|
|
71
|
+
/** How many slurpable files carry one of `sourceExts`; zero means no lens has code to scan. */
|
|
72
|
+
sourceFileCount: number;
|
|
73
|
+
snapshot: RepoSnapshotSource;
|
|
74
|
+
/** Files left out of every lens because their name says they hold secrets (#252). */
|
|
75
|
+
secretFilesSkipped: string[];
|
|
76
|
+
/** Secret-like values redacted from the entry point, manifest, and README excerpt. */
|
|
77
|
+
redactedValues: number;
|
|
78
|
+
};
|
|
79
|
+
export type FileSlice = {
|
|
80
|
+
moduleName: string;
|
|
81
|
+
content: string;
|
|
82
|
+
fileCount: number;
|
|
83
|
+
chars: number;
|
|
84
|
+
/** Repo-relative paths of the files folded into this slice. */
|
|
85
|
+
files: string[];
|
|
86
|
+
/** Secret-like values redacted from this slice's files before upload (#252). */
|
|
87
|
+
redactedValues?: number;
|
|
88
|
+
/** The files in this slice that had at least one value redacted. */
|
|
89
|
+
redactedFiles?: string[];
|
|
90
|
+
/**
|
|
91
|
+
* Set when the lens's targeted globs matched nothing and the slice was
|
|
92
|
+
* built from its fallback globs instead (#319). The estimate, the batch
|
|
93
|
+
* entry, and the prompt all say so.
|
|
94
|
+
*/
|
|
95
|
+
fallback?: string;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* OpenRouter's unified `reasoning` control, as sent on a lens request.
|
|
99
|
+
*
|
|
100
|
+
* Left unsent, each model applies its own default — which is how a
|
|
101
|
+
* reasoning-capable model came to spend 5,758 of a 6,000-token output budget
|
|
102
|
+
* thinking, leaving ~230 tokens for JSON that then truncated mid-structure. The
|
|
103
|
+
* thinking is billed at the full *output* rate, so the run paid for roughly
|
|
104
|
+
* 6,000 output tokens per slice to receive 230 usable ones.
|
|
105
|
+
*/
|
|
106
|
+
export type BroadsideReasoning = {
|
|
107
|
+
enabled?: boolean;
|
|
108
|
+
effort?: "minimal" | "low" | "medium" | "high";
|
|
109
|
+
max_tokens?: number;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* The reasoning control every lens request carries: low effort.
|
|
113
|
+
*
|
|
114
|
+
* It used to be a token cap — `max_tokens` at a quarter of the lens's output
|
|
115
|
+
* budget, so three quarters stayed for the answer. Measured live on
|
|
116
|
+
* `google/gemini-3.8-flash:batch` (0.22.0 verification, defect lens, cap
|
|
117
|
+
* 5,800 of a 6,000 budget): the model reasoned 5,218 tokens on the first
|
|
118
|
+
* pass and **11,518 under the same cap** on the doubled-budget retry —
|
|
119
|
+
* thinking scaled with `max_tokens` and the cap changed nothing, both
|
|
120
|
+
* results truncated, and the retry cost twice the original for no JSON.
|
|
121
|
+
* The same lens with `effort: "low"` reasoned 0 tokens, finished with
|
|
122
|
+
* `stop`, returned valid JSON, and cost a twelfth as much. Gemini 3.x
|
|
123
|
+
* models take a thinking *level*, not a budget, and OpenRouter forwards a
|
|
124
|
+
* `max_tokens` cap to them as nothing at all; `effort` is what it can
|
|
125
|
+
* translate for every provider (a level where the provider has levels, a
|
|
126
|
+
* fraction of the budget where it takes a budget). So the default asks for
|
|
127
|
+
* little thinking in the one vocabulary that reaches everyone.
|
|
128
|
+
*
|
|
129
|
+
* Deliberately not `enabled: false`: `google/gemini-3.8-flash:batch` refuses
|
|
130
|
+
* the whole batch with *"Reasoning is mandatory for this endpoint and cannot
|
|
131
|
+
* be disabled"*, turning a partial result into none at all. Low effort works
|
|
132
|
+
* whether or not a provider allows reasoning to be switched off.
|
|
133
|
+
*/
|
|
134
|
+
export declare const BROADSIDE_DEFAULT_REASONING: Readonly<BroadsideReasoning>;
|
|
135
|
+
/** The reasoning control a lens request carries when config.yaml sets none. */
|
|
136
|
+
export declare function defaultReasoningFor(): BroadsideReasoning;
|
|
137
|
+
/**
|
|
138
|
+
* The reasoning control a truncated slice is re-submitted with.
|
|
139
|
+
*
|
|
140
|
+
* A truncation on a reasoning-capable model is usually thinking that ate the
|
|
141
|
+
* answer's budget, and doubling `max_tokens` doubles the thinking where the
|
|
142
|
+
* provider ignores a token cap (see {@link BROADSIDE_DEFAULT_REASONING}). The
|
|
143
|
+
* retry therefore asks for low effort as well, replacing a `max_tokens` cap
|
|
144
|
+
* (OpenRouter refuses a request carrying both) and lowering a higher effort.
|
|
145
|
+
* An explicit `enabled: false` and an effort already at or below low are left
|
|
146
|
+
* as they are.
|
|
147
|
+
*/
|
|
148
|
+
export declare function retryReasoningFor(original: BroadsideReasoning | undefined): BroadsideReasoning;
|
|
149
|
+
export type BatchRequest = {
|
|
150
|
+
custom_id: string;
|
|
151
|
+
body: {
|
|
152
|
+
model: string;
|
|
153
|
+
messages: {
|
|
154
|
+
role: "system" | "user";
|
|
155
|
+
content: string;
|
|
156
|
+
}[];
|
|
157
|
+
response_format: {
|
|
158
|
+
type: "json_schema";
|
|
159
|
+
json_schema: JsonSchemaDef;
|
|
160
|
+
};
|
|
161
|
+
max_tokens: number;
|
|
162
|
+
reasoning?: BroadsideReasoning;
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
export type BatchTerminalStatus = "completed" | "failed" | "expired" | "cancelled";
|
|
166
|
+
export type BroadsideBatchEntry = {
|
|
167
|
+
batchId: string;
|
|
168
|
+
requests: number;
|
|
169
|
+
status: string;
|
|
170
|
+
submittedAt: string;
|
|
171
|
+
completedAt?: string;
|
|
172
|
+
estimatedCost: number;
|
|
173
|
+
cost?: number;
|
|
174
|
+
resultCount?: number;
|
|
175
|
+
error?: unknown;
|
|
176
|
+
/** Why a `skipped` lens had nothing to submit: the globs that matched no file. */
|
|
177
|
+
reason?: string;
|
|
178
|
+
/**
|
|
179
|
+
* Set when the lens scanned its fallback scope because its targeted globs
|
|
180
|
+
* matched nothing (#319): "no files matched …; scanned all javascript
|
|
181
|
+
* sources instead". Absent for a targeted scan.
|
|
182
|
+
*/
|
|
183
|
+
fallback?: string;
|
|
184
|
+
/** Set when this lens used a model other than the run default. */
|
|
185
|
+
model?: string;
|
|
186
|
+
/** The completion ceiling of this lens's model; bounds the truncation retry. */
|
|
187
|
+
outputCap?: number;
|
|
188
|
+
};
|
|
189
|
+
export type BroadsideSynthesisEntry = {
|
|
190
|
+
batchId?: string;
|
|
191
|
+
status: "pending" | "submitted" | "completed" | "failed";
|
|
192
|
+
cost?: number;
|
|
193
|
+
/** Why the pass was retired, when the batch reported one. */
|
|
194
|
+
error?: string;
|
|
195
|
+
/**
|
|
196
|
+
* How many verification verdicts the pass was built from (#338): the
|
|
197
|
+
* `verified.json` a `verify` pass wrote before this pass was submitted.
|
|
198
|
+
* Absent when the pass was built from the lens findings alone.
|
|
199
|
+
*/
|
|
200
|
+
verdicts?: number;
|
|
201
|
+
};
|
|
202
|
+
/** One triage item — a scouting lead turned into a work-order entry. */
|
|
203
|
+
export type TriageItem = {
|
|
204
|
+
title: string;
|
|
205
|
+
severity: string;
|
|
206
|
+
module: string;
|
|
207
|
+
impact: "high" | "medium" | "low";
|
|
208
|
+
difficulty: "high" | "medium" | "low";
|
|
209
|
+
priority: string;
|
|
210
|
+
effort_estimate: string;
|
|
211
|
+
rationale: string;
|
|
212
|
+
};
|
|
213
|
+
/** The triage post-pass entry: the same shape as synthesis's. */
|
|
214
|
+
export type BroadsideTriageEntry = BroadsideSynthesisEntry;
|
|
215
|
+
/** Recorded on the run once a verification pass has run (#143); see core/broadside/verify.ts. */
|
|
216
|
+
export type BroadsideVerifyEntry = {
|
|
217
|
+
/** `completed`: every selected finding got a verdict; `partial`: the cost cap or an abort stopped it early. */
|
|
218
|
+
status: "completed" | "partial";
|
|
219
|
+
model: string;
|
|
220
|
+
top: number;
|
|
221
|
+
verified: number;
|
|
222
|
+
confirmed: number;
|
|
223
|
+
cost: number;
|
|
224
|
+
at: string;
|
|
225
|
+
/** Secret-like values redacted from the pass's tool output before upload (#358); absent on passes from before it. */
|
|
226
|
+
redactedValues?: number;
|
|
227
|
+
};
|
|
228
|
+
/** The truncation retry pass of one run: one batch per model (#206). */
|
|
229
|
+
export type BroadsideRetryEntry = {
|
|
230
|
+
status: "submitted" | "completed" | "failed";
|
|
231
|
+
batches: Array<{
|
|
232
|
+
model: string;
|
|
233
|
+
batchId: string;
|
|
234
|
+
}>;
|
|
235
|
+
/** When the owning collect claimed the pass (#322). */
|
|
236
|
+
claimedAt: string;
|
|
237
|
+
/** What the retry batches cost, once polled to completion. */
|
|
238
|
+
cost?: number;
|
|
239
|
+
/** Why a retry batch was refused at submit, per model (#370). */
|
|
240
|
+
error?: string;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* The parts of a run that cost money to submit and that exactly one collect
|
|
244
|
+
* may own: the two post-passes and the truncation retry (#322).
|
|
245
|
+
*/
|
|
246
|
+
export type BroadsideRunSlot = "synthesis" | "triage" | "retry";
|
|
247
|
+
export declare const BROADSIDE_RUN_SLOTS: readonly BroadsideRunSlot[];
|
|
248
|
+
export type BroadsideRun = {
|
|
249
|
+
id: string;
|
|
250
|
+
createdAt: string;
|
|
251
|
+
model: string;
|
|
252
|
+
lenses: BroadsideLensId[];
|
|
253
|
+
status: "in-flight" | "completed" | "partial" | "failed";
|
|
254
|
+
outputDir: string;
|
|
255
|
+
batches: Partial<Record<BroadsideLensId, BroadsideBatchEntry>>;
|
|
256
|
+
synthesis: BroadsideSynthesisEntry;
|
|
257
|
+
triage: BroadsideTriageEntry;
|
|
258
|
+
/**
|
|
259
|
+
* The truncation retry pass (#133), recorded so that two collects on one
|
|
260
|
+
* run cannot both submit it (#322). Absent until a collect claims it.
|
|
261
|
+
*/
|
|
262
|
+
retry?: BroadsideRetryEntry;
|
|
263
|
+
/** The verification pass over the top findings, when one has run (#143). */
|
|
264
|
+
verify?: BroadsideVerifyEntry;
|
|
265
|
+
/**
|
|
266
|
+
* What post-pass results that were later regenerated had cost (#338):
|
|
267
|
+
* money the run spent that no current entry accounts for.
|
|
268
|
+
*/
|
|
269
|
+
retiredCost?: number;
|
|
270
|
+
totalCost?: number;
|
|
271
|
+
pricing?: ModelPricing;
|
|
272
|
+
maxCost?: number;
|
|
273
|
+
/** The model's completion ceiling, recorded so collect can cap retries. */
|
|
274
|
+
outputCap?: number;
|
|
275
|
+
/** Git HEAD at submit time, for incremental re-scouting (#142). */
|
|
276
|
+
sourceHead?: string | null;
|
|
277
|
+
/** Whether the working tree was dirty at submit time. */
|
|
278
|
+
sourceDirty?: boolean;
|
|
279
|
+
/** When incremental, the previous run's HEAD this run diffs against. */
|
|
280
|
+
baseHead?: string | null;
|
|
281
|
+
/** Where the scanned files and their contents were read from (#248). */
|
|
282
|
+
snapshot?: RepoSnapshotSource;
|
|
283
|
+
/** The language the lenses scanned as. */
|
|
284
|
+
language?: string;
|
|
285
|
+
/** What the secret-redaction pass did before upload (#252); absent on runs from before it. */
|
|
286
|
+
redaction?: {
|
|
287
|
+
enabled: boolean;
|
|
288
|
+
values: number;
|
|
289
|
+
files: number;
|
|
290
|
+
skippedFiles: number;
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
export type BroadsideStateFile = {
|
|
294
|
+
schema_version: number;
|
|
295
|
+
runs: BroadsideRun[];
|
|
296
|
+
};
|
|
297
|
+
export type BroadsideConfig = {
|
|
298
|
+
model: string;
|
|
299
|
+
apiKey: string;
|
|
300
|
+
defaultLenses: BroadsideLensId[];
|
|
301
|
+
/** Approximate run expense limit in USD; 0 means no limit. */
|
|
302
|
+
maxCost: number;
|
|
303
|
+
/** Manual pricing overrides (USD per million). Live lookup is preferred. */
|
|
304
|
+
pricing: {
|
|
305
|
+
inputPerM: number;
|
|
306
|
+
outputPerM: number;
|
|
307
|
+
} | null;
|
|
308
|
+
/**
|
|
309
|
+
* Per-lens model overrides. A lens absent here uses `model`. This is how a
|
|
310
|
+
* repository routes the semantic lenses (security, defect) to a stronger
|
|
311
|
+
* batch model while the cheap default carries the rest — the whole point of
|
|
312
|
+
* the cheap model is telling the expensive one where to look, and that
|
|
313
|
+
* trade-off is not the same for every lens.
|
|
314
|
+
*/
|
|
315
|
+
lensModels: Partial<Record<BroadsideLensId, string>>;
|
|
316
|
+
/** Overrides every lens's reasoning setting when present. */
|
|
317
|
+
reasoning: BroadsideReasoning | null;
|
|
318
|
+
/**
|
|
319
|
+
* Repo defaults for the per-call run knobs. Each mirrors a tool parameter
|
|
320
|
+
* of the same name; an explicit parameter always wins. They live here so a
|
|
321
|
+
* repository can fix its own scouting policy once instead of restating it
|
|
322
|
+
* on every submit and collect.
|
|
323
|
+
*/
|
|
324
|
+
incremental: boolean;
|
|
325
|
+
retryTruncated: boolean;
|
|
326
|
+
includeSynthesis: boolean;
|
|
327
|
+
includeTriage: boolean;
|
|
328
|
+
/** Default poll budget in seconds; 0 means "return immediately". */
|
|
329
|
+
waitSeconds: number;
|
|
330
|
+
/**
|
|
331
|
+
* Replace secret-like values with `[REDACTED:<kind>]` and skip files named
|
|
332
|
+
* like credential stores before anything is uploaded (#252). On by default;
|
|
333
|
+
* off only for a repository whose maintainers have decided its contents may
|
|
334
|
+
* leave as they are.
|
|
335
|
+
*/
|
|
336
|
+
redactSecrets: boolean;
|
|
337
|
+
};
|
|
338
|
+
/**
|
|
339
|
+
* The pre-flight facts a caller needs to decide whether a run is worth its
|
|
340
|
+
* price: what each lens would cost, at what rates, against which limit. Handed
|
|
341
|
+
* to {@link BroadsideSubmitOptions.confirm} before anything is submitted.
|
|
342
|
+
*/
|
|
343
|
+
export type BroadsideEstimate = {
|
|
344
|
+
model: string;
|
|
345
|
+
pricing: ModelPricing;
|
|
346
|
+
lenses: Array<{
|
|
347
|
+
lensId: BroadsideLensId;
|
|
348
|
+
name: string;
|
|
349
|
+
slices: number;
|
|
350
|
+
maxTokens: number;
|
|
351
|
+
cost: number;
|
|
352
|
+
/** The model this lens would use — `model` unless a per-lens override applies. */
|
|
353
|
+
model: string;
|
|
354
|
+
pricing: ModelPricing;
|
|
355
|
+
/** Set when this lens is priced on its fallback scope (#319); see BroadsideBatchEntry.fallback. */
|
|
356
|
+
fallback?: string;
|
|
357
|
+
}>;
|
|
358
|
+
/** True when at least one lens uses a model other than the run default. */
|
|
359
|
+
mixedModels: boolean;
|
|
360
|
+
totalCost: number;
|
|
361
|
+
inputTokens: number;
|
|
362
|
+
outputTokens: number;
|
|
363
|
+
/** Run expense limit in USD; 0 means no limit. */
|
|
364
|
+
maxCost: number;
|
|
365
|
+
/** True when totalCost is over a non-zero maxCost. */
|
|
366
|
+
exceedsLimit: boolean;
|
|
367
|
+
/** Set when incremental scouting found a baseline to diff against. */
|
|
368
|
+
baseHead: string | null;
|
|
369
|
+
sourceDirty: boolean;
|
|
370
|
+
/** Whether a requested incremental run actually narrowed this estimate. */
|
|
371
|
+
incremental: BroadsideIncrementalOutcome;
|
|
372
|
+
/** The provider's completion ceiling, when the catalog advertises one. */
|
|
373
|
+
outputCap?: number;
|
|
374
|
+
};
|
|
375
|
+
/**
|
|
376
|
+
* OpenRouter rejected the API key (HTTP 401/403). Thrown from the catalog
|
|
377
|
+
* lookup rather than swallowed into "could not price" or a silent built-in
|
|
378
|
+
* fallback: a run that cannot authenticate cannot submit either, and the
|
|
379
|
+
* message that reaches the user has to say so (#251).
|
|
380
|
+
*/
|
|
381
|
+
export declare class BroadsideAuthError extends Error {
|
|
382
|
+
readonly httpStatus: number;
|
|
383
|
+
readonly detail: string;
|
|
384
|
+
constructor(httpStatus: number, detail: string);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* `broadside/config.yaml` exists but cannot be used. A file that failed to
|
|
388
|
+
* parse used to be treated exactly like an absent one — defaults, including
|
|
389
|
+
* no spend cap and no lens routing, with no message — so a typo removed the
|
|
390
|
+
* user's own guard (#232). Only an absent file yields defaults now.
|
|
391
|
+
*/
|
|
392
|
+
export declare class BroadsideConfigError extends Error {
|
|
393
|
+
readonly path: string;
|
|
394
|
+
constructor(path: string, detail: string);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* `broadside/state.json` exists but cannot be read. It used to be read as
|
|
398
|
+
* empty and the next checkpoint wrote that empty state over it, losing the
|
|
399
|
+
* batch ids of every in-flight, already-paid run (#233). The corrupt file is
|
|
400
|
+
* preserved beside itself and nothing writes over it until someone looks.
|
|
401
|
+
*/
|
|
402
|
+
export declare class BroadsideStateError extends Error {
|
|
403
|
+
readonly path: string;
|
|
404
|
+
readonly backupPath: string;
|
|
405
|
+
constructor(path: string, backupPath: string, detail: string);
|
|
406
|
+
}
|
|
407
|
+
/** Thrown when a confirm hook declines a run. Nothing was submitted. */
|
|
408
|
+
export declare class BroadsideCancelledError extends Error {
|
|
409
|
+
constructor(message?: string);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Whether incremental scouting actually narrowed the run.
|
|
413
|
+
*
|
|
414
|
+
* A request for incremental falls back to a full scan whenever there is nothing
|
|
415
|
+
* to diff against, and that fallback costs real money — the caller asked for the
|
|
416
|
+
* cheap mode and gets the expensive one. It must be reported, not inferred from
|
|
417
|
+
* the request counts.
|
|
418
|
+
*/
|
|
419
|
+
export type BroadsideIncrementalOutcome = {
|
|
420
|
+
requested: boolean;
|
|
421
|
+
applied: boolean;
|
|
422
|
+
/** The commit the run diffed against, when one was found. */
|
|
423
|
+
baseHead: string | null;
|
|
424
|
+
/** Why a requested incremental run did not apply. */
|
|
425
|
+
reason?: "dirty-worktree" | "no-baseline" | "diff-failed";
|
|
426
|
+
};
|
|
427
|
+
export type BroadsideSubmitResult = {
|
|
428
|
+
runId: string;
|
|
429
|
+
outputDir: string;
|
|
430
|
+
batches: Partial<Record<BroadsideLensId, BroadsideBatchEntry>>;
|
|
431
|
+
estimatedTotalCost: number;
|
|
432
|
+
estimatedInputTokens: number;
|
|
433
|
+
estimatedOutputTokens: number;
|
|
434
|
+
pricing: ModelPricing;
|
|
435
|
+
maxCost?: number;
|
|
436
|
+
modelInfo: {
|
|
437
|
+
contextLength?: number;
|
|
438
|
+
maxCompletionTokens?: number;
|
|
439
|
+
supportsStructuredOutputs?: boolean;
|
|
440
|
+
expirationDate?: string | null;
|
|
441
|
+
};
|
|
442
|
+
incremental: BroadsideIncrementalOutcome;
|
|
443
|
+
/** What was scanned: the language the lenses ran as and the snapshot the files came from. */
|
|
444
|
+
repo: {
|
|
445
|
+
language: string;
|
|
446
|
+
sourceFiles: number;
|
|
447
|
+
snapshot: RepoSnapshotSource;
|
|
448
|
+
sourceHead: string | null;
|
|
449
|
+
sourceDirty: boolean;
|
|
450
|
+
};
|
|
451
|
+
/** What the secret-redaction pass did before upload (#252). */
|
|
452
|
+
redaction: {
|
|
453
|
+
enabled: boolean;
|
|
454
|
+
values: number;
|
|
455
|
+
files: number;
|
|
456
|
+
skippedFiles: string[];
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
export type BroadsideCollectResult = {
|
|
460
|
+
runId: string;
|
|
461
|
+
status: string;
|
|
462
|
+
totalCost: number;
|
|
463
|
+
resultCount: number;
|
|
464
|
+
/** Results whose JSON did not parse even after fence stripping —
|
|
465
|
+
* the signature of an output cut off at max_tokens. */
|
|
466
|
+
truncatedCount: number;
|
|
467
|
+
/** Truncated slices recovered by the automatic re-submit pass (#133). */
|
|
468
|
+
retriedCount: number;
|
|
469
|
+
/** Another collect on this run owns the retry pass; its result lands on a later collect (#322). */
|
|
470
|
+
retryElsewhere?: boolean;
|
|
471
|
+
/** Why the truncation retry could not be submitted, when it was refused (#370). */
|
|
472
|
+
retryError?: string;
|
|
473
|
+
lensOutcomes: Partial<Record<BroadsideLensId, {
|
|
474
|
+
status: string;
|
|
475
|
+
cost?: number;
|
|
476
|
+
resultCount?: number;
|
|
477
|
+
truncated?: number;
|
|
478
|
+
error?: string;
|
|
479
|
+
}>>;
|
|
480
|
+
synthesis: BroadsideSynthesisEntry;
|
|
481
|
+
triage: BroadsideTriageEntry;
|
|
482
|
+
topFindings: {
|
|
483
|
+
title: string;
|
|
484
|
+
severity: string;
|
|
485
|
+
sourceLens: string;
|
|
486
|
+
summary: string;
|
|
487
|
+
}[];
|
|
488
|
+
topTriageItems: TriageItem[];
|
|
489
|
+
/** The post-passes this collect reset and re-ran on request (#338). */
|
|
490
|
+
regenerated?: Array<"synthesis" | "triage">;
|
|
491
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Broad-Side types: repository info, slices, batch requests and entries, runs, state, config, results, errors; the reasoning defaults.
|
|
2
|
+
//
|
|
3
|
+
// Split out of core/broadside.ts (#339); the barrel there re-exports every
|
|
4
|
+
// name, so `core/index.ts` and the tests see one module as before.
|
|
5
|
+
/**
|
|
6
|
+
* The reasoning control every lens request carries: low effort.
|
|
7
|
+
*
|
|
8
|
+
* It used to be a token cap — `max_tokens` at a quarter of the lens's output
|
|
9
|
+
* budget, so three quarters stayed for the answer. Measured live on
|
|
10
|
+
* `google/gemini-3.8-flash:batch` (0.22.0 verification, defect lens, cap
|
|
11
|
+
* 5,800 of a 6,000 budget): the model reasoned 5,218 tokens on the first
|
|
12
|
+
* pass and **11,518 under the same cap** on the doubled-budget retry —
|
|
13
|
+
* thinking scaled with `max_tokens` and the cap changed nothing, both
|
|
14
|
+
* results truncated, and the retry cost twice the original for no JSON.
|
|
15
|
+
* The same lens with `effort: "low"` reasoned 0 tokens, finished with
|
|
16
|
+
* `stop`, returned valid JSON, and cost a twelfth as much. Gemini 3.x
|
|
17
|
+
* models take a thinking *level*, not a budget, and OpenRouter forwards a
|
|
18
|
+
* `max_tokens` cap to them as nothing at all; `effort` is what it can
|
|
19
|
+
* translate for every provider (a level where the provider has levels, a
|
|
20
|
+
* fraction of the budget where it takes a budget). So the default asks for
|
|
21
|
+
* little thinking in the one vocabulary that reaches everyone.
|
|
22
|
+
*
|
|
23
|
+
* Deliberately not `enabled: false`: `google/gemini-3.8-flash:batch` refuses
|
|
24
|
+
* the whole batch with *"Reasoning is mandatory for this endpoint and cannot
|
|
25
|
+
* be disabled"*, turning a partial result into none at all. Low effort works
|
|
26
|
+
* whether or not a provider allows reasoning to be switched off.
|
|
27
|
+
*/
|
|
28
|
+
export const BROADSIDE_DEFAULT_REASONING = Object.freeze({ effort: "low" });
|
|
29
|
+
/** The reasoning control a lens request carries when config.yaml sets none. */
|
|
30
|
+
export function defaultReasoningFor() {
|
|
31
|
+
return { ...BROADSIDE_DEFAULT_REASONING };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The reasoning control a truncated slice is re-submitted with.
|
|
35
|
+
*
|
|
36
|
+
* A truncation on a reasoning-capable model is usually thinking that ate the
|
|
37
|
+
* answer's budget, and doubling `max_tokens` doubles the thinking where the
|
|
38
|
+
* provider ignores a token cap (see {@link BROADSIDE_DEFAULT_REASONING}). The
|
|
39
|
+
* retry therefore asks for low effort as well, replacing a `max_tokens` cap
|
|
40
|
+
* (OpenRouter refuses a request carrying both) and lowering a higher effort.
|
|
41
|
+
* An explicit `enabled: false` and an effort already at or below low are left
|
|
42
|
+
* as they are.
|
|
43
|
+
*/
|
|
44
|
+
export function retryReasoningFor(original) {
|
|
45
|
+
if (original?.enabled === false)
|
|
46
|
+
return { ...original };
|
|
47
|
+
if (original?.effort === "minimal" || original?.effort === "low")
|
|
48
|
+
return { ...original };
|
|
49
|
+
const { max_tokens: _cap, effort: _effort, ...rest } = original ?? {};
|
|
50
|
+
return { ...rest, effort: "low" };
|
|
51
|
+
}
|
|
52
|
+
export const BROADSIDE_RUN_SLOTS = ["synthesis", "triage", "retry"];
|
|
53
|
+
/**
|
|
54
|
+
* OpenRouter rejected the API key (HTTP 401/403). Thrown from the catalog
|
|
55
|
+
* lookup rather than swallowed into "could not price" or a silent built-in
|
|
56
|
+
* fallback: a run that cannot authenticate cannot submit either, and the
|
|
57
|
+
* message that reaches the user has to say so (#251).
|
|
58
|
+
*/
|
|
59
|
+
export class BroadsideAuthError extends Error {
|
|
60
|
+
httpStatus;
|
|
61
|
+
detail;
|
|
62
|
+
constructor(httpStatus, detail) {
|
|
63
|
+
super(`OpenRouter rejected the API key (HTTP ${httpStatus}${detail ? `: ${detail}` : ""}). ` +
|
|
64
|
+
"Check OPENROUTER_API_KEY, the api_key parameter, or api_key in .codecarto/broadside/config.yaml. Nothing was submitted.");
|
|
65
|
+
this.name = "BroadsideAuthError";
|
|
66
|
+
this.httpStatus = httpStatus;
|
|
67
|
+
this.detail = detail;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* `broadside/config.yaml` exists but cannot be used. A file that failed to
|
|
72
|
+
* parse used to be treated exactly like an absent one — defaults, including
|
|
73
|
+
* no spend cap and no lens routing, with no message — so a typo removed the
|
|
74
|
+
* user's own guard (#232). Only an absent file yields defaults now.
|
|
75
|
+
*/
|
|
76
|
+
export class BroadsideConfigError extends Error {
|
|
77
|
+
path;
|
|
78
|
+
constructor(path, detail) {
|
|
79
|
+
super(`Broad-Side config ${path} ${detail}. Fix or remove the file; nothing runs on defaults while it is unreadable.`);
|
|
80
|
+
this.name = "BroadsideConfigError";
|
|
81
|
+
this.path = path;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `broadside/state.json` exists but cannot be read. It used to be read as
|
|
86
|
+
* empty and the next checkpoint wrote that empty state over it, losing the
|
|
87
|
+
* batch ids of every in-flight, already-paid run (#233). The corrupt file is
|
|
88
|
+
* preserved beside itself and nothing writes over it until someone looks.
|
|
89
|
+
*/
|
|
90
|
+
export class BroadsideStateError extends Error {
|
|
91
|
+
path;
|
|
92
|
+
backupPath;
|
|
93
|
+
constructor(path, backupPath, detail) {
|
|
94
|
+
super(`Broad-Side state ${path} ${detail}. A copy is preserved at ${backupPath}; the file is not overwritten. ` +
|
|
95
|
+
"Repair state.json from the copy (each run's batch ids are what collect needs), or move it aside to start fresh.");
|
|
96
|
+
this.name = "BroadsideStateError";
|
|
97
|
+
this.path = path;
|
|
98
|
+
this.backupPath = backupPath;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Thrown when a confirm hook declines a run. Nothing was submitted. */
|
|
102
|
+
export class BroadsideCancelledError extends Error {
|
|
103
|
+
constructor(message = "Broad-Side submission cancelled. Nothing was submitted.") {
|
|
104
|
+
super(message);
|
|
105
|
+
this.name = "BroadsideCancelledError";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { type BroadsideLensId
|
|
1
|
+
import { type BroadsideLensId } from "./constants.ts";
|
|
2
|
+
import { type BroadsideVerifyEntry } from "./types.ts";
|
|
3
|
+
import { type FetchLike } from "./client.ts";
|
|
4
|
+
import { type StoredLensResult } from "./results.ts";
|
|
2
5
|
export declare const BROADSIDE_CHAT_URL = "https://openrouter.ai/api/v1/chat/completions";
|
|
3
6
|
/** How many findings `verify` reads by default, most severe first. */
|
|
4
7
|
export declare const BROADSIDE_VERIFY_DEFAULT_TOP = 10;
|
|
@@ -36,6 +39,10 @@ export type BroadsideVerifyResult = {
|
|
|
36
39
|
totalCost: number;
|
|
37
40
|
/** Set when the cost cap stopped the pass before every selected finding was read. */
|
|
38
41
|
stoppedByCost?: boolean;
|
|
42
|
+
/** Secret-like values redacted from tool output before it reached the model (#358). */
|
|
43
|
+
redactedValues: number;
|
|
44
|
+
/** The files those values were in. */
|
|
45
|
+
redactedFiles: string[];
|
|
39
46
|
};
|
|
40
47
|
type CandidateFinding = {
|
|
41
48
|
lensId: BroadsideLensId;
|
|
@@ -54,6 +61,11 @@ export type RepoReader = {
|
|
|
54
61
|
readFile(path: string, startLine?: number, endLine?: number): Promise<string>;
|
|
55
62
|
grep(pattern: string, pathPrefix?: string): Promise<string>;
|
|
56
63
|
listDir(path: string): Promise<string>;
|
|
64
|
+
/** What the redaction pass did to this reader's output so far (#358). */
|
|
65
|
+
readonly redactions: {
|
|
66
|
+
values: number;
|
|
67
|
+
files: Set<string>;
|
|
68
|
+
};
|
|
57
69
|
};
|
|
58
70
|
/**
|
|
59
71
|
* Three read-only tools over the repository's own file list — the same
|
|
@@ -61,8 +73,17 @@ export type RepoReader = {
|
|
|
61
73
|
* everything {@link isSlurpable} keeps out of a lens: credential stores, build
|
|
62
74
|
* output, binaries. A path outside the repository, or one the listing does
|
|
63
75
|
* not contain, is an error the model sees, not a read.
|
|
76
|
+
*
|
|
77
|
+
* Every line the reader hands back goes through the same secret-redaction
|
|
78
|
+
* pass `submit` runs over its slices (#358): the file list keeps credential
|
|
79
|
+
* *stores* out, but a key in an ordinary source file is exactly what a
|
|
80
|
+
* finding points a verifier at, and the tool result is the upload. `redact`
|
|
81
|
+
* mirrors config.yaml's `redact_secrets`. A pattern the model greps for can
|
|
82
|
+
* still tell it that a line matched; the line it sees is redacted.
|
|
64
83
|
*/
|
|
65
|
-
export declare function createRepoReader(cwd: string
|
|
84
|
+
export declare function createRepoReader(cwd: string, opts?: {
|
|
85
|
+
redact?: boolean;
|
|
86
|
+
}): Promise<RepoReader>;
|
|
66
87
|
/**
|
|
67
88
|
* The rubric. The order is deliberate — it is the order a reviewer settles a
|
|
68
89
|
* claim in — and `not-a-defect` is the verdict that separates "the code does
|