codecartographer-pi 0.20.0 → 0.22.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 (40) hide show
  1. package/.codecarto/GUIDE.md +1 -1
  2. package/.codecarto/broadside/SKILL.md +15 -4
  3. package/.codecarto/broadside/config.yaml +26 -10
  4. package/.codecarto/findings/contracts/SKILL.md +4 -1
  5. package/.codecarto/findings/defect-scan/SKILL.md +10 -0
  6. package/.codecarto/findings/defect-scan-mechanical/SKILL.md +6 -0
  7. package/.codecarto/findings/defect-scan-semantic/SKILL.md +8 -1
  8. package/.codecarto/findings/porting/SKILL.md +4 -0
  9. package/.codecarto/findings/protocols/SKILL.md +4 -0
  10. package/.codecarto/templates/mechanical-defects.md +15 -0
  11. package/.codecarto/templates/reimplementation-spec.md +5 -3
  12. package/.codecarto/templates/reverse-engineering-bundle.md +10 -1
  13. package/.codecarto/templates/semantic-defects.md +15 -0
  14. package/.codecarto/workflow/VALIDATE.md +1 -1
  15. package/.codecarto/workflow/scaffold-version.yaml +1 -1
  16. package/README.md +5 -5
  17. package/agent-skill/codecartographer/references/broadside.md +10 -0
  18. package/dist/core/amendment.js +9 -4
  19. package/dist/core/broadside.d.ts +98 -0
  20. package/dist/core/broadside.js +330 -63
  21. package/dist/core/completion.js +7 -1
  22. package/dist/core/library.js +5 -3
  23. package/dist/core/pipeline.d.ts +39 -3
  24. package/dist/core/pipeline.js +61 -7
  25. package/dist/core/prompts.js +10 -3
  26. package/dist/core/status.d.ts +8 -0
  27. package/dist/core/status.js +39 -17
  28. package/dist/core/utils.d.ts +7 -0
  29. package/dist/core/utils.js +7 -0
  30. package/dist/core/workspace.js +2 -2
  31. package/dist/core/yaml.js +8 -1
  32. package/dist/extensions/codecarto/auto-runner.d.ts +1 -1
  33. package/dist/extensions/codecarto/auto-runner.js +18 -3
  34. package/dist/extensions/codecarto/broadside-flags.d.ts +7 -1
  35. package/dist/extensions/codecarto/broadside-flags.js +51 -0
  36. package/dist/extensions/codecarto/index.js +103 -35
  37. package/dist/extensions/codecarto/phase-compaction.js +5 -1
  38. package/dist/mcp-server/server.d.ts +5 -1
  39. package/dist/mcp-server/server.js +140 -32
  40. package/package.json +1 -1
@@ -81,7 +81,7 @@ Some files in this workspace are **read-only instructions** and must not be modi
81
81
  | Scaffold version stamp (framework-owned) | `workflow/scaffold-version.yaml` | Written at release, copied by init. The framework compares it to its own version to warn about stale scaffolds. Never edit. |
82
82
  | Findings (read-write) | `findings/<phase>/<primary-output>.md`, secondary output files | Create and update during phases. |
83
83
  | Phase handoff (read-write) | `scratch/handoffs/<phase>.yaml` | Phase executors propose state changes and closeout content here. The framework validates and applies them. |
84
- | Closeouts (framework-owned) | `closeouts/<date>-<phase-or-module>.md`, `THREAD_LOG.md` | Completion writes or updates one canonical closeout and one idempotent index entry. |
84
+ | Closeouts (framework-owned) | `closeouts/<date>-<phase-or-module>.md`, `THREAD_LOG.md` | Completion writes or updates one canonical closeout and one idempotent index entry. `<date>` and every other date the framework writes is the UTC calendar day; a date you write yourself (a `**Validated by:**` line, a decision) should be UTC too, or a late-evening session dates its validation a day before its closeout. |
85
85
  | Conventions (orchestrator-maintained) | `CONVENTIONS.md` | Cross-cutting patterns promoted to project-wide invariants. Phase executors propose; the orchestrator promotes at the phase boundary — in inline runs, the same chat changing hats. |
86
86
  | Decisions (orchestrator-maintained, append-only) | `DECISIONS.md` | Numbered log of decisions that diverge from spec, prompt, or obvious-default. Completion appends each handoff's `decisions` under `## Completion log`; the orchestrator may re-file entries into categories. |
87
87
  | Backlog (orchestrator-maintained) | `BACKLOG.md` | Work this project decided to **defer**, with the reasoning, the preconditions for revisiting, and the smallest viable form. Seeded from `templates/backlog-project.md` at init. |
@@ -108,7 +108,14 @@ architecture map. Each override is priced, capability-checked, and clamped like
108
108
  the default, the submit estimate breaks cost out per lens, and `run-meta.json`
109
109
  records which lens ran on what. No stronger default is shipped: which model is
110
110
  worth the money depends on the repository and the budget, so compare with the
