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.
Files changed (55) hide show
  1. package/.codecarto/broadside/SKILL.md +20 -1
  2. package/.codecarto/workflow/scaffold-version.yaml +1 -1
  3. package/README.md +5 -4
  4. package/agent-skill/codecartographer/references/broadside.md +5 -1
  5. package/dist/core/broadside/client.d.ts +56 -0
  6. package/dist/core/broadside/client.js +200 -0
  7. package/dist/core/broadside/collect.d.ts +68 -0
  8. package/dist/core/broadside/collect.js +676 -0
  9. package/dist/core/broadside/constants.d.ts +51 -0
  10. package/dist/core/broadside/constants.js +74 -0
  11. package/dist/core/broadside/lenses.d.ts +31 -0
  12. package/dist/core/broadside/lenses.js +312 -0
  13. package/dist/core/broadside/models.d.ts +46 -0
  14. package/dist/core/broadside/models.js +321 -0
  15. package/dist/core/broadside/render.d.ts +20 -0
  16. package/dist/core/broadside/render.js +285 -0
  17. package/dist/core/broadside/repo.d.ts +58 -0
  18. package/dist/core/broadside/repo.js +592 -0
  19. package/dist/core/broadside/requests.d.ts +23 -0
  20. package/dist/core/broadside/requests.js +71 -0
  21. package/dist/core/broadside/results.d.ts +36 -0
  22. package/dist/core/broadside/results.js +163 -0
  23. package/dist/core/broadside/schemas.d.ts +2 -0
  24. package/dist/core/broadside/schemas.js +342 -0
  25. package/dist/core/broadside/state.d.ts +99 -0
  26. package/dist/core/broadside/state.js +384 -0
  27. package/dist/core/broadside/submit.d.ts +30 -0
  28. package/dist/core/broadside/submit.js +350 -0
  29. package/dist/core/broadside/types.d.ts +491 -0
  30. package/dist/core/broadside/types.js +107 -0
  31. package/dist/core/{broadside-verify.d.ts → broadside/verify.d.ts} +23 -2
  32. package/dist/core/{broadside-verify.js → broadside/verify.js} +43 -5
  33. package/dist/core/broadside.d.ts +14 -890
  34. package/dist/core/broadside.js +25 -3564
  35. package/dist/core/completion.js +91 -72
  36. package/dist/core/dashboard-writer.js +9 -1
  37. package/dist/core/index.d.ts +0 -1
  38. package/dist/core/index.js +0 -1
  39. package/dist/core/library.d.ts +24 -1
  40. package/dist/core/library.js +46 -15
  41. package/dist/core/orchestrator-config.js +22 -8
  42. package/dist/core/status.d.ts +42 -23
  43. package/dist/core/status.js +163 -137
  44. package/dist/core/workspace.d.ts +2 -0
  45. package/dist/core/workspace.js +49 -25
  46. package/dist/core/yaml.js +9 -3
  47. package/dist/extensions/codecarto/auto-runner.d.ts +7 -0
  48. package/dist/extensions/codecarto/auto-runner.js +54 -23
  49. package/dist/extensions/codecarto/broadside-flags.d.ts +3 -1
  50. package/dist/extensions/codecarto/broadside-flags.js +13 -0
  51. package/dist/extensions/codecarto/index.js +13 -7
  52. package/dist/extensions/codecarto/phase-compaction.js +6 -2
  53. package/dist/mcp-server/server.d.ts +1 -0
  54. package/dist/mcp-server/server.js +28 -5
  55. package/package.json +1 -1