111
- `models` action and decide.
111
+ `models` action and decide — for one run with the `model` and `lens_models`
112
+ parameters (Pi: `--model=ID`, `--lens-model=LENS:ID`), or here for the
113
+ repository. The `models` listing is advisory: OpenRouter's catalog returns a
114
+ `:batch` id for some models its Batch API refuses (`does not have a :batch
115
+ endpoint`), at no cost, and nothing in the catalog tells them apart. The
116
+ listing tags the ids this repository's own submits have seen accepted or
117
+ refused (`broadside/batch-endpoints.json`), and a refused lens says why in the
118
+ submit report.
112
119
 
113
120
  Every run knob — `incremental`, `retry_truncated`, `include_synthesis`,
114
121
  `include_triage`, `wait_seconds` — also has a repository default under the same
@@ -127,9 +134,13 @@ environment variable, or `api_key` in this directory's `config.yaml`.
127
134
 
128
135
  Submits are priced before they fire: Broad-Side estimates the run from the
129
136
  collected file sizes against the model's live per-token pricing and refuses
130
- when the estimate exceeds `max_cost` (`config.yaml` or the tool parameter)
131
- unless `force` is passed. See `config.yaml` for the model, limit, and manual
132
- pricing-override keys.
137
+ when the estimate exceeds `max_cost` (`config.yaml` or the tool parameter;
138
+ $1.00 when neither sets it, `0` for no limit) unless `force` is passed. See
139
+ `config.yaml` for the model, limit, and manual pricing-override keys. A
140
+ `config.yaml` that exists but cannot be parsed refuses every action except
141
+ `status` rather than running on defaults, and a `state.json` that cannot be
142
+ read refuses everything and is preserved beside itself — a paid run's batch
143
+ ids live there, so nothing writes over it.
133
144
 
134
145
  What leaves the machine is repository content, so a redaction pass runs
135
146
  before upload: files named like credential stores (`.env*`, `*.pem`,
@@ -54,10 +54,18 @@
54
54
  # Nothing in the catalog distinguishes them. Every Anthropic and OpenAI
55
55
  # batch id tried so far is rejected this way; Google and DeepSeek work.
56
56
  # A rejected batch costs nothing, so probe a candidate on one lens before
57
- # relying on it.
57
+ # relying on it: `codecarto_broadside {action: "submit", lenses:
58
+ # ["architecture"], model: "<id>"}` (Pi: `/codecarto-broadside architecture
59
+ # --model=<id>`). Submits remember the answer in batch-endpoints.json next
60
+ # to this file, and the `models` listing tags each id accordingly.
58
61
  # 2. A reasoning-capable model spends its output budget thinking, and the
59
62
  # thinking is billed at the full output rate. See `reasoning:` below.
60
63
  #
64
+ # The same two keys are submit parameters for one run — `model` and
65
+ # `lens_models` on codecarto_broadside, `--model=ID` and `--lens-model=LENS:ID`
66
+ # on /codecarto-broadside — and a lens set both there and here takes the
67
+ # parameter's.
68
+ #
61
69
  # lens_models:
62
70
  # security: deepseek/deepseek-v4-pro-0813:batch
63
71
  # defect: deepseek/deepseek-v4-pro-0813:batch
@@ -89,17 +97,24 @@
89
97
  # max_tokens: 2000 # or set the thinking budget directly
90
98
  # enabled: false # only where the provider allows it
91
99
 
92
- # Approximate run expense limit in USD (0 = no limit). Before submitting,
93
- # Broad-Side estimates the run cost from the collected file sizes and the
94
- # model's per-token pricing — fetched live from OpenRouter's model catalog
95
- # and cached for 24h. If the estimate exceeds max_cost, submit refuses and
96
- # prints the per-lens breakdown; pass force: true to override, or set a
97
- # value here so every run is guarded by default.
100
+ # Approximate run expense limit in USD. The default is 1.00, and it applies
101
+ # whether or not this key is set: the MCP surface cannot ask a human before
102
+ # spending, so a repository is guarded before anyone configures it. Before
103
+ # submitting, Broad-Side estimates the run cost from the collected file
104
+ # sizes and the model's per-token pricing — fetched live from OpenRouter's
105
+ # model catalog and cached for 24h. If the estimate exceeds max_cost, submit
106
+ # refuses and prints the per-lens breakdown; pass force: true (or approve
107
+ # Pi's dialog) to override for one run, raise the value here for a larger
108
+ # repository, or set 0 for no limit at all.
98
109
  #
99
110
  # This is a pre-flight estimate guardrail, not a runtime stop: OpenRouter
100
111
  # bills actual usage, which may differ from the estimate either direction.
101
112
  # Actual cost lands in each run's run-meta.json after collect.
102
113
  #
114
+ # A config.yaml that exists but cannot be parsed refuses submit, collect,
115
+ # and models rather than falling back to these defaults — a typo here must
116
+ # not silently remove the guard you set.
117
+ #
103
118
  # max_cost: 1.00
104
119
 
105
120
  # Manual pricing overrides in USD per MILLION tokens. Normally Broad-Side
@@ -136,9 +151,10 @@
136
151
  # include_triage: true
137
152
 
138
153
  # Default poll budget in seconds. 0 returns as soon as the batches are
139
- # submitted or the recorded state is read; a positive value polls that long
140
- # before returning with whatever is done. Batch jobs routinely take tens of
141
- # minutes, so a submit-then-collect-later rhythm is normal.
154
+ # submitted, and on collect polls each in-flight batch once and returns with
155
+ # whatever is done; a positive value keeps polling that long. Batch jobs
156
+ # routinely take tens of minutes, so a submit-then-collect-later rhythm is
157
+ # normal.
142
158
  #
143
159
  # wait_seconds: 0
144
160
 
@@ -20,7 +20,10 @@ Split the analysis by user-facing surface:
20
20
  - web UI
21
21
  - API or SDK
22
22
  - bot or background worker
23
- - storage or export formats
23
+ - storage or export formats — as user-visible behavior (what is written where, when it changes, what
24
+ survives a restart). The byte-level catalog of on-disk and wire formats belongs to the protocols
25
+ phase; name the format here and let protocols own its shape, so the two phases do not each write
26
+ it.
24
27
 
25
28
  For each feature, capture a contract with these fields:
26
29
  - `feature`
@@ -58,6 +58,16 @@ Assign one severity per finding:
58
58
  - `medium`: latent risk, dead code, defense-in-depth gap, or poor error handling.
59
59
  - `low`: style issue with correctness implications, stale docs, or minor inconsistency.
60
60
 
61
+ Severity is the consequence, not the confidence. A cheap runtime probe — a script that drives the
62
+ code path and shows the wrong result, the lost write, the escaped path — is worth running before
63
+ a `high` or `critical` is assigned on reading alone: it settles the evidence level at
64
+ `observed fact`, it may move the severity either way (a consequence that reading predicted and the
65
+ probe did not reproduce drops; one the probe showed to be worse than read rises), and it gives the
66
+ porting phase something it can cite. Record what the probe did and showed in the finding's
67
+ evidence cell, and keep the probe script under `.codecarto/scratch/probes/` so the porting phase
68
+ can rerun it. A finding whose consequence a probe confirmed keeps the severity that consequence
69
+ earns; it is not promoted for having been confirmed.
70
+
61
71
  ## Action Classification
62
72
 
63
73
  Tag each finding with a recommended action. Use the set that matches your pipeline:
@@ -13,6 +13,12 @@ The source code to analyze is in the parent directory (`../` relative to `.codec
13
13
 
14
14
  **Required:**
15
15
  - `findings/architecture/architecture-map.md` — for the layer map and public surfaces.
16
+ - The repository's own build and packaging surface, which pass 6 is about and which the source
17
+ files never mention: the package manifest (`package.json`, `pyproject.toml`, `go.mod`,
18
+ `Cargo.toml`, …) with its `files`/include list, the compiler or bundler config (`tsconfig.json`
19
+ and kin), the CI and release workflows (`.github/workflows/*`), and the ignore files. Three of
20
+ the high findings in one self-audit came from these files alone; a scan that reads only source
21
+ cannot see what ships or what runs in CI.
16
22
 
17
23
  You do **not** need contracts or protocols for this phase. If those exist already, skim them as bonus context, but do not block on them — the mechanical pass is by design context-light.
18
24
 
@@ -17,6 +17,13 @@ The source code to analyze is in the parent directory (`../` relative to `.codec
17
17
  - `findings/protocols/protocols-and-state.md` — passes 3 and 5 need this for state machine and event-ordering analysis.
18
18
  - `findings/defect-scan-mechanical/mechanical-defects.md` — read so you do not re-flag what the mechanical phase already covered.
19
19
 
20
+ **Read when a finding turns on it:** the source of a dependency the code trusts (under
21
+ `node_modules/`, the vendored tree, or the SDK's published source), at the pinned version. The
22
+ evidence rules allow "that system's own source" to settle an `external-behavior claim` about it,
23
+ and passes 4 and 5 keep meeting claims of that shape — what a lock does on release, what a
24
+ session runtime frees on dispose. Reading the pinned source is cheaper than a runtime probe and
25
+ turns the claim into an `observed fact`; say which file and version you read.
26
+
20
27
  If any prerequisite is missing, stop and surface the gap. The semantic pass is the *paid-for* benefit of running the mechanical phase early; do not regress it by running the semantic pass blind.
21
28
 
22
29
  ## Pass Execution Order
@@ -39,7 +46,7 @@ Use the contracts and protocols outputs to decide emphasis:
39
46
 
40
47
  ## Evidence, Severity, and Action Classification
41
48
 
42
- Use the scheme from `findings/defect-scan/SKILL.md`. The severity and action sets are the same as the legacy defect-scan phase.
49
+ Use the scheme from `findings/defect-scan/SKILL.md`. The severity and action sets are the same as the legacy defect-scan phase, including its rule for probe-confirmed findings: a cheap runtime probe before a `high` or `critical` settles the evidence level and may move the severity either way; it never promotes a finding for having been confirmed.
43
50
 
44
51
  When citing a contract or protocol violation, include the contract ID or state-machine reference from the upstream finding so the porting phase can trace the chain.
45
52
 
@@ -39,6 +39,10 @@ Sort features by porting importance:
39
39
  - `incidental`: source-specific ergonomics or implementation accidents.
40
40
 
41
41
  If the defect report is available, integrate defect findings into the porting bundle:
42
+ - Read each scan's `§Runtime probes` section (and the probe scripts it points at under
43
+ `scratch/probes/`) before the findings tables: a probe-confirmed finding is the strongest
44
+ evidence the scans produce, its severity is settled, and the bundle's dispositions should lean
45
+ on it first.
42
46
  - Reference relevant defects in the feature contract table.
43
47
  - Tag each referenced defect with a porting recommendation: `fix before porting` (the defect would carry into a new implementation), `port differently` (the new implementation should handle this case differently by design), `leave behind` (the defect is specific to the source implementation and won't survive porting), or `verify at runtime` (the diagnosis is an `external-behavior claim` or `open question` — carry it as a spike for the spec, and do not design around an unverified diagnosis). Preserve `verify at runtime` as written: flattening it into one of the settled three is how a hedge stops traveling.
44
48
  - Consolidate defect-related portability hazards alongside hazards from other phases.
@@ -37,6 +37,10 @@ Convert control flow into a state machine:
37
37
  - Record guards and side effects.
38
38
  - Separate synchronous barriers from observational events.
39
39
 
40
+ This phase owns the storage-format catalog: every on-disk and wire format the system reads or
41
+ writes, with its shape, version markers, and reader/writer pairs. The contracts phase names the
42
+ formats it saw as user-visible behavior; catalog them here once rather than asking contracts to.
43
+
40
44
  Capture persistence semantics explicitly:
41
45
  - append-only vs mutable
42
46
  - branching vs linear history
@@ -95,6 +95,21 @@
95
95
 
96
96
  ---
97
97
 
98
+ ## Runtime probes
99
+
100
+ <!-- Optional, but the strongest evidence this report can carry. One row per probe you ran to
101
+ confirm or refute a finding before assigning its severity: a short script that drives the
102
+ code path and shows the wrong result, the lost write, the escaped path. Keep the scripts
103
+ under scratch/probes/ so the porting phase can rerun them, and put the finding's row number
104
+ in the Finding column so the two can be read together. A probe that did NOT reproduce the
105
+ read prediction is worth a row too — it is what lowers a severity honestly. -->
106
+
107
+ | Probe | Finding | What it did | What it showed | Script |
108
+ |---|---|---|---|---|
109
+ | | | | | |
110
+
111
+ ---
112
+
98
113
  ## Open Questions
99
114
 
100
115
  <!-- Every finding whose Evidence Level is open question or external-behavior claim gets a row
@@ -121,9 +121,11 @@
121
121
  ## Carry-Forward
122
122
 
123
123
  <!-- Reimplementation-spec is the terminal phase in most pipelines, so most items belong in Known Unknowns
124
- above. Use this section only when post-pipeline work (spikes, deltas, amendments) is the right place
125
- to close an item. Each entry: { id, kind: defer-to-phase, target_phase, description, deferred_reason }.
126
- Allowed target_phase values for post-pipeline: "spike", "delta", "amendment". -->
124
+ above. A carry_forward entry needs a target_phase that is a LATER phase of the active pipeline —
125
+ completion refuses anything else — so in a pipeline where this phase is last, this table stays empty.
126
+ Work for after the pipeline (spikes, deltas, amendments, maintainer rulings, opinionated reruns) goes
127
+ in the handoff's post_pipeline list instead: { id, kind: spike | delta | amendment, description }.
128
+ An amendment retires those entries once the pipeline is complete. -->
127
129
 
128
130
  | ID | Target Phase | Description | Deferred Reason |
129
131
  |---|---|---|---|
@@ -81,7 +81,16 @@
81
81
 
82
82
  This section is the porting-oriented view of defects — it should answer
83
83
  "which defects must the reimplementation design around, and how?" not
84
- re-list every finding. Keep it under one screen.
84
+ re-list every finding.
85
+
86
+ Two pressures meet here and one wins: the spec phase needs a disposition
87
+ (fix before porting / port differently / leave behind / verify at runtime)
88
+ for every defect it must design around, and a reader needs a table short
89
+ enough to hold in mind. Completeness wins. Give every high and medium
90
+ finding its own row; group lows by shared root cause into one row each,
91
+ naming the source rows, so that nothing is dropped and the table still
92
+ reads. A scan of 78 findings came out as 11 highs, 28 mediums, and 7 low
93
+ groups this way — long, but every disposition the spec needed was there.
85
94
  -->
86
95
 
87
96
  | Defect ID | Source Report | One-line Description | Severity | Disposition | Required design consequence |
@@ -104,6 +104,21 @@
104
104
 
105
105
  ---
106
106
 
107
+ ## Runtime probes
108
+
109
+ <!-- Optional, but the strongest evidence this report can carry. One row per probe you ran to
110
+ confirm or refute a finding before assigning its severity: a short script that drives the
111
+ code path and shows the wrong result, the lost write, the escaped path. Keep the scripts
112
+ under scratch/probes/ so the porting phase can rerun them, and put the finding's row number
113
+ in the Finding column so the two can be read together. A probe that did NOT reproduce the
114
+ read prediction is worth a row too — it is what lowers a severity honestly. -->
115
+
116
+ | Probe | Finding | What it did | What it showed | Script |
117
+ |---|---|---|---|---|
118
+ | | | | | |
119
+
120
+ ---
121
+
107
122
  ## Open Questions
108
123
 
109
124
  <!-- Every finding whose Evidence Level is open question or external-behavior claim gets a row
@@ -31,7 +31,7 @@ Append this to the end of every primary output file:
31
31
  | 1 | [criterion text from pipeline.yaml] | PASS / PARTIAL / FAIL | [section reference or note] |
32
32
  | 2 | ... | ... | ... |
33
33
 
34
- **Validated by:** [session identifier or date]
34
+ **Validated by:** [session identifier or date — YYYY-MM-DD in UTC, the clock every framework-written date uses]
35
35
  **Overall:** PASS / PASS WITH GAPS / FAIL
36
36
  ```
37
37
 
@@ -3,4 +3,4 @@
3
3
  # workspace's framework-owned files (GUIDE.md, templates/, workflow/ pipelines
4
4
  # and VALIDATE.md) predate the running release. Written at release time and
5
5
  # copied verbatim by init — never edit by hand.
6
- scaffold_version: 0.20.0
6
+ scaffold_version: 0.22.0
package/README.md CHANGED
@@ -271,11 +271,11 @@ Every state change re-renders `.codecarto/dashboard.html` — a self-contained s
271
271
  - Pipeline progress strip with per-phase status badges
272
272
  - Per-phase cards with output links, open questions, carry-forward routing, owner notes, last-run usage
273
273
  - Aggregate token and compaction telemetry + per-phase breakdown
274
- - Activity timeline with session-file links
274
+ - Activity timeline of phase runs (tokens, duration, compactions)
275
275
  - Open questions roll-up grouped by source phase
276
276
  - Closeouts list (reverse-chronological) with relative-path links
277
277
 
278
- No JavaScript. No external assets. Light/dark via `prefers-color-scheme`. Works opened directly from `file://`.
278
+ No external assets of any kind — nothing linked, nothing fetched. The file inlines two `<script>` blocks of its own (a JSON data island and the search/filter code), so it works opened directly from `file://`. Light/dark via `prefers-color-scheme`.
279
279
 
280
280
  **Opt-in narrative summary.** `/codecarto-dashboard --narrate` runs the orchestrator's model as a one-shot session that writes a 200–400 word executive summary citing specific findings from recent closeouts. Cached to `.codecarto/.dashboard-narration.local.md` and preserved across deterministic re-renders with a "(N runs since)" staleness note.
281
281
 
@@ -396,11 +396,11 @@ codecarto_broadside {cwd, action: "status"} # what is in fligh
396
396
  codecarto_broadside {cwd, action: "collect"} # poll, save, synthesize, triage
397
397
  ```
398
398
 
399
- Submit and collect are separate because batch jobs routinely take tens of minutes; collect is resumable and picks up whatever is still in flight. Submit prices the run from the collected file sizes against the model's live per-token pricing (cached 24h) and refuses when the estimate exceeds `max_cost` unless `force: true` is passed — a pre-flight estimate, not a runtime stop. Actual spend lands in each run's `run-meta.json`.
399
+ Submit and collect are separate because batch jobs routinely take tens of minutes; collect is resumable and picks up whatever is still in flight (`wait_seconds: 0`, the default, polls once and returns). Submit prices the run from the collected file sizes against the model's live per-token pricing (cached 24h) and refuses when the estimate exceeds `max_cost` — $1.00 unless the config or the call sets another value, `0` for no limit — unless `force: true` is passed — a pre-flight estimate, not a runtime stop. Actual spend lands in each run's `run-meta.json`.
400
400
 
401
- Repository defaults live in `.codecarto/broadside/config.yaml` (`model`, `api_key`, `default_lenses`, `max_cost`, `pricing` overrides, `lens_models`, `incremental`, `retry_truncated`, `include_synthesis`, `include_triage`, `wait_seconds`); an explicit tool parameter always wins. `lens_models` routes individual lenses to their own batch model — a stronger model changes security and defect findings far more than it changes an architecture map — and each override is priced, capability-checked, and clamped exactly like the default, with the estimate broken out per lens so a mixed-model run cannot be approved without seeing which lens costs what. CodeCartographer ships no stronger default: which model earns its price depends on your repository and budget, so compare candidates with the `models` action and choose. `codecarto_skill {cwd, name: "broadside"}` returns the reading guide for a completed run, and unlike post-pipeline skills it is not gated on a finished pipeline.
401
+ Repository defaults live in `.codecarto/broadside/config.yaml` (`model`, `api_key`, `default_lenses`, `max_cost`, `pricing` overrides, `lens_models`, `incremental`, `retry_truncated`, `include_synthesis`, `include_triage`, `wait_seconds`); an explicit tool parameter always wins. `lens_models` routes individual lenses to their own batch model — a stronger model changes security and defect findings far more than it changes an architecture map — and each override is priced, capability-checked, and clamped exactly like the default, with the estimate broken out per lens so a mixed-model run cannot be approved without seeing which lens costs what. CodeCartographer ships no stronger default: which model earns its price depends on your repository and budget, so compare candidates with the `models` action and choose — for one run with the `model` and `lens_models` parameters (Pi: `--model=ID`, `--lens-model=LENS:ID`), or for the repository in `config.yaml`. The `models` listing is advisory: OpenRouter's catalog returns a `:batch` id for some models its Batch API then refuses (`does not have a :batch endpoint`), at no cost, and nothing in the catalog tells them apart — so the listing tags the ids this repository's own submits have seen accepted or refused, and a refused lens says why in the submit report. `codecarto_skill {cwd, name: "broadside"}` returns the reading guide for a completed run, and unlike post-pipeline skills it is not gated on a finished pipeline.
402
402
 
403
- On the Pi extension the same run is `/codecarto-broadside [submit|collect|status|models] [lenses…]`, with tab-completion for actions and lens names and live per-lens progress while batches poll. The two surfaces differ in one deliberate place: MCP cannot ask a human, so it refuses a run over `max_cost` until you pass `force`; Pi shows the per-lens breakdown and asks, and your approval *is* the force flag. Neither surface takes an API key as a command argument — a key typed into a slash command lands in the session transcript.
403
+ On the Pi extension the same run is `/codecarto-broadside [submit|collect|status|models] [lenses…] [--model=ID] [--lens-model=LENS:ID]`, with tab-completion for actions, lens names, and flags and live per-lens progress while batches poll. The two surfaces differ in one deliberate place: MCP cannot ask a human, so it refuses a run over `max_cost` until you pass `force`; Pi shows the per-lens breakdown and asks, and your approval *is* the force flag. Neither surface takes an API key as a command argument — a key typed into a slash command lands in the session transcript.
404
404
 
405
405
  Broad-Side needs runtime code, so firing a run is an executable-surface feature: Pi and MCP have it, the pure drop-in template does not (it carries only the reading guide).
406
406
 
@@ -82,6 +82,16 @@ Two more economies worth knowing:
82
82
  conventions is usually a better trade than raising the model for everything.
83
83
  Overrides are priced and capability-checked individually, and the estimate
84
84
  breaks cost out per lens.
85
+ - `model` and `lens_models` are also submit parameters (Pi: `--model=ID`,
86
+ `--lens-model=LENS:ID`), for one run without editing the file. Choose from
87
+ `action: "models"`, and read that listing as advisory: OpenRouter's catalog
88
+ returns a `:batch` id for some models its Batch API then refuses (`does not
89
+ have a :batch endpoint`) — free, reported on the lens with the reason, and
90
+ remembered, so the listing tags ids this repository has seen accepted or
91
+ refused. Probe an untried model on one lens before a six-lens run. A
92
+ `job-submission-count` refusal is the account's concurrent-job quota (one
93
+ job per lens fills it fast across runs); collect or wait out what is in
94
+ flight, then re-submit.
85
95
 
86
96
  ## Reading a run
87
97
 
@@ -7,7 +7,7 @@
7
7
  // "record for a later explicit amendment" that nothing can perform.
8
8
  import { appendFile, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
9
9
  import { basename, join } from "node:path";
10
- import { getNextEligiblePhase } from "./pipeline.js";
10
+ import { describeStuckPipeline, resolvePipelineOutcome } from "./pipeline.js";
11
11
  import { buildTerminalNextActions, ensureArray, normalizeStatus } from "./status.js";
12
12
  import { dateOnly, newlineIfUnterminated, pathExists } from "./utils.js";
13
13
  import { getWorkspaceState, updateStatusAtomically } from "./workspace.js";
@@ -101,11 +101,16 @@ export async function applyAmendment(cwd, name) {
101
101
  if (!initialState)
102
102
  throw new Error("CodeCartographer workspace not found. Run /codecarto-init first.");
103
103
  const amendment = await loadAmendmentFile(name, initialState.workspaceDir);
104
- const nextPhase = getNextEligiblePhase(initialState);
105
- if (nextPhase) {
106
- throw new Error(`Cannot amend: the pipeline is not complete (next phase: ${nextPhase.id}). `
104
+ const outcome = resolvePipelineOutcome(initialState);
105
+ if (outcome.kind === "eligible") {
106
+ throw new Error(`Cannot amend: the pipeline is not complete (next phase: ${outcome.phase.id}). `
107
107
  + `Resolve open questions and routed items through that phase's handoff (open_question_closures / carry_forward_closures) instead.`);
108
108
  }
109
+ if (outcome.kind === "stuck") {
110
+ // Amendments are the post-pipeline channel; a pipeline that cannot
111
+ // finish is not there yet (#228).
112
+ throw new Error(`Cannot amend: the pipeline is not complete. ${describeStuckPipeline(outcome.blocked)}`);
113
+ }
109
114
  const timestamp = new Date().toISOString();
110
115
  const applied = { openQuestionsClosed: [], postPipelineClosed: [], unknownIds: [] };
111
116
  let closeoutNotice = "";
@@ -11,11 +11,28 @@ export declare const BROADSIDE_OUTPUT_PRICE_PER_M = 1.875;
11
11
  export declare const BROADSIDE_MODELS_URL = "https://openrouter.ai/api/v1/models";
12
12
  export declare const BROADSIDE_BENCHMARKS_URL = "https://openrouter.ai/api/v1/benchmarks";
13
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";
14
21
  export declare const BROADSIDE_CATALOG_CACHE_TTL_MS: number;
15
22
  export declare const BROADSIDE_LENS_IDS: readonly ["architecture", "api", "security", "defect", "conventions", "porting"];
16
23
  export type BroadsideLensId = (typeof BROADSIDE_LENS_IDS)[number];
17
24
  export declare const BROADSIDE_POLL_INTERVAL_MS = 15000;
18
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;
19
36
  export type ModelPricing = {
20
37
  /** USD per million input tokens. */
21
38
  inputPerM: number;
@@ -324,6 +341,27 @@ export declare class BroadsideAuthError extends Error {
324
341
  readonly detail: string;
325
342
  constructor(httpStatus: number, detail: string);
326
343
  }
344
+ /**
345
+ * `broadside/config.yaml` exists but cannot be used. A file that failed to
346
+ * parse used to be treated exactly like an absent one — defaults, including
347
+ * no spend cap and no lens routing, with no message — so a typo removed the
348
+ * user's own guard (#232). Only an absent file yields defaults now.
349
+ */
350
+ export declare class BroadsideConfigError extends Error {
351
+ readonly path: string;
352
+ constructor(path: string, detail: string);
353
+ }
354
+ /**
355
+ * `broadside/state.json` exists but cannot be read. It used to be read as
356
+ * empty and the next checkpoint wrote that empty state over it, losing the
357
+ * batch ids of every in-flight, already-paid run (#233). The corrupt file is
358
+ * preserved beside itself and nothing writes over it until someone looks.
359
+ */
360
+ export declare class BroadsideStateError extends Error {
361
+ readonly path: string;
362
+ readonly backupPath: string;
363
+ constructor(path: string, backupPath: string, detail: string);
364
+ }
327
365
  /** Thrown when a confirm hook declines a run. Nothing was submitted. */
328
366
  export declare class BroadsideCancelledError extends Error {
329
367
  constructor(message?: string);
@@ -504,6 +542,30 @@ export declare function updateBroadsideStateAtomically(broadsideDir: string, mut
504
542
  */
505
543
  export declare function persistBroadsideRun(broadsideDir: string, run: BroadsideRun): Promise<BroadsideStateFile>;
506
544
  export declare function loadBroadsideConfig(broadsideDir: string): Promise<BroadsideConfig>;
545
+ /** The shipped defaults: what an absent config.yaml means. */
546
+ export declare function defaultBroadsideConfig(): BroadsideConfig;
547
+ /** The catalog cache schema this build writes; a file from another is not read. */
548
+ export declare const BROADSIDE_CATALOG_CACHE_SCHEMA = 3;
549
+ /** One model's most recent submit outcome, as remembered in {@link BROADSIDE_ENDPOINTS_FILE}. */
550
+ export type BatchEndpointRecord = {
551
+ status: "accepted" | "rejected";
552
+ /** ISO timestamp of the submit that produced this record. */
553
+ at: string;
554
+ /** The provider's refusal, for a rejected endpoint. */
555
+ error?: string;
556
+ };
557
+ export declare function readBatchEndpoints(broadsideDir: string): Promise<Record<string, BatchEndpointRecord>>;
558
+ /**
559
+ * Remember what a submit learned about each model it posted to. An accepted
560
+ * job proves the endpoint exists; a "does not have a :batch endpoint"
561
+ * refusal proves it does not. Any other rejection (quota, malformed request,
562
+ * auth) says nothing about the endpoint and leaves the record alone.
563
+ */
564
+ export declare function recordBatchEndpoints(broadsideDir: string, outcomes: Array<{
565
+ model: string;
566
+ batchId: string;
567
+ error?: unknown;
568
+ }>): Promise<void>;
507
569
  export declare function builtInCatalogEntry(model: string): CatalogEntry | null;
508
570
  export declare function builtInPricing(model: string): ModelPricing | null;
509
571
  export declare function resolveCatalogEntry(broadsideDir: string, config: BroadsideConfig, model: string, apiKey: string, fetcher?: FetchLike): Promise<BroadsideCatalogResult>;
@@ -517,6 +579,8 @@ export declare function listBatchModels(broadsideDir: string, config: BroadsideC
517
579
  source: string;
518
580
  benchmarks: CodingBenchmarks | null;
519
581
  defaultModel: string;
582
+ /** This repository's remembered submit outcomes per model, from {@link BROADSIDE_ENDPOINTS_FILE}. */
583
+ endpoints: Record<string, BatchEndpointRecord>;
520
584
  }>;
521
585
  export type FetchLike = (url: string, init: Record<string, unknown>) => Promise<Response>;
522
586
  export declare function submitBatch(batchRequests: BatchRequest[], apiKey: string, fetcher?: FetchLike, model?: string): Promise<{
@@ -533,6 +597,14 @@ export declare function fetchBatch(batchId: string, apiKey: string, fetcher?: Fe
533
597
  * charged, so callers must come back for it rather than retire it.
534
598
  */
535
599
  export declare const BROADSIDE_DEAD_BATCH_STATUSES: string[];
600
+ /**
601
+ * Batch entry statuses collect never polls again: the dead ones above, plus
602
+ * `completed`, plus the two a submit assigns without a batch (`skipped`: no
603
+ * matching files; `rejected`: the provider refused it). The 0.19.1 changelog
604
+ * called the dead set "a named constant rather than two hand-maintained
605
+ * lists"; this set was still three literal copies (self-audit sem 5.8).
606
+ */
607
+ export declare const BROADSIDE_TERMINAL_ENTRY_STATUSES: string[];
536
608
  export declare function pollBatchUntilTerminal(batchId: string, apiKey: string, opts?: {
537
609
  deadlineMs?: number;
538
610
  onStatus?: (status: string, counts: Record<string, unknown>) => void;
@@ -559,6 +631,12 @@ export declare function runBroadsideSubmit(cwd: string, apiKey: string, opts?: {
559
631
  lenses?: BroadsideLensId[];
560
632
  fetcher?: FetchLike;
561
633
  model?: string;
634
+ /**
635
+ * Per-lens model overrides for this run, layered over config.yaml's
636
+ * `lens_models`: a lens named here runs on this model, a lens named only
637
+ * in the file runs on the file's, and the rest run on `model` (#141).
638
+ */
639
+ lensModels?: Partial<Record<BroadsideLensId, string>>;
562
640
  /** Approximate run expense limit in USD; 0 means no limit. */
563
641
  maxCost?: number;
564
642
  /** Submit even when the estimate exceeds maxCost. */
@@ -615,6 +693,12 @@ export declare function runBroadsideCollect(cwd: string, apiKey: string, opts?:
615
693
  retryTruncated?: boolean;
616
694
  onStatus?: (lensId: string, status: string, counts: Record<string, unknown>) => void;
617
695
  fetcher?: FetchLike;
696
+ /**
697
+ * Which run to collect. Absent, the most recent — which used to be the
698
+ * only choice, so an older run still in flight could not be collected
699
+ * once a newer submit existed (#268). `status` lists the ids.
700
+ */
701
+ runId?: string;
618
702
  }): Promise<BroadsideCollectResult>;
619
703
  export declare function runBroadsideStatus(cwd: string): Promise<{
620
704
  state: BroadsideStateFile;
@@ -624,7 +708,21 @@ export declare function estimateSubmitText(result: BroadsideSubmitResult, lenses
624
708
  export declare function modelsText(entries: CatalogEntry[], opts: {
625
709
  benchmarks: CodingBenchmarks | null;
626
710
  defaultModel: string;
711
+ endpoints?: Record<string, BatchEndpointRecord>;
627
712
  }): string;
713
+ /**
714
+ * A provider refusal plus what to do about it, for the two refusals a batch
715
+ * run meets in practice and cannot fix by itself (#141):
716
+ *
717
+ * - `Model '<id>' does not have a :batch endpoint.` — the catalog advertises a
718
+ * `:batch` id that OpenRouter runs no batch endpoint for. Nothing in the
719
+ * catalog distinguishes these; the `models` action marks ids this
720
+ * repository has seen refused.
721
+ * - `job-submission-count … in use: 16, quota: 16` — the per-account limit
722
+ * on concurrent batch jobs. Broad-Side submits one job per lens, so a few
723
+ * runs in flight on the same key fill it; the refusal costs nothing.
724
+ */
725
+ export declare function explainBatchError(error: unknown): string | null;
628
726
  export declare function collectResultText(result: BroadsideCollectResult): string;
629
727
  export declare function statusText(state: BroadsideStateFile): string;
630
728
  export {};