@@ -1,890 +1,14 @@
1
- export declare const BROADSIDE_MODEL = "google/gemini-3.7-flash:batch";
2
- export declare const BROADSIDE_BATCH_URL = "https://openrouter.ai/api/beta/batches";
3
- export declare const BROADSIDE_DIR = "broadside";
4
- /** Name Broad-Side answers to on the skill surfaces. Not a post-pipeline skill — see readBroadsideSkill. */
5
- export declare const BROADSIDE_SKILL_NAME = "broadside";
6
- export declare const BROADSIDE_STATE_FILE = "state.json";
7
- export declare const BROADSIDE_CONFIG_FILE = "config.yaml";
8
- export declare const BROADSIDE_STATE_SCHEMA_VERSION = 1;
9
- export declare const BROADSIDE_INPUT_PRICE_PER_M = 0.375;
10
- export declare const BROADSIDE_OUTPUT_PRICE_PER_M = 1.875;
11
- export declare const BROADSIDE_MODELS_URL = "https://openrouter.ai/api/v1/models";
12
- export declare const BROADSIDE_BENCHMARKS_URL = "https://openrouter.ai/api/v1/benchmarks";
13
- export declare const BROADSIDE_CATALOG_CACHE_FILE = "model-catalog.json";
14
- /**
15
- * What this repository's own submits learned about batch endpoints: which
16
- * `:batch` ids OpenRouter accepted a job for and which it refused with
17
- * "does not have a :batch endpoint". The catalog cannot tell the two apart
18
- * (#141), so the `models` action annotates its rows from this file.
19
- */
20
- export declare const BROADSIDE_ENDPOINTS_FILE = "batch-endpoints.json";
21
- export declare const BROADSIDE_CATALOG_CACHE_TTL_MS: number;
22
- export declare const BROADSIDE_LENS_IDS: readonly ["architecture", "api", "security", "defect", "conventions", "porting"];
23
- export type BroadsideLensId = (typeof BROADSIDE_LENS_IDS)[number];
24
- export declare const BROADSIDE_POLL_INTERVAL_MS = 15000;
25
- export declare const BROADSIDE_DEFAULT_POLL_BUDGET_MS: number;
26
- /**
27
- * The run expense limit in USD a repository gets before it configures one.
28
- * Pi asks a human before submitting over the estimate; the MCP surface cannot,
29
- * and shipped with no limit at all, so a host calling submit with the stock
30
- * config spent whatever the estimate came to (#231). One dollar covers a
31
- * six-lens run of a repository this size with room to spare; a larger one
32
- * raises `max_cost` in config.yaml, passes `max_cost` on the call, or sets it
33
- * to 0 for no limit.
34
- */
35
- export declare const BROADSIDE_DEFAULT_MAX_COST = 1;
36
- export type ModelPricing = {
37
- /** USD per million input tokens. */
38
- inputPerM: number;
39
- /** USD per million output tokens. */
40
- outputPerM: number;
41
- /** Where the numbers came from — affects what the submit text claims. */
42
- source: "built-in" | "config" | "live" | "cache";
43
- };
44
- /** The subset of the OpenRouter model catalog Broad-Side actually uses. */
45
- export type CatalogEntry = {
46
- id: string;
47
- name: string;
48
- inputPerM: number;
49
- outputPerM: number;
50
- cachedInputPerM?: number;
51
- contextLength?: number;
52
- maxCompletionTokens?: number;
53
- /** Empty array means unknown, not "supports nothing". */
54
- supportedParameters: string[];
55
- expirationDate?: string | null;
56
- };
57
- export type CodingBenchmarks = {
58
- /** Base model slug (batch suffix stripped) → indices. */
59
- byBaseSlug: Record<string, {
60
- codingIndex?: number;
61
- intelligenceIndex?: number;
62
- }>;
63
- /** Citation/attribution metadata from the benchmarks endpoint. */
64
- meta: Record<string, unknown>;
65
- };
66
- export type BroadsideCatalogResult = {
67
- model: string;
68
- source: "built-in" | "config" | "live" | "cache";
69
- /**
70
- * Always resolved. `resolveCatalogEntry` either returns an entry — from
71
- * config, cache, the live catalog, or the compile-time fallback — or throws
72
- * naming the model it could not price. This was declared nullable, which is
73
- * the only reason the single consumer needed a non-null assertion to read it.
74
- */
75
- entry: CatalogEntry;
76
- benchmarks?: CodingBenchmarks;
77
- };
78
- export type JsonSchemaDef = {
79
- name: string;
80
- strict: boolean;
81
- schema: Record<string, unknown>;
82
- };
83
- /**
84
- * Where the file list and the file contents both came from — one source, so
85
- * a run's results correspond to one state of the repository (#248).
86
- * `working-tree`: git's view of the checkout (tracked plus untracked files,
87
- * ignore rules applied, files deleted on disk left out); `walk`: a bounded
88
- * directory walk, for a target that is not a git repository.
89
- */
90
- export type RepoSnapshotSource = "working-tree" | "walk";
91
- export type RepoInfo = {
92
- name: string;
93
- path: string;
94
- language: string;
95
- manifest: {
96
- path: string;
97
- content: string;
98
- } | null;
99
- mainFile: string;
100
- readmeFirst: string;
101
- fileTree: string;
102
- fileCounts: Record<string, number>;
103
- sourceGlob: string;
104
- sourceExts: string[];
105
- /** How many slurpable files carry one of `sourceExts`; zero means no lens has code to scan. */
106
- sourceFileCount: number;
107
- snapshot: RepoSnapshotSource;
108
- /** Files left out of every lens because their name says they hold secrets (#252). */
109
- secretFilesSkipped: string[];
110
- /** Secret-like values redacted from the entry point, manifest, and README excerpt. */
111
- redactedValues: number;
112
- };
113
- export type FileSlice = {
114
- moduleName: string;
115
- content: string;
116
- fileCount: number;
117
- chars: number;
118
- /** Repo-relative paths of the files folded into this slice. */
119
- files: string[];
120
- /** Secret-like values redacted from this slice's files before upload (#252). */
121
- redactedValues?: number;
122
- /** The files in this slice that had at least one value redacted. */
123
- redactedFiles?: string[];
124
- /**
125
- * Set when the lens's targeted globs matched nothing and the slice was
126
- * built from its fallback globs instead (#319). The estimate, the batch
127
- * entry, and the prompt all say so.
128
- */
129
- fallback?: string;
130
- };
131
- /**
132
- * OpenRouter's unified `reasoning` control, as sent on a lens request.
133
- *
134
- * Left unsent, each model applies its own default — which is how a
135
- * reasoning-capable model came to spend 5,758 of a 6,000-token output budget
136
- * thinking, leaving ~230 tokens for JSON that then truncated mid-structure. The
137
- * thinking is billed at the full *output* rate, so the run paid for roughly
138
- * 6,000 output tokens per slice to receive 230 usable ones.
139
- */
140
- export type BroadsideReasoning = {
141
- enabled?: boolean;
142
- effort?: "minimal" | "low" | "medium" | "high";
143
- max_tokens?: number;
144
- };
145
- /**
146
- * The reasoning control every lens request carries: low effort.
147
- *
148
- * It used to be a token cap — `max_tokens` at a quarter of the lens's output
149
- * budget, so three quarters stayed for the answer. Measured live on
150
- * `google/gemini-3.8-flash:batch` (0.22.0 verification, defect lens, cap
151
- * 5,800 of a 6,000 budget): the model reasoned 5,218 tokens on the first
152
- * pass and **11,518 under the same cap** on the doubled-budget retry —
153
- * thinking scaled with `max_tokens` and the cap changed nothing, both
154
- * results truncated, and the retry cost twice the original for no JSON.
155
- * The same lens with `effort: "low"` reasoned 0 tokens, finished with
156
- * `stop`, returned valid JSON, and cost a twelfth as much. Gemini 3.x
157
- * models take a thinking *level*, not a budget, and OpenRouter forwards a
158
- * `max_tokens` cap to them as nothing at all; `effort` is what it can
159
- * translate for every provider (a level where the provider has levels, a
160
- * fraction of the budget where it takes a budget). So the default asks for
161
- * little thinking in the one vocabulary that reaches everyone.
162
- *
163
- * Deliberately not `enabled: false`: `google/gemini-3.8-flash:batch` refuses
164
- * the whole batch with *"Reasoning is mandatory for this endpoint and cannot
165
- * be disabled"*, turning a partial result into none at all. Low effort works
166
- * whether or not a provider allows reasoning to be switched off.
167
- */
168
- export declare const BROADSIDE_DEFAULT_REASONING: Readonly<BroadsideReasoning>;
169
- /** The reasoning control a lens request carries when config.yaml sets none. */
170
- export declare function defaultReasoningFor(): BroadsideReasoning;
171
- /**
172
- * The reasoning control a truncated slice is re-submitted with.
173
- *
174
- * A truncation on a reasoning-capable model is usually thinking that ate the
175
- * answer's budget, and doubling `max_tokens` doubles the thinking where the
176
- * provider ignores a token cap (see {@link BROADSIDE_DEFAULT_REASONING}). The
177
- * retry therefore asks for low effort as well, replacing a `max_tokens` cap
178
- * (OpenRouter refuses a request carrying both) and lowering a higher effort.
179
- * An explicit `enabled: false` and an effort already at or below low are left
180
- * as they are.
181
- */
182
- export declare function retryReasoningFor(original: BroadsideReasoning | undefined): BroadsideReasoning;
183
- export type BatchRequest = {
184
- custom_id: string;
185
- body: {
186
- model: string;
187
- messages: {
188
- role: "system" | "user";
189
- content: string;
190
- }[];
191
- response_format: {
192
- type: "json_schema";
193
- json_schema: JsonSchemaDef;
194
- };
195
- max_tokens: number;
196
- reasoning?: BroadsideReasoning;
197
- };
198
- };
199
- export type BatchTerminalStatus = "completed" | "failed" | "expired" | "cancelled";
200
- export type BroadsideBatchEntry = {
201
- batchId: string;
202
- requests: number;
203
- status: string;
204
- submittedAt: string;
205
- completedAt?: string;
206
- estimatedCost: number;
207
- cost?: number;
208
- resultCount?: number;
209
- error?: unknown;
210
- /** Why a `skipped` lens had nothing to submit: the globs that matched no file. */
211
- reason?: string;
212
- /**
213
- * Set when the lens scanned its fallback scope because its targeted globs
214
- * matched nothing (#319): "no files matched …; scanned all javascript
215
- * sources instead". Absent for a targeted scan.
216
- */
217
- fallback?: string;
218
- /** Set when this lens used a model other than the run default. */
219
- model?: string;
220
- /** The completion ceiling of this lens's model; bounds the truncation retry. */
221
- outputCap?: number;
222
- };
223
- export type BroadsideSynthesisEntry = {
224
- batchId?: string;
225
- status: "pending" | "submitted" | "completed" | "failed";
226
- cost?: number;
227
- /** Why the pass was retired, when the batch reported one. */
228
- error?: string;
229
- };
230
- /** One triage item — a scouting lead turned into a work-order entry. */
231
- export type TriageItem = {
232
- title: string;
233
- severity: string;
234
- module: string;
235
- impact: "high" | "medium" | "low";
236
- difficulty: "high" | "medium" | "low";
237
- priority: string;
238
- effort_estimate: string;
239
- rationale: string;
240
- };
241
- export type BroadsideTriageEntry = {
242
- batchId?: string;
243
- status: "pending" | "submitted" | "completed" | "failed";
244
- cost?: number;
245
- error?: string;
246
- };
247
- /** Recorded on the run once a verification pass has run (#143); see core/broadside-verify.ts. */
248
- export type BroadsideVerifyEntry = {
249
- /** `completed`: every selected finding got a verdict; `partial`: the cost cap or an abort stopped it early. */
250
- status: "completed" | "partial";
251
- model: string;
252
- top: number;
253
- verified: number;
254
- confirmed: number;
255
- cost: number;
256
- at: string;
257
- };
258
- /** The truncation retry pass of one run: one batch per model (#206). */
259
- export type BroadsideRetryEntry = {
260
- status: "submitted" | "completed" | "failed";
261
- batches: Array<{
262
- model: string;
263
- batchId: string;
264
- }>;
265
- /** When the owning collect claimed the pass (#322). */
266
- claimedAt: string;
267
- };
268
- /**
269
- * The parts of a run that cost money to submit and that exactly one collect
270
- * may own: the two post-passes and the truncation retry (#322).
271
- */
272
- export type BroadsideRunSlot = "synthesis" | "triage" | "retry";
273
- export declare const BROADSIDE_RUN_SLOTS: readonly BroadsideRunSlot[];
274
- export type BroadsideRun = {
275
- id: string;
276
- createdAt: string;
277
- model: string;
278
- lenses: BroadsideLensId[];
279
- status: "in-flight" | "completed" | "partial" | "failed";
280
- outputDir: string;
281
- batches: Partial<Record<BroadsideLensId, BroadsideBatchEntry>>;
282
- synthesis: BroadsideSynthesisEntry;
283
- triage: BroadsideTriageEntry;
284
- /**
285
- * The truncation retry pass (#133), recorded so that two collects on one
286
- * run cannot both submit it (#322). Absent until a collect claims it.
287
- */
288
- retry?: BroadsideRetryEntry;
289
- /** The verification pass over the top findings, when one has run (#143). */
290
- verify?: BroadsideVerifyEntry;
291
- totalCost?: number;
292
- pricing?: ModelPricing;
293
- maxCost?: number;
294
- /** The model's completion ceiling, recorded so collect can cap retries. */
295
- outputCap?: number;
296
- /** Git HEAD at submit time, for incremental re-scouting (#142). */
297
- sourceHead?: string | null;
298
- /** Whether the working tree was dirty at submit time. */
299
- sourceDirty?: boolean;
300
- /** When incremental, the previous run's HEAD this run diffs against. */
301
- baseHead?: string | null;
302
- /** Where the scanned files and their contents were read from (#248). */
303
- snapshot?: RepoSnapshotSource;
304
- /** The language the lenses scanned as. */
305
- language?: string;
306
- /** What the secret-redaction pass did before upload (#252); absent on runs from before it. */
307
- redaction?: {
308
- enabled: boolean;
309
- values: number;
310
- files: number;
311
- skippedFiles: number;
312
- };
313
- };
314
- export type BroadsideStateFile = {
315
- schema_version: number;
316
- runs: BroadsideRun[];
317
- };
318
- export type BroadsideConfig = {
319
- model: string;
320
- apiKey: string;
321
- defaultLenses: BroadsideLensId[];
322
- /** Approximate run expense limit in USD; 0 means no limit. */
323
- maxCost: number;
324
- /** Manual pricing overrides (USD per million). Live lookup is preferred. */
325
- pricing: {
326
- inputPerM: number;
327
- outputPerM: number;
328
- } | null;
329
- /**
330
- * Per-lens model overrides. A lens absent here uses `model`. This is how a
331
- * repository routes the semantic lenses (security, defect) to a stronger
332
- * batch model while the cheap default carries the rest — the whole point of
333
- * the cheap model is telling the expensive one where to look, and that
334
- * trade-off is not the same for every lens.
335
- */
336
- lensModels: Partial<Record<BroadsideLensId, string>>;
337
- /** Overrides every lens's reasoning setting when present. */
338
- reasoning: BroadsideReasoning | null;
339
- /**
340
- * Repo defaults for the per-call run knobs. Each mirrors a tool parameter
341
- * of the same name; an explicit parameter always wins. They live here so a
342
- * repository can fix its own scouting policy once instead of restating it
343
- * on every submit and collect.
344
- */
345
- incremental: boolean;
346
- retryTruncated: boolean;
347
- includeSynthesis: boolean;
348
- includeTriage: boolean;
349
- /** Default poll budget in seconds; 0 means "return immediately". */
350
- waitSeconds: number;
351
- /**
352
- * Replace secret-like values with `[REDACTED:<kind>]` and skip files named
353
- * like credential stores before anything is uploaded (#252). On by default;
354
- * off only for a repository whose maintainers have decided its contents may
355
- * leave as they are.
356
- */
357
- redactSecrets: boolean;
358
- };
359
- /**
360
- * The pre-flight facts a caller needs to decide whether a run is worth its
361
- * price: what each lens would cost, at what rates, against which limit. Handed
362
- * to {@link BroadsideSubmitOptions.confirm} before anything is submitted.
363
- */
364
- export type BroadsideEstimate = {
365
- model: string;
366
- pricing: ModelPricing;
367
- lenses: Array<{
368
- lensId: BroadsideLensId;
369
- name: string;
370
- slices: number;
371
- maxTokens: number;
372
- cost: number;
373
- /** The model this lens would use — `model` unless a per-lens override applies. */
374
- model: string;
375
- pricing: ModelPricing;
376
- /** Set when this lens is priced on its fallback scope (#319); see BroadsideBatchEntry.fallback. */
377
- fallback?: string;
378
- }>;
379
- /** True when at least one lens uses a model other than the run default. */
380
- mixedModels: boolean;
381
- totalCost: number;
382
- inputTokens: number;
383
- outputTokens: number;
384
- /** Run expense limit in USD; 0 means no limit. */
385
- maxCost: number;
386
- /** True when totalCost is over a non-zero maxCost. */
387
- exceedsLimit: boolean;
388
- /** Set when incremental scouting found a baseline to diff against. */
389
- baseHead: string | null;
390
- sourceDirty: boolean;
391
- /** Whether a requested incremental run actually narrowed this estimate. */
392
- incremental: BroadsideIncrementalOutcome;
393
- /** The provider's completion ceiling, when the catalog advertises one. */
394
- outputCap?: number;
395
- };
396
- /**
397
- * OpenRouter rejected the API key (HTTP 401/403). Thrown from the catalog
398
- * lookup rather than swallowed into "could not price" or a silent built-in
399
- * fallback: a run that cannot authenticate cannot submit either, and the
400
- * message that reaches the user has to say so (#251).
401
- */
402
- export declare class BroadsideAuthError extends Error {
403
- readonly httpStatus: number;
404
- readonly detail: string;
405
- constructor(httpStatus: number, detail: string);
406
- }
407
- /**
408
- * `broadside/config.yaml` exists but cannot be used. A file that failed to
409
- * parse used to be treated exactly like an absent one — defaults, including
410
- * no spend cap and no lens routing, with no message — so a typo removed the
411
- * user's own guard (#232). Only an absent file yields defaults now.
412
- */
413
- export declare class BroadsideConfigError extends Error {
414
- readonly path: string;
415
- constructor(path: string, detail: string);
416
- }
417
- /**
418
- * `broadside/state.json` exists but cannot be read. It used to be read as
419
- * empty and the next checkpoint wrote that empty state over it, losing the
420
- * batch ids of every in-flight, already-paid run (#233). The corrupt file is
421
- * preserved beside itself and nothing writes over it until someone looks.
422
- */
423
- export declare class BroadsideStateError extends Error {
424
- readonly path: string;
425
- readonly backupPath: string;
426
- constructor(path: string, backupPath: string, detail: string);
427
- }
428
- /** Thrown when a confirm hook declines a run. Nothing was submitted. */
429
- export declare class BroadsideCancelledError extends Error {
430
- constructor(message?: string);
431
- }
432
- /**
433
- * Whether incremental scouting actually narrowed the run.
434
- *
435
- * A request for incremental falls back to a full scan whenever there is nothing
436
- * to diff against, and that fallback costs real money — the caller asked for the
437
- * cheap mode and gets the expensive one. It must be reported, not inferred from
438
- * the request counts.
439
- */
440
- export type BroadsideIncrementalOutcome = {
441
- requested: boolean;
442
- applied: boolean;
443
- /** The commit the run diffed against, when one was found. */
444
- baseHead: string | null;
445
- /** Why a requested incremental run did not apply. */
446
- reason?: "dirty-worktree" | "no-baseline" | "diff-failed";
447
- };
448
- export type BroadsideSubmitResult = {
449
- runId: string;
450
- outputDir: string;
451
- batches: Partial<Record<BroadsideLensId, BroadsideBatchEntry>>;
452
- estimatedTotalCost: number;
453
- estimatedInputTokens: number;
454
- estimatedOutputTokens: number;
455
- pricing: ModelPricing;
456
- maxCost?: number;
457
- modelInfo: {
458
- contextLength?: number;
459
- maxCompletionTokens?: number;
460
- supportsStructuredOutputs?: boolean;
461
- expirationDate?: string | null;
462
- };
463
- incremental: BroadsideIncrementalOutcome;
464
- /** What was scanned: the language the lenses ran as and the snapshot the files came from. */
465
- repo: {
466
- language: string;
467
- sourceFiles: number;
468
- snapshot: RepoSnapshotSource;
469
- sourceHead: string | null;
470
- sourceDirty: boolean;
471
- };
472
- /** What the secret-redaction pass did before upload (#252). */
473
- redaction: {
474
- enabled: boolean;
475
- values: number;
476
- files: number;
477
- skippedFiles: string[];
478
- };
479
- };
480
- export type BroadsideCollectResult = {
481
- runId: string;
482
- status: string;
483
- totalCost: number;
484
- resultCount: number;
485
- /** Results whose JSON did not parse even after fence stripping —
486
- * the signature of an output cut off at max_tokens. */
487
- truncatedCount: number;
488
- /** Truncated slices recovered by the automatic re-submit pass (#133). */
489
- retriedCount: number;
490
- /** Another collect on this run owns the retry pass; its result lands on a later collect (#322). */
491
- retryElsewhere?: boolean;
492
- lensOutcomes: Partial<Record<BroadsideLensId, {
493
- status: string;
494
- cost?: number;
495
- resultCount?: number;
496
- truncated?: number;
497
- error?: string;
498
- }>>;
499
- synthesis: BroadsideSynthesisEntry;
500
- triage: BroadsideTriageEntry;
501
- topFindings: {
502
- title: string;
503
- severity: string;
504
- sourceLens: string;
505
- summary: string;
506
- }[];
507
- topTriageItems: TriageItem[];
508
- };
509
- type LensDefinition = {
510
- id: BroadsideLensId;
511
- name: string;
512
- description: string;
513
- schemaName: string;
514
- sliceBy: "none" | "directory" | "auto";
515
- maxChars: number;
516
- maxTokens: number;
517
- reasoning?: BroadsideReasoning;
518
- skipTestFiles?: boolean;
519
- globsFor: (info: RepoInfo) => string[];
520
- /**
521
- * Where to look when `globsFor` matches no source file (#319). The
522
- * security and api lenses target server/, auth, and middleware paths
523
- * because that is where the trust boundary usually lives; a service whose
524
- * server is `src/server.js` matched none of them and got no security
525
- * review at all. A match that is only documents is the same starvation:
526
- * `SECURITY.md` satisfied the security lens on CodeCartographer itself,
527
- * which then reviewed a policy and reported zero findings. The fallback
528
- * is the language's whole source set, added to whatever did match —
529
- * priced as such, and said so in the estimate, the run record, and the
530
- * prompt.
531
- */
532
- fallbackGlobsFor?: (info: RepoInfo) => string[];
533
- systemPrompt: (info: RepoInfo) => string;
534
- userPrompt: (info: RepoInfo, source: string, moduleName: string) => string;
535
- };
536
- export declare function getLens(lensId: BroadsideLensId): LensDefinition;
537
- export declare function listLenses(): LensDefinition[];
538
- /** The languages Broad-Side can scan; anything else is refused at submit. */
539
- export declare const BROADSIDE_LANGUAGES: readonly ["go", "python", "rust", "typescript", "javascript"];
540
- /**
541
- * The files a run scans, and where they came from. Contents are always read
542
- * from the working tree, so the list is the working tree's too: tracked files
543
- * plus untracked ones git does not ignore, minus files deleted on disk. The
544
- * list used to come from `git ls-tree HEAD`, so a run mixed the committed
545
- * file list with uncommitted contents and never saw an untracked file (#248).
546
- * A target that is not a git repository gets a bounded walk.
547
- */
548
- export declare function listRepoFiles(targetDir: string): Promise<{
549
- files: string[];
550
- snapshot: RepoSnapshotSource;
551
- }>;
552
- export declare function collectRepoInfo(targetDir: string, opts?: {
553
- redact?: boolean;
554
- }): Promise<RepoInfo>;
555
- export declare function isSlurpable(relPath: string): boolean;
556
- type CollectedFile = {
557
- relPath: string;
558
- moduleName: string;
559
- };
560
- /**
561
- * The files a lens will read: its targeted globs, or — when those match no
562
- * source file and the lens declares a fallback — the fallback globs on top
563
- * of whatever did match, with a sentence saying so (#319). The sentence
564
- * travels to the estimate, the batch entry, and the prompt, so a fallback
565
- * scan is never a silent one.
566
- *
567
- * "No source file" rather than "no file": a policy document or a config
568
- * file under a targeted path satisfies the globs and leaves the lens with
569
- * nothing to review, and the coverage note it writes back is the only sign.
570
- */
571
- export declare function selectLensFiles(allFiles: string[], lens: LensDefinition, info: RepoInfo): {
572
- files: CollectedFile[];
573
- fallback?: string;
574
- };
575
- export declare function gatherSlices(targetDir: string, lens: LensDefinition, info: RepoInfo, opts?: {
576
- redact?: boolean;
577
- }): Promise<FileSlice[]>;
578
- export declare function buildBatchRequest(lens: LensDefinition, info: RepoInfo, slice: FileSlice, index: number, sliceCount: number, model?: string, maxTokensOverride?: number, reasoningOverride?: BroadsideReasoning): BatchRequest;
579
- /**
580
- * Pre-flight cost estimate for one lens.
581
- *
582
- * Every slice is its own batch request, so both halves scale with the slice
583
- * count. The output half used to be a single `maxTokens * 0.75` for the whole
584
- * lens no matter how many requests it sent — on a repository that sliced into
585
- * 13 modules that budgeted one request's output and shipped thirteen, and a
586
- * live run came in at roughly 3x its estimate. Since this number is what
587
- * `max_cost` binds against, under-counting it lets a run outspend the cap the
588
- * user set.
589
- *
590
- * @param info - Repo info, when the caller has it: lets the estimate include
591
- * the system prompt and JSON schema each request carries. Omitted, the
592
- * estimate covers slice content only, which is what the old signature did.
593
- */
594
- export declare function estimateCost(lens: LensDefinition, slices: FileSlice[], pricing: ModelPricing, maxTokensOverride?: number, info?: RepoInfo): {
595
- inputTokens: number;
596
- outputTokens: number;
597
- cost: number;
598
- };
599
- export declare function broadsideDirFor(cwd: string): string;
600
- /**
601
- * Read the Broad-Side reading guide.
602
- *
603
- * It is deliberately not a post-pipeline skill under `.codecarto/skills/`: a
604
- * scout run is read *before* or *during* the interactive pipeline, and the
605
- * post-pipeline machinery gates on a completed run and wraps its prompt in
606
- * post-pipeline framing that would be false here. It is also readable on a
607
- * repository that has scout state and no workspace at all, which is why this
608
- * falls back to the packaged copy.
609
- *
610
- * @param cwd - Absolute path to the target repository.
611
- * @returns the skill text and the path it came from.
612
- * @throws when neither the workspace copy nor the packaged copy exists.
613
- */
614
- export declare function readBroadsideSkill(cwd: string): Promise<{
615
- path: string;
616
- content: string;
617
- }>;
618
- export declare function defaultBroadsideState(): BroadsideStateFile;
619
- export declare function loadBroadsideState(broadsideDir: string): Promise<BroadsideStateFile>;
620
- /**
621
- * Overwrite `state.json` wholesale with `state`.
622
- *
623
- * Prefer {@link persistBroadsideRun} anywhere a live operation is recording its
624
- * own progress — this entry point replaces the file, so any run a concurrent
625
- * process recorded in the meantime is erased. It remains the right call for
626
- * seeding a fresh workspace and for test fixtures, where "make the file exactly
627
- * this" is the intent.
628
- */
629
- export declare function saveBroadsideState(broadsideDir: string, state: BroadsideStateFile): Promise<void>;
630
- /**
631
- * Read-modify-write `state.json` under a lock.
632
- *
633
- * The lock is held only for the read-modify-write, never for the surrounding
634
- * operation: a `collect` can poll for the better part of an hour, and holding
635
- * the lock across that would push every concurrent caller past the 5s lock
636
- * timeout.
637
- */
638
- export declare function updateBroadsideStateAtomically(broadsideDir: string, mutate: (state: BroadsideStateFile) => void | Promise<void>): Promise<BroadsideStateFile>;
639
- /**
640
- * Record one run's current shape, merged into whatever is on disk *now*.
641
- *
642
- * Broad-Side operations are long-lived and hold their state in memory while
643
- * they poll. Writing that snapshot back wholesale silently erased any run a
644
- * concurrent operation had recorded since it was loaded, orphaning that run's
645
- * paid results on disk — present as files, invisible to `list`, and unreachable
646
- * by `collect`, which finds its run by position in `state.runs`. Observed live:
647
- * a submit at 23:35 was erased by a collect that had loaded state before it and
648
- * wrote back at 00:08.
649
- *
650
- * Merging by run id also self-heals: a run erased by an older writer is
651
- * restored the next time its own operation checkpoints.
652
- */
653
- export declare function persistBroadsideRun(broadsideDir: string, run: BroadsideRun): Promise<BroadsideStateFile>;
654
- /**
655
- * Record a collect's view of its run, keeping whatever is further along on
656
- * disk (#322).
657
- *
658
- * Two collects on one run each hold the run in memory and each used to write
659
- * the whole thing back, so the last writer replaced the other's post-pass
660
- * entries with its own — and both had submitted their own post-passes, since
661
- * each decided from the copy it loaded at entry. This writer merges slot by
662
- * slot: a post-pass or retry entry that is further along on disk (claimed
663
- * over pending, submitted over claimed, settled over submitted) wins and is
664
- * copied into `run`, so the caller reports what is true; a lens entry never
665
- * goes backwards from terminal to polling. A tie keeps this collect's copy,
666
- * so the collect that settled a pass records its cost. Submitting is guarded
667
- * separately by {@link claimRunSlot}.
668
- */
669
- export declare function persistBroadsideRunMerging(broadsideDir: string, run: BroadsideRun): Promise<BroadsideStateFile>;
670
- /**
671
- * Claim one spending slot of a run for this collect (#322).
672
- *
673
- * Read-modify-write under the state lock: if the slot on disk is still
674
- * unclaimed (`pending`, or absent for the retry), it is marked `submitted`
675
- * with no batch id *before* any network call and `true` comes back — this
676
- * collect owns it and may submit. Otherwise another collect got there first:
677
- * its entry is copied into `run` and `false` comes back. An adopted entry
678
- * with a batch id can be polled (polling is idempotent); one without an id
679
- * is a claim whose owner has not recorded the id yet, and is reported as in
680
- * flight elsewhere.
681
- */
682
- export declare function claimRunSlot(broadsideDir: string, run: BroadsideRun, slot: BroadsideRunSlot): Promise<boolean>;
683
- export declare function loadBroadsideConfig(broadsideDir: string): Promise<BroadsideConfig>;
684
- /** The shipped defaults: what an absent config.yaml means. */
685
- export declare function defaultBroadsideConfig(): BroadsideConfig;
686
- /** The catalog cache schema this build writes; a file from another is not read. */
687
- export declare const BROADSIDE_CATALOG_CACHE_SCHEMA = 3;
688
- /** One model's most recent submit outcome, as remembered in {@link BROADSIDE_ENDPOINTS_FILE}. */
689
- export type BatchEndpointRecord = {
690
- status: "accepted" | "rejected";
691
- /** ISO timestamp of the submit that produced this record. */
692
- at: string;
693
- /** The provider's refusal, for a rejected endpoint. */
694
- error?: string;
695
- };
696
- export declare function readBatchEndpoints(broadsideDir: string): Promise<Record<string, BatchEndpointRecord>>;
697
- /**
698
- * Remember what a submit learned about each model it posted to. An accepted
699
- * job proves the endpoint exists; a "does not have a :batch endpoint"
700
- * refusal proves it does not. Any other rejection (quota, malformed request,
701
- * auth) says nothing about the endpoint and leaves the record alone.
702
- */
703
- export declare function recordBatchEndpoints(broadsideDir: string, outcomes: Array<{
704
- model: string;
705
- batchId: string;
706
- error?: unknown;
707
- }>): Promise<void>;
708
- export declare function builtInCatalogEntry(model: string): CatalogEntry | null;
709
- export declare function builtInPricing(model: string): ModelPricing | null;
710
- export declare function resolveCatalogEntry(broadsideDir: string, config: BroadsideConfig, model: string, apiKey: string, fetcher?: FetchLike): Promise<BroadsideCatalogResult>;
711
- export declare function resolveModelPricing(broadsideDir: string, config: BroadsideConfig, model: string, apiKey: string, fetcher?: FetchLike): Promise<ModelPricing>;
712
- export declare function fetchCodingBenchmarks(apiKey: string, fetcher?: FetchLike): Promise<CodingBenchmarks | null>;
713
- export declare function listBatchModels(broadsideDir: string, config: BroadsideConfig, apiKey: string, opts?: {
714
- includeBenchmarks?: boolean;
715
- fetcher?: FetchLike;
716
- }): Promise<{
717
- entries: CatalogEntry[];
718
- source: string;
719
- benchmarks: CodingBenchmarks | null;
720
- defaultModel: string;
721
- /** This repository's remembered submit outcomes per model, from {@link BROADSIDE_ENDPOINTS_FILE}. */
722
- endpoints: Record<string, BatchEndpointRecord>;
723
- }>;
724
- export type FetchLike = (url: string, init: Record<string, unknown>) => Promise<Response>;
725
- export declare function submitBatch(batchRequests: BatchRequest[], apiKey: string, fetcher?: FetchLike, model?: string): Promise<{
726
- batchId: string;
727
- status: string;
728
- error?: unknown;
729
- }>;
730
- export declare function fetchBatch(batchId: string, apiKey: string, fetcher?: FetchLike): Promise<Record<string, unknown>>;
731
- /**
732
- * Batch statuses that will never produce a result.
733
- *
734
- * Deliberately excludes the synthetic `timeout` this module returns when a poll
735
- * budget expires: that batch is still running server-side and has already been
736
- * charged, so callers must come back for it rather than retire it.
737
- */
738
- export declare const BROADSIDE_DEAD_BATCH_STATUSES: string[];
739
- /**
740
- * Batch entry statuses collect never polls again: the dead ones above, plus
741
- * `completed`, plus the two a submit assigns without a batch (`skipped`: no
742
- * matching files; `rejected`: the provider refused it). The 0.19.1 changelog
743
- * called the dead set "a named constant rather than two hand-maintained
744
- * lists"; this set was still three literal copies (self-audit sem 5.8).
745
- */
746
- export declare const BROADSIDE_TERMINAL_ENTRY_STATUSES: string[];
747
- export declare function pollBatchUntilTerminal(batchId: string, apiKey: string, opts?: {
748
- deadlineMs?: number;
749
- onStatus?: (status: string, counts: Record<string, unknown>) => void;
750
- fetcher?: FetchLike;
751
- pollIntervalMs?: number;
752
- /**
753
- * Stops polling early with the same synthetic `timeout` a spent budget
754
- * returns: the batch keeps running server-side and a later collect
755
- * claims it. The MCP server aborts when its client disconnects (#322).
756
- */
757
- signal?: AbortSignal;
758
- }): Promise<Record<string, unknown>>;
759
- /**
760
- * Poll several batch ids in parallel against one shared deadline. Collect
761
- * previously polled one lens at a time, so a slow first lens serialized the
762
- * wall clock for lenses that had already finished server-side (#136). The
763
- * onStatus callback identifies the lens so progress output stays readable
764
- * even while the polls interleave.
765
- */
766
- export declare function pollBatchesConcurrently(entries: Array<{
767
- lensId: BroadsideLensId;
768
- batchId: string;
769
- }>, apiKey: string, opts?: {
770
- deadlineMs?: number;
771
- fetcher?: FetchLike;
772
- pollIntervalMs?: number;
773
- signal?: AbortSignal;
774
- onStatus?: (lensId: string, status: string, counts: Record<string, unknown>) => void;
775
- }): Promise<Map<string, Record<string, unknown>>>;
776
- export declare function runBroadsideSubmit(cwd: string, apiKey: string, opts?: {
777
- lenses?: BroadsideLensId[];
778
- fetcher?: FetchLike;
779
- model?: string;
780
- /**
781
- * Per-lens model overrides for this run, layered over config.yaml's
782
- * `lens_models`: a lens named here runs on this model, a lens named only
783
- * in the file runs on the file's, and the rest run on `model` (#141).
784
- */
785
- lensModels?: Partial<Record<BroadsideLensId, string>>;
786
- /** Approximate run expense limit in USD; 0 means no limit. */
787
- maxCost?: number;
788
- /** Submit even when the estimate exceeds maxCost. */
789
- force?: boolean;
790
- /** Diff against the previous run's HEAD and scan only changed modules (#142). */
791
- incremental?: boolean;
792
- /**
793
- * Called with the pre-flight estimate after slicing and before any state
794
- * write or submission. Returning false throws {@link BroadsideCancelledError}
795
- * and nothing is submitted; returning true proceeds even past maxCost,
796
- * because an interactive approval of a priced run *is* the force flag.
797
- *
798
- * A surface that cannot ask a human (MCP) omits this and keeps the
799
- * refuse-unless-force behavior.
800
- */
801
- confirm?: (estimate: BroadsideEstimate) => boolean | Promise<boolean>;
802
- }): Promise<BroadsideSubmitResult>;
803
- export type StoredLensResult = {
804
- lensId: BroadsideLensId;
805
- customId: string;
806
- moduleName: string;
807
- content: string;
808
- raw: Record<string, unknown>;
809
- /** True when the content is not parseable JSON even after fence stripping —
810
- * the telltale of an output cut off at max_tokens. */
811
- truncated: boolean;
812
- };
813
- /**
814
- * Parse lens content as JSON, tolerating the markdown code fences some models
815
- * wrap structured output in (the same tolerance OpenRouter's headless-agent
816
- * scaffold ships for --output-schema). Returns null when the content is not
817
- * JSON at all — which for a strict json_schema request means the output was
818
- * truncated at max_tokens, not that the model chose prose.
819
- */
820
- export declare function parseLensJson(content: string): unknown | null;
821
- export declare function saveLensResults(runDir: string, lensId: BroadsideLensId, batch: Record<string, unknown>): Promise<StoredLensResult[]>;
822
- /**
823
- * Rebuild lens results from what a previous collect already wrote to disk.
824
- *
825
- * The post-passes are gated on having lens findings in hand, and a collect
826
- * only holds the ones *it* polled. When an earlier collect saved every lens
827
- * and then died before synthesis and triage ran — the batch window is long
828
- * and a poll can easily be interrupted — the next collect finds every lens
829
- * already terminal, skips them all, and would otherwise reach the post-pass
830
- * gate with nothing to hand it. Reading the saved results back is what makes
831
- * "a resumed collect can finish whichever is still pending" true.
832
- */
833
- export declare function loadSavedLensResults(runDir: string, lenses: BroadsideLensId[]): Promise<StoredLensResult[]>;
834
- export declare function runBroadsideCollect(cwd: string, apiKey: string, opts?: {
835
- waitMs?: number;
836
- includeSynthesis?: boolean;
837
- includeTriage?: boolean;
838
- /** Re-submit truncated slices once with a doubled output cap (#133). */
839
- retryTruncated?: boolean;
840
- onStatus?: (lensId: string, status: string, counts: Record<string, unknown>) => void;
841
- fetcher?: FetchLike;
842
- /**
843
- * Which run to collect. Absent, the most recent — which used to be the
844
- * only choice, so an older run still in flight could not be collected
845
- * once a newer submit existed (#268). `status` lists the ids.
846
- */
847
- runId?: string;
848
- /**
849
- * Stops polling and submits nothing further once fired; what was
850
- * already submitted keeps running server-side for a later collect to
851
- * claim. The MCP server fires it when its client disconnects (#322).
852
- */
853
- signal?: AbortSignal;
854
- /** Poll cadence override; tests drive the loop faster than 15 s. */
855
- pollIntervalMs?: number;
856
- }): Promise<BroadsideCollectResult>;
857
- export declare function runBroadsideStatus(cwd: string): Promise<{
858
- state: BroadsideStateFile;
859
- }>;
860
- export declare function renderFindingsMarkdown(content: string): string;
861
- export declare function describeIncrementalFallback(reason: BroadsideIncrementalOutcome["reason"]): string;
862
- export declare function estimateSubmitText(result: BroadsideSubmitResult, lenses: LensDefinition[]): string;
863
- export declare function modelsText(entries: CatalogEntry[], opts: {
864
- benchmarks: CodingBenchmarks | null;
865
- defaultModel: string;
866
- endpoints?: Record<string, BatchEndpointRecord>;
867
- }): string;
868
- /**
869
- * A provider refusal plus what to do about it, for the two refusals a batch
870
- * run meets in practice and cannot fix by itself (#141):
871
- *
872
- * - `Model '<id>' does not have a :batch endpoint.` — the catalog advertises a
873
- * `:batch` id that OpenRouter runs no batch endpoint for. Nothing in the
874
- * catalog distinguishes these; the `models` action marks ids this
875
- * repository has seen refused.
876
- * - `job-submission-count … in use: 16, quota: 16` — the per-account limit
877
- * on concurrent batch jobs. Broad-Side submits one job per lens, so a few
878
- * runs in flight on the same key fill it; the refusal costs nothing.
879
- */
880
- export declare function explainBatchError(error: unknown): string | null;
881
- export declare function collectResultText(result: BroadsideCollectResult): string;
882
- /**
883
- * An `onStatus` callback that appends one line to `lines` per *change* of a
884
- * lens's polled status. Every poll used to append a line, so a four-minute
885
- * wait returned twenty-six identical "in_progress (0/1)" lines per lens
886
- * before the result (0.22.0 live run).
887
- */
888
- export declare function statusLineWriter(lines: string[]): (lensId: string, status: string, counts: Record<string, unknown>) => void;
889
- export declare function statusText(state: BroadsideStateFile): string;
890
- export {};
1
+ export * from "./broadside/constants.ts";
2
+ export * from "./broadside/types.ts";
3
+ export * from "./broadside/schemas.ts";
4
+ export * from "./broadside/lenses.ts";
5
+ export * from "./broadside/repo.ts";
6
+ export * from "./broadside/requests.ts";
7
+ export * from "./broadside/state.ts";
8
+ export * from "./broadside/models.ts";
9
+ export * from "./broadside/client.ts";
10
+ export * from "./broadside/submit.ts";
11
+ export * from "./broadside/results.ts";
12
+ export * from "./broadside/verify.ts";
13
+ export * from "./broadside/collect.ts";
14
+ export * from "./broadside/render.ts";