codecartographer-pi 0.17.1 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codecarto/GUIDE.md +9 -2
- package/.codecarto/skills/spec-delta-application/SKILL.md +1 -1
- package/.codecarto/templates/amendment.yaml +3 -3
- package/.codecarto/templates/phase-handoff.yaml +20 -1
- package/.codecarto/templates/spike-report.md +2 -2
- package/.codecarto/workflow/scaffold-version.yaml +1 -1
- package/README.md +10 -6
- package/agent-skill/codecartographer/SKILL.md +1 -1
- package/agent-skill/codecartographer/references/handoff-contract.md +30 -2
- package/agent-skill/codecartographer/references/library.md +2 -2
- package/agent-skill/codecartographer/references/phase-recovery.md +1 -1
- package/dist/core/amendment.d.ts +5 -0
- package/dist/core/amendment.js +21 -2
- package/dist/core/completion.d.ts +15 -0
- package/dist/core/completion.js +80 -3
- package/dist/core/coverage.d.ts +45 -0
- package/dist/core/coverage.js +131 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/library.d.ts +73 -0
- package/dist/core/library.js +135 -37
- package/dist/core/orchestrator-config.d.ts +6 -0
- package/dist/core/orchestrator-config.js +2 -0
- package/dist/core/prompts.js +19 -1
- package/dist/core/status.d.ts +10 -2
- package/dist/core/status.js +56 -7
- package/dist/core/types.d.ts +24 -1
- package/dist/core/workspace.d.ts +16 -0
- package/dist/core/workspace.js +31 -4
- package/dist/extensions/codecarto/index.js +355 -20
- package/dist/mcp-server/server.js +63 -4
- package/package.json +1 -1
package/.codecarto/GUIDE.md
CHANGED
|
@@ -92,7 +92,7 @@ If you are uncertain whether a file should be modified, treat it as read-only.
|
|
|
92
92
|
### Two things named "backlog", and neither is the other
|
|
93
93
|
|
|
94
94
|
- **`BACKLOG.md` in this workspace** is *this project's* deferrals: work the project chose not to do yet. `DECISIONS.md` records what the project decided to **do**; `BACKLOG.md` records what it decided to **defer**. Deferrals get no `D` number. When a deferred item is later picked up, remove its entry here and record the decision in `DECISIONS.md`.
|
|
95
|
-
- **`status.yaml`'s `post_pipeline` list** is framework-owned lifecycle state, not this file. Items there are retired by `codecarto_amend
|
|
95
|
+
- **`status.yaml`'s `post_pipeline` list** is framework-owned lifecycle state, not this file. Items there are retired by an amendment (`codecarto_amend` on MCP, `/codecarto-amend` on Pi), never by hand.
|
|
96
96
|
- **CodeCartographer's own backlog** — deferred improvements to the *framework* — lives in the CodeCartographer repository, not in your workspace. If a phase prompt misled you or a validation criterion did not fit, that is feedback to the framework; it does not belong in this file.
|
|
97
97
|
|
|
98
98
|
## Pipeline Selection
|
|
@@ -215,7 +215,14 @@ post_pipeline:
|
|
|
215
215
|
|
|
216
216
|
`kind` is one of: `needs-runtime-test`, `needs-maintainer-decision`, `needs-spec-ruling`, `defer-to-phase`, `needs-fixture-capture`, or a post-pipeline work kind such as `spike` or `amendment`. Every new `carry_forward.target_phase` must be an ID in the active pipeline. Every `post_pipeline` entry requires a stable ID. Open questions should carry a stable `id` (e.g. `q-loadconfig-ambiguity`); if omitted, the framework auto-assigns one. When a later phase resolves an open question, list its id in `open_question_closures` to remove it from all phases. The downstream phase records resolved carry-forward IDs in `carry_forward_closures`; completion removes those entries atomically.
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
**Closing a routed item does not settle the question it came from.** A phase routinely registers an open question and routes one of its candidate answers onward in the same handoff; addressing the routed item later is not the same as answering the question. Two optional fields make that distinction enforceable:
|
|
219
|
+
|
|
220
|
+
- A `carry_forward` entry may name `derives_from: <open-question-id>`, meaning "this routed item is one candidate answer to that question." Fill it in the handoff that registers the question, when both entries are in front of you. Completion then refuses a `carry_forward_closures` entry for that item while its question is still open and is not closed by the same handoff, naming both ids. Close the question with evidence in that handoff, or leave the item routed and give the finding an unsettled action (`verify at runtime`). Entirely opt-in: a `carry_forward` entry without `derives_from` closes exactly as it always has.
|
|
221
|
+
- An `open_question_closures` entry may be written as `{ id, evidence }` instead of a bare id, where `evidence` names what settled the question. For a question whose `kind` is `needs-runtime-test`, non-empty `evidence` is **required** — such a question closes on a spike report or an observation against the running system, not on another read of the same source — and that requirement holds however the closure is written, so a bare id no longer closes a runtime question. Questions of every other kind are unaffected, and a bare id stays valid for them. The requirement follows the scaffold: a workspace at scaffold version 0.19.0 or newer has the closure refused, while an older scaffold — which never documented the rule — gets a non-gating note, and refreshing the scaffold opts it in.
|
|
222
|
+
|
|
223
|
+
Completed phases' declared coverage gaps travel too: the `Skipped scope` and `Known blind spots` bullets of every completed phase's `## Coverage and limits` section are surfaced in the next phase's orchestrator duties. A finding that lands inside one of those gaps must either close it with cited new evidence of its own or inherit its uncertainty.
|
|
224
|
+
|
|
225
|
+
After the pipeline completes, the handoff channel closes with it. Post-pipeline resolutions — an open question answered on evidence, a finished `post_pipeline` backlog item — are applied with an **amendment**: write `scratch/amendments/<slug>.yaml` (see `templates/amendment.yaml`) and run `codecarto_amend` (MCP) or `/codecarto-amend <slug>` (Pi). It updates `workflow/status.yaml` under the same lock completion uses and writes an amendment closeout plus THREAD_LOG entry. Never hand-edit `status.yaml` for this; amendments are refused while the pipeline is still running, so the two channels cannot race.
|
|
219
226
|
|
|
220
227
|
## Phase Selection Logic
|
|
221
228
|
|
|
@@ -88,7 +88,7 @@ Per the standard closeout ritual:
|
|
|
88
88
|
|
|
89
89
|
- Append a one-line entry to `THREAD_LOG.md` pointing at the closeout file.
|
|
90
90
|
- Write `closeouts/<YYYY-MM-DD>-spec-deltas.md` using `templates/closeout-template.md`.
|
|
91
|
-
- If a delta resolved an `open_questions` entry (or finished a `post_pipeline` backlog item), apply it: write `scratch/amendments/<slug>.yaml` (see `templates/amendment.yaml`) listing the closures, then run `codecarto_amend
|
|
91
|
+
- If a delta resolved an `open_questions` entry (or finished a `post_pipeline` backlog item), apply it: write `scratch/amendments/<slug>.yaml` (see `templates/amendment.yaml`) listing the closures, then run `codecarto_amend` (MCP) or `/codecarto-amend <slug>` (Pi). It updates `status.yaml` under the completion lock and writes the amendment closeout — never hand-edit `status.yaml`, which is framework-owned. Without Pi or the MCP server (drop-in mode), record the intended amendment file in the closeout for the next Pi or MCP session to apply.
|
|
92
92
|
- Append numbered entries to `DECISIONS.md` for any decisions made during triage that weren't already in the deltas (e.g., "rejected Δ7 because the spec already covered the case at §X").
|
|
93
93
|
|
|
94
94
|
## What to avoid
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Post-pipeline amendment schema v1. Copy to scratch/amendments/<slug>.yaml,
|
|
2
|
-
# then run codecarto_amend (MCP)
|
|
3
|
-
# incomplete — mid-pipeline resolutions belong in the phase
|
|
4
|
-
# (open_question_closures / carry_forward_closures).
|
|
2
|
+
# then run codecarto_amend (MCP) or /codecarto-amend <slug> (Pi). Refused while
|
|
3
|
+
# the pipeline is incomplete — mid-pipeline resolutions belong in the phase
|
|
4
|
+
# handoff (open_question_closures / carry_forward_closures).
|
|
5
5
|
schema_version: 1
|
|
6
6
|
# Open-question ids resolved on evidence after the pipeline completed;
|
|
7
7
|
# removed from every phase in workflow/status.yaml.
|
|
@@ -4,9 +4,28 @@ schema_version: 1
|
|
|
4
4
|
phase_id: <phase-id>
|
|
5
5
|
owner_notes: []
|
|
6
6
|
open_questions: []
|
|
7
|
+
# Items a specific later phase closes. Every entry needs a target_phase naming a
|
|
8
|
+
# downstream phase in the active pipeline. Optional derives_from names the
|
|
9
|
+
# open_questions id this item is one candidate answer to:
|
|
10
|
+
# - id: mech-CF3
|
|
11
|
+
# kind: defer-to-phase
|
|
12
|
+
# target_phase: defect-scan-semantic
|
|
13
|
+
# derives_from: q-logit-bias-root-cause
|
|
14
|
+
# description: <the routed item>
|
|
15
|
+
# Fill derives_from in the handoff that registers the question — that is the one
|
|
16
|
+
# moment both entries are in front of you. Closing the routed item later does not
|
|
17
|
+
# settle the question it came from, and completion refuses such a closure while
|
|
18
|
+
# the question is still open and unclosed by the same handoff.
|
|
7
19
|
carry_forward: []
|
|
8
20
|
carry_forward_closures: []
|
|
9
|
-
# Resolved open questions: their IDs are removed from all phases.
|
|
21
|
+
# Resolved open questions: their IDs are removed from all phases. Either a bare
|
|
22
|
+
# id, or an object naming the evidence that settled it:
|
|
23
|
+
# - q-loadconfig-ambiguity
|
|
24
|
+
# - id: q-logit-bias-root-cause
|
|
25
|
+
# evidence: <the spike report or runtime observation that answered it>
|
|
26
|
+
# A question of kind needs-runtime-test requires non-empty evidence — it closes
|
|
27
|
+
# on runtime evidence, not on another read of the same source. Refused from
|
|
28
|
+
# scaffold version 0.19.0; older scaffolds get a non-gating note instead.
|
|
10
29
|
open_question_closures: []
|
|
11
30
|
# Work after the active pipeline: spikes, amendments, deltas, maintainer rulings,
|
|
12
31
|
# or opinionated reruns. Every entry requires a stable id.
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
phase handoff. When a spike's findings change the reimplementation spec, write
|
|
13
13
|
the deltas as Recommended Deltas below and apply them with the
|
|
14
14
|
spec-delta-application skill; when a spike resolves an open question after the
|
|
15
|
-
pipeline completed, close it with an amendment (templates/amendment.yaml
|
|
16
|
-
codecarto_amend), citing this report.
|
|
15
|
+
pipeline completed, close it with an amendment (templates/amendment.yaml, then
|
|
16
|
+
codecarto_amend on MCP or /codecarto-amend on Pi), citing this report.
|
|
17
17
|
|
|
18
18
|
Keep it honest: a spike that failed to answer its question is a valid result —
|
|
19
19
|
record what was tried and what blocked it.
|
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ Use this when your coding agent isn't Pi — Claude Code, Codex, opencode, Curso
|
|
|
92
92
|
|
|
93
93
|
> **30-second setup for Claude Code, Cursor, Codex, and Claude Desktop: see the [MCP quickstart](docs/mcp-quickstart.md).**
|
|
94
94
|
|
|
95
|
-
> **Teaching an agent to drive it:** call the `codecarto_guide` tool — the server returns the full drive loop, the phase-handoff contract, executor selection, and recovery patterns, with nothing to install. The same content ships as an installable skill at `agent-skill/codecartographer/` for agents that load skills from disk.
|
|
95
|
+
> **Teaching an agent to drive it:** call the `codecarto_guide` tool — the server returns the full drive loop, the phase-handoff contract, executor selection, and recovery patterns, with nothing to install. The same content ships as an installable skill at `agent-skill/codecartographer/` for agents that load skills from disk, and `/codecarto-guide [topic]` reads it into a Pi session.
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
98
|
npm install --global codecartographer-pi
|
|
@@ -137,7 +137,7 @@ Analysis turns repositories into reusable specifications. Synthesis runs the oth
|
|
|
137
137
|
library:
|
|
138
138
|
path: /absolute/path/to/codecarto-library
|
|
139
139
|
namespace: your-namespace # omit for a single-tenant library
|
|
140
|
-
publish_confirm: true
|
|
140
|
+
publish_confirm: true # Pi asks before writing; MCP refuses a publish that lacks confirm: true
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
2. Initialize a clean planning workspace and fill in its brief:
|
|
@@ -321,12 +321,16 @@ Beyond the slash commands, the Pi extension layers on:
|
|
|
321
321
|
| `/codecarto-validate [phase]` | Validate a phase output against completion criteria |
|
|
322
322
|
| `/codecarto-complete [phase]` | Validate and atomically apply the phase handoff, canonical status, closeout, and log entry |
|
|
323
323
|
| `/codecarto-skill <name>` | Run a post-pipeline skill once all phases are complete (or `broadside` any time, for the scout reading guide) |
|
|
324
|
+
| `/codecarto-list-skills` | List the installed post-pipeline skills and the ungated `broadside` reading guide, and say when the gated ones unlock |
|
|
325
|
+
| `/codecarto-guide [topic]` | Read the packaged agent guide — drive loop, handoff contract, executors, recovery, Broad-Side — into the session; tab-completes topics; needs no workspace |
|
|
324
326
|
| `/codecarto-broadside [action] [lenses…]` | Batch reconnaissance (Broad-Side). Actions: `submit`, `collect`, `status`, `models`. Prices the run and asks before spending; works with or without a workspace |
|
|
325
327
|
| `/codecarto-publish` | Publish the reimplementation spec to the configured library after reviewing an explicit confirmation preview |
|
|
326
328
|
| `/codecarto-library-init <path> [--namespace <name>]` | Create a library directory with marker and write the config — fixes the first-publish dead end |
|
|
327
329
|
| `/codecarto-config` | Show the effective merged configuration (global + workspace) and library marker status |
|
|
328
330
|
| `/codecarto-usage` | Cumulative + per-phase token usage |
|
|
329
331
|
| `/codecarto-dashboard [--narrate]` | Regenerate `.codecarto/dashboard.html`; `--narrate` for the LLM executive summary |
|
|
332
|
+
| `/codecarto-refresh-scaffold` | Refresh the framework-owned `.codecarto/` files (GUIDE.md, templates/, workflow/ pipelines and VALIDATE.md) from the packaged template after a confirmation that lists the exact file set; project state, config, findings outputs, scratch, closeouts, and `broadside/` are never touched |
|
|
333
|
+
| `/codecarto-amend <name>` | Apply a post-pipeline amendment from `scratch/amendments/<name>.yaml` after a confirmation that previews which open questions and post-pipeline items it closes; refused while the pipeline is incomplete |
|
|
330
334
|
|
|
331
335
|
### End-to-end auto mode (0.8.0+)
|
|
332
336
|
|
|
@@ -360,7 +364,7 @@ Implements MCP spec revision [`2025-11-25`](https://modelcontextprotocol.io/spec
|
|
|
360
364
|
| `codecarto_validate` | `/codecarto-validate` |
|
|
361
365
|
| `codecarto_complete` | `/codecarto-complete` |
|
|
362
366
|
| `codecarto_skill` | `/codecarto-skill` |
|
|
363
|
-
| `codecarto_list_skills` |
|
|
367
|
+
| `codecarto_list_skills` | `/codecarto-list-skills` |
|
|
364
368
|
| `codecarto_publish` | `/codecarto-publish` |
|
|
365
369
|
| `codecarto_library_init` | `/codecarto-library-init` |
|
|
366
370
|
| `codecarto_library_list` | MCP-only library listing |
|
|
@@ -368,9 +372,9 @@ Implements MCP spec revision [`2025-11-25`](https://modelcontextprotocol.io/spec
|
|
|
368
372
|
| `codecarto_config` | `/codecarto-config` |
|
|
369
373
|
| `codecarto_usage` | `/codecarto-usage` |
|
|
370
374
|
| `codecarto_dashboard` | `/codecarto-dashboard` |
|
|
371
|
-
| `codecarto_guide` |
|
|
372
|
-
| `codecarto_amend` |
|
|
373
|
-
| `codecarto_refresh_scaffold` |
|
|
375
|
+
| `codecarto_guide` | `/codecarto-guide` |
|
|
376
|
+
| `codecarto_amend` | `/codecarto-amend` (Pi previews the closures and asks first) |
|
|
377
|
+
| `codecarto_refresh_scaffold` | `/codecarto-refresh-scaffold` (Pi lists the file set and asks first) |
|
|
374
378
|
| `codecarto_broadside` | `/codecarto-broadside` |
|
|
375
379
|
|
|
376
380
|
Each workflow tool accepts an absolute `cwd` for the target repository. `codecarto_init` requires `force: true` to overwrite an existing `.codecarto/` (instead of Pi's interactive confirmation). The library tools accept an explicit absolute `library_path` or resolve `library.path` from `.codecarto/workflow/config.yaml` / `~/.codecarto/config.yaml`. `codecarto_library_reindex` and `codecarto_library_list` also report entries whose versions disagree about `source_repo` — the shape a slug collision left behind before v0.17.0's publish guard — and leave the repair manual, since splitting an entry changes paths the library format treats as ABI. The library schema is experimental and may break before v2.
|
|
@@ -117,7 +117,7 @@ A PARTIAL row's evidence must name what is missing and which `open_questions` or
|
|
|
117
117
|
- `codecarto_next` returns a prompt. Something still has to *do* the phase.
|
|
118
118
|
- Never hand-edit `workflow/status.yaml`, append `THREAD_LOG.md`, or write a second closeout. Propose through the handoff.
|
|
119
119
|
- Do not force phases out of DAG order unless the user asked.
|
|
120
|
-
- If `codecarto_status` reports a scaffold-staleness warning, refresh the workspace's framework-owned files before trusting anything written inside `.codecarto/`; a stale scaffold's `GUIDE.md` can contradict this contract.
|
|
120
|
+
- If `codecarto_status` reports a scaffold-staleness warning, refresh the workspace's framework-owned files (`codecarto_refresh_scaffold`; `/codecarto-refresh-scaffold` on the Pi extension) before trusting anything written inside `.codecarto/`; a stale scaffold's `GUIDE.md` can contradict this contract. The refresh never touches project state, findings outputs, or session directories.
|
|
121
121
|
- A delegated run that times out may still have written its artifact. Check for the file and validate before retrying.
|
|
122
122
|
- The drop-in `.codecarto/` template works without MCP, but the server is preferred: it owns atomic state updates, validation parsing, and the completion gate.
|
|
123
123
|
|
|
@@ -17,7 +17,7 @@ owner_notes: [] # 2-3 durable observations; appended to the phas
|
|
|
17
17
|
open_questions: [] # genuinely unknown, no later phase will close them
|
|
18
18
|
carry_forward: [] # deferred to a specific later phase in this pipeline
|
|
19
19
|
carry_forward_closures: [] # ids of carry_forward entries this phase resolved
|
|
20
|
-
open_question_closures: [] #
|
|
20
|
+
open_question_closures: [] # open questions this phase resolved, removed everywhere; bare id or {id, evidence}
|
|
21
21
|
post_pipeline: [] # work after the pipeline; every entry needs a stable id
|
|
22
22
|
decisions: [] # choices made beyond what the prompt specified; completion appends them to DECISIONS.md
|
|
23
23
|
proposed_conventions: [] # patterns proposed for promotion; completion stages them in CONVENTIONS.md
|
|
@@ -41,7 +41,7 @@ Omitted arrays default to empty. A malformed collection fails completion without
|
|
|
41
41
|
deferred_reason: Distinguishing them needs a runtime probe this phase cannot run.
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
`carry_forward` entries add `target_phase`:
|
|
44
|
+
`carry_forward` entries add `target_phase`, and optionally `derives_from`:
|
|
45
45
|
|
|
46
46
|
```yaml
|
|
47
47
|
- id: arch-CF2
|
|
@@ -49,10 +49,18 @@ Omitted arrays default to empty. A malformed collection fails completion without
|
|
|
49
49
|
target_phase: protocols
|
|
50
50
|
description: MCP endpoints listed by name only; schemas not extracted.
|
|
51
51
|
deferred_reason: Wire-format extraction is the protocols phase's rubric.
|
|
52
|
+
|
|
53
|
+
- id: mech-CF3
|
|
54
|
+
kind: defer-to-phase
|
|
55
|
+
target_phase: defect-scan-semantic
|
|
56
|
+
derives_from: q-logit-bias-root-cause # optional: the open question this is one candidate answer to
|
|
57
|
+
description: The client sends logit_bias as a map; the documented shape is an array.
|
|
52
58
|
```
|
|
53
59
|
|
|
54
60
|
Allowed `kind` values: `needs-runtime-test`, `needs-maintainer-decision`, `needs-spec-ruling`, `defer-to-phase`, `needs-fixture-capture`.
|
|
55
61
|
|
|
62
|
+
`derives_from` names an `open_questions` id. Fill it in the handoff that registers the question — a phase that routes a candidate answer onward usually writes both entries at once, which is the one moment both are in front of you. It is optional and additive: a handoff that omits it behaves exactly as before.
|
|
63
|
+
|
|
56
64
|
`proposed_conventions` entries (optional; omitted defaults to empty):
|
|
57
65
|
|
|
58
66
|
```yaml
|
|
@@ -81,6 +89,26 @@ Completion then removes the entry atomically. Resolving an open question works t
|
|
|
81
89
|
|
|
82
90
|
Re-deferring instead of closing means writing a fresh `carry_forward` entry naming a later `target_phase`.
|
|
83
91
|
|
|
92
|
+
### Closing a routed item does not settle the question it came from
|
|
93
|
+
|
|
94
|
+
Addressing what was routed to you is not the same as answering the question that produced it. Two rules make that enforceable:
|
|
95
|
+
|
|
96
|
+
- **A closure whose `derives_from` question is still open is refused.** If the item you are closing declares `derives_from: <question-id>`, that question is still in `status.yaml`, and this same handoff does not close it, completion refuses and names both ids. Either close the question here with the evidence that settles it, or leave the item routed and give the finding an unsettled action (`verify at runtime`) so it inherits the question's uncertainty. This rule is entirely opt-in — an entry without `derives_from` closes as it always has.
|
|
97
|
+
- **A `needs-runtime-test` question closes on runtime evidence.** Write the closure as an object and say where that evidence lives:
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
open_question_closures:
|
|
101
|
+
- q-loadconfig-ambiguity # bare id: still valid for any other kind
|
|
102
|
+
- id: q-logit-bias-root-cause
|
|
103
|
+
evidence: scratch/spikes/logit-bias.md — probe against llama-server b4321
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Non-empty `evidence` is required when the question's `kind` is `needs-runtime-test`, whether the closure is written as a bare id or as an object. It is checked for presence, not judged — a spike report or an observation against the running system is what belongs there, and another read of the same source is not. Questions of every other kind close on a bare id exactly as before.
|
|
107
|
+
|
|
108
|
+
The requirement is scoped to the scaffold that documents it. A workspace whose `workflow/scaffold-version.yaml` is 0.19.0 or newer has completion refuse such a closure; an older or unversioned scaffold — whose own templates never stated the rule — gets a non-gating `NOTE:` instead, so an in-flight run written against the older contract cannot be stopped by a rule it was never told. Refreshing the scaffold (`codecarto_refresh_scaffold` on MCP, `/codecarto-refresh-scaffold` on Pi) opts a workspace in.
|
|
109
|
+
|
|
110
|
+
Upstream coverage gaps travel the same way, without gating: the `Skipped scope` and `Known blind spots` bullets of every completed phase's `## Coverage and limits` section appear in your phase prompt's orchestrator duties. A finding of yours inside one of those gaps must either close it with cited new evidence or inherit its uncertainty.
|
|
111
|
+
|
|
84
112
|
## The failure this prevents
|
|
85
113
|
|
|
86
114
|
Before completion required a handoff, a phase could finish with empty state and no signal. A real seven-phase run documented five cross-phase routings in its report prose, wrote no handoffs, and completed all seven phases with `carry_forward: []` throughout. Every downstream phase's routed-item intake was empty. The findings survived only because each phase happened to re-read the previous phase's full markdown.
|
|
@@ -13,7 +13,7 @@ A CodeCartographer **library** is a directory of published reimplementation-spec
|
|
|
13
13
|
| Tool | Does | Notes |
|
|
14
14
|
|---|---|---|
|
|
15
15
|
| `codecarto_library_init` | Create the directory, write the marker, record `library.path` in user-global config | Idempotent; pass `namespace` to create a namespaced library |
|
|
16
|
-
| `codecarto_publish` | Publish a spec as a library entry | Required: `source_repo`, `headline`, and `spec` (inline) or `spec_path` (absolute). Content-hash idempotent: identical bytes update metadata in place, no version bump. `slug` derives from `source_repo` if omitted; namespaced libraries require `namespace` (or inherit via `cwd`). Provenance (`source_commit`, `source_branch`, `source_dirty`, `analyzed_at`, `pipeline`, `model_metadata`) is recorded; omitted generation fields default to `unknown` |
|
|
16
|
+
| `codecarto_publish` | Publish a spec as a library entry | Required: `source_repo`, `headline`, and `spec` (inline) or `spec_path` (absolute). Content-hash idempotent: identical bytes update metadata in place, no version bump. `slug` derives from `source_repo` if omitted; namespaced libraries require `namespace` (or inherit via `cwd`). Provenance (`source_commit`, `source_branch`, `source_dirty`, `analyzed_at`, `pipeline`, `model_metadata`) is recorded; omitted generation fields default to `unknown`. `confirm: true` acknowledges the `publish_confirm` gate (below) |
|
|
17
17
|
| `codecarto_library_list` | List entries | Filter by `namespace`, `tag`, `slug`, or `source_repo` |
|
|
18
18
|
| `codecarto_library_reindex` | Regenerate `index.yaml` + `INDEX.md` from filesystem state | For manual edits and index merge conflicts. Also reports entries whose versions disagree about `source_repo` (merged by a slug collision before publish refused cross-project appends; `codecarto_library_list` flags them too) — repair is manual, split the entry by hand |
|
|
19
19
|
|
|
@@ -25,7 +25,7 @@ The moment `reimplementation-spec` completes and validates is the publish moment
|
|
|
25
25
|
codecarto_publish cwd:<workspace repo> source_repo:<repo URL or path> headline:"<one line>" spec_path:<abs path to reimplementation-spec.md>
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Set `publish_confirm` in config if you want an explicit confirmation gate before writes.
|
|
28
|
+
Set `publish_confirm` in config if you want an explicit confirmation gate before writes. It means something on both executable surfaces, in the only way each can ask. The Pi extension shows a preview and asks interactively before `/codecarto-publish` writes. The MCP `codecarto_publish` tool has no one to ask, so when the key is set — in `~/.codecarto/config.yaml`, or in the workspace's `.codecarto/workflow/config.yaml` when `cwd` is passed — it refuses a call that lacks `confirm: true` and returns the preview instead: library, entry, whether this would be a new version or a metadata-only update, `source_repo`, headline, confidentiality. Nothing is written by the refusal. Show the preview, then re-invoke with the same arguments plus `confirm: true` to publish. The gate applies only when the key is actually set in a config file (`codecarto_library_init` writes `publish_confirm: true`, so a library initialized through the tool has it on); a host that never configured the key is not gated, and `publish_confirm: false` drops the gate.
|
|
29
29
|
|
|
30
30
|
## What this is not
|
|
31
31
|
|
|
@@ -39,7 +39,7 @@ If two reduced-scope attempts fail, the problem is usually scope, not the execut
|
|
|
39
39
|
|
|
40
40
|
- Split the reading. Use scoped pre-passes over individual subsystems, save the notes under `.codecarto/scratch/`, and give the retry those notes as evidence.
|
|
41
41
|
- Consider whether the pipeline variant is right. A repository too large for one `architecture` pass may want `architecture-only` first, reviewed, then a switch.
|
|
42
|
-
- Check for a scaffold-staleness warning in `codecarto_status`. A workspace whose framework-owned files predate the running version can carry instructions that contradict the current contract, which produces artifacts that fail validation for reasons the executor cannot see.
|
|
42
|
+
- Check for a scaffold-staleness warning in `codecarto_status`. A workspace whose framework-owned files predate the running version can carry instructions that contradict the current contract, which produces artifacts that fail validation for reasons the executor cannot see. `codecarto_refresh_scaffold` (`/codecarto-refresh-scaffold` on the Pi extension) refreshes those files without touching project state.
|
|
43
43
|
|
|
44
44
|
## What not to do
|
|
45
45
|
|
package/dist/core/amendment.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export type AmendmentResult = {
|
|
|
27
27
|
};
|
|
28
28
|
/** Same charset rule as phase ids: the slug becomes file names, so path shapes are refused. */
|
|
29
29
|
export declare function assertSafeAmendmentSlug(slug: string): void;
|
|
30
|
+
/**
|
|
31
|
+
* Slugs of the amendment files staged under scratch/amendments/, sorted.
|
|
32
|
+
* Discovery only — nothing here is validated; {@link loadAmendmentFile} does that.
|
|
33
|
+
*/
|
|
34
|
+
export declare function listAmendmentNames(workspaceDir: string): Promise<string[]>;
|
|
30
35
|
/**
|
|
31
36
|
* Load and validate one amendment file.
|
|
32
37
|
* @param name - the amendment slug, with or without a `.yaml` suffix.
|
package/dist/core/amendment.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// carry_forward_closures); an amendment is the post-pipeline counterpart, so
|
|
6
6
|
// spec-delta sessions, spikes, and maintainer rulings no longer end with
|
|
7
7
|
// "record for a later explicit amendment" that nothing can perform.
|
|
8
|
-
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
9
|
-
import { join } from "node:path";
|
|
8
|
+
import { appendFile, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
9
|
+
import { basename, join } from "node:path";
|
|
10
10
|
import { getNextEligiblePhase } from "./pipeline.js";
|
|
11
11
|
import { buildTerminalNextActions, ensureArray, normalizeStatus } from "./status.js";
|
|
12
12
|
import { dateOnly, newlineIfUnterminated, pathExists } from "./utils.js";
|
|
@@ -18,6 +18,25 @@ export function assertSafeAmendmentSlug(slug) {
|
|
|
18
18
|
throw new Error(`Invalid amendment name: ${slug}`);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Slugs of the amendment files staged under scratch/amendments/, sorted.
|
|
23
|
+
* Discovery only — nothing here is validated; {@link loadAmendmentFile} does that.
|
|
24
|
+
*/
|
|
25
|
+
export async function listAmendmentNames(workspaceDir) {
|
|
26
|
+
const amendmentsDir = join(workspaceDir, "scratch", "amendments");
|
|
27
|
+
if (!(await pathExists(amendmentsDir)))
|
|
28
|
+
return [];
|
|
29
|
+
try {
|
|
30
|
+
const entries = await readdir(amendmentsDir, { withFileTypes: true });
|
|
31
|
+
return entries
|
|
32
|
+
.filter((entry) => entry.isFile() && /\.ya?ml$/i.test(entry.name))
|
|
33
|
+
.map((entry) => basename(entry.name).replace(/\.ya?ml$/i, ""))
|
|
34
|
+
.sort();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
21
40
|
/**
|
|
22
41
|
* Load and validate one amendment file.
|
|
23
42
|
* @param name - the amendment slug, with or without a `.yaml` suffix.
|
|
@@ -24,4 +24,19 @@ export declare const CONVENTIONS_PENDING_HEADING = "## Pending proposals";
|
|
|
24
24
|
* @param content - the file content, or null to read from disk (null when the file is absent).
|
|
25
25
|
*/
|
|
26
26
|
export declare function countPendingProposals(workspaceDir: string, content?: string): Promise<number>;
|
|
27
|
+
/**
|
|
28
|
+
* First scaffold version whose handoff template documents the `{id, evidence}`
|
|
29
|
+
* closure shape and the runtime-evidence rule (#122, #186).
|
|
30
|
+
*
|
|
31
|
+
* A workspace scaffolded before it was never told that closing a
|
|
32
|
+
* `needs-runtime-test` question requires evidence, so refusing its completion
|
|
33
|
+
* would apply a rule its own templates do not carry — and could stop an
|
|
34
|
+
* in-flight `--auto` run on a handoff written against the older contract.
|
|
35
|
+
* Those workspaces get a warning instead; refreshing the scaffold
|
|
36
|
+
* (`codecarto_refresh_scaffold` on MCP, `/codecarto-refresh-scaffold` on Pi)
|
|
37
|
+
* opts them in. Same treatment Stage 2's findings pairing check uses.
|
|
38
|
+
*/
|
|
39
|
+
export declare const CLOSURE_EVIDENCE_GATE_SCAFFOLD_VERSION = "0.19.0";
|
|
40
|
+
/** Whether the runtime-evidence requirement refuses (current scaffold) or warns (older). */
|
|
41
|
+
export declare function closureEvidenceGateActive(scaffoldVersion: string | undefined | null): boolean;
|
|
27
42
|
export declare function completeValidatedPhase(cwd: string, validation: ValidationResult, sourceLabel: string): Promise<CompletionResult>;
|
package/dist/core/completion.js
CHANGED
|
@@ -2,7 +2,7 @@ import { appendFile, copyFile, mkdir, readFile, readdir, writeFile } from "node:
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getNextEligiblePhase, resolvePhase, validatePhaseOutput } from "./pipeline.js";
|
|
4
4
|
import { applyHandoff, autoAssignIds, buildTerminalNextActions, loadHandoffFile, normalizeStatus } from "./status.js";
|
|
5
|
-
import { dateOnly, newlineIfUnterminated, pathExists, uniqueStrings } from "./utils.js";
|
|
5
|
+
import { compareDottedVersions, dateOnly, newlineIfUnterminated, pathExists, uniqueStrings } from "./utils.js";
|
|
6
6
|
import { getWorkspaceState, updateStatusAtomically } from "./workspace.js";
|
|
7
7
|
/**
|
|
8
8
|
* The Markdown a reader sees: content inside `<!-- -->` blocks removed by a
|
|
@@ -233,6 +233,26 @@ async function writeCompletionArtifacts(workspaceDir, phaseId, validation, times
|
|
|
233
233
|
const { totalPending: totalPendingProposals } = await stageProposedConventions(workspaceDir, phaseId, timestamp, handoff?.proposed_conventions ?? []);
|
|
234
234
|
return { closeoutPath: `.codecarto/closeouts/${closeoutFile}`, decisionsAppended, totalPendingProposals };
|
|
235
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* First scaffold version whose handoff template documents the `{id, evidence}`
|
|
238
|
+
* closure shape and the runtime-evidence rule (#122, #186).
|
|
239
|
+
*
|
|
240
|
+
* A workspace scaffolded before it was never told that closing a
|
|
241
|
+
* `needs-runtime-test` question requires evidence, so refusing its completion
|
|
242
|
+
* would apply a rule its own templates do not carry — and could stop an
|
|
243
|
+
* in-flight `--auto` run on a handoff written against the older contract.
|
|
244
|
+
* Those workspaces get a warning instead; refreshing the scaffold
|
|
245
|
+
* (`codecarto_refresh_scaffold` on MCP, `/codecarto-refresh-scaffold` on Pi)
|
|
246
|
+
* opts them in. Same treatment Stage 2's findings pairing check uses.
|
|
247
|
+
*/
|
|
248
|
+
export const CLOSURE_EVIDENCE_GATE_SCAFFOLD_VERSION = "0.19.0";
|
|
249
|
+
/** Whether the runtime-evidence requirement refuses (current scaffold) or warns (older). */
|
|
250
|
+
export function closureEvidenceGateActive(scaffoldVersion) {
|
|
251
|
+
if (!scaffoldVersion)
|
|
252
|
+
return false;
|
|
253
|
+
const comparison = compareDottedVersions(scaffoldVersion, CLOSURE_EVIDENCE_GATE_SCAFFOLD_VERSION);
|
|
254
|
+
return comparison !== null && comparison >= 0;
|
|
255
|
+
}
|
|
236
256
|
export async function completeValidatedPhase(cwd, validation, sourceLabel) {
|
|
237
257
|
const initialState = await getWorkspaceState(cwd);
|
|
238
258
|
if (!initialState)
|
|
@@ -254,6 +274,7 @@ export async function completeValidatedPhase(cwd, validation, sourceLabel) {
|
|
|
254
274
|
+ `plus closeout_summary and optional closeout_content. Then re-run completion.`);
|
|
255
275
|
}
|
|
256
276
|
}
|
|
277
|
+
const warnings = [];
|
|
257
278
|
if (handoff) {
|
|
258
279
|
const activePhases = new Set(initialState.pipeline.phase_order);
|
|
259
280
|
const sourceIndex = initialState.pipeline.phase_order.indexOf(validation.phaseId);
|
|
@@ -267,14 +288,70 @@ export async function completeValidatedPhase(cwd, validation, sourceLabel) {
|
|
|
267
288
|
if (!entry.id?.trim())
|
|
268
289
|
throw new Error("Invalid handoff: post_pipeline entries require a canonical id");
|
|
269
290
|
}
|
|
291
|
+
// Closure integrity, gating (#122, #186). Both checks are deterministic
|
|
292
|
+
// reads of ids the model wrote itself, so neither can wedge an --auto run
|
|
293
|
+
// on a heuristic; both sit here, before the lock, alongside the
|
|
294
|
+
// target_phase check, so a refusal mutates nothing.
|
|
295
|
+
const questionsById = new Map();
|
|
296
|
+
const derivesFromById = new Map();
|
|
297
|
+
for (const phaseState of Object.values(initialState.status.phases)) {
|
|
298
|
+
for (const entry of phaseState.open_questions ?? []) {
|
|
299
|
+
if (entry.id)
|
|
300
|
+
questionsById.set(entry.id, entry);
|
|
301
|
+
}
|
|
302
|
+
for (const entry of phaseState.carry_forward ?? []) {
|
|
303
|
+
if (entry.id && entry.derives_from)
|
|
304
|
+
derivesFromById.set(entry.id, entry.derives_from);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const closingQuestionIds = new Set(handoff.open_question_closures.map((closure) => closure.id).filter(Boolean));
|
|
308
|
+
// D1: a routed item that declares `derives_from` is one candidate answer
|
|
309
|
+
// to that question. Closing it while the question stands is exactly the
|
|
310
|
+
// contradiction #122 reported — the routed candidate shipped as settled
|
|
311
|
+
// while the question that said "source alone cannot determine which" was
|
|
312
|
+
// still open. A derives_from naming an id that no longer exists is fine:
|
|
313
|
+
// the question was already resolved.
|
|
314
|
+
for (const closureId of handoff.carry_forward_closures) {
|
|
315
|
+
const questionId = derivesFromById.get(closureId);
|
|
316
|
+
if (!questionId || !questionsById.has(questionId))
|
|
317
|
+
continue;
|
|
318
|
+
if (closingQuestionIds.has(questionId))
|
|
319
|
+
continue;
|
|
320
|
+
throw new Error(`Refusing to complete ${validation.phaseId}: the handoff closes carry_forward ${closureId}, which derives_from open question ${questionId} — and ${questionId} is still unresolved and is not in this handoff's open_question_closures. `
|
|
321
|
+
+ `A routed item is one candidate answer to the question it came from; closing it does not settle the question. `
|
|
322
|
+
+ `Either close ${questionId} in this same handoff with the evidence that settles it, or leave ${closureId} routed and give the finding an unsettled action ("verify at runtime") instead.`);
|
|
323
|
+
}
|
|
324
|
+
// D3: a `needs-runtime-test` question closes on runtime evidence, not on
|
|
325
|
+
// another source read. Requiring the evidence string to be non-empty is
|
|
326
|
+
// the whole gate — judging what it says stays prose guidance.
|
|
327
|
+
//
|
|
328
|
+
// Unlike D1, this one can fire on a handoff that uses none of the new
|
|
329
|
+
// fields: a bare-string closure was the only shape before this release.
|
|
330
|
+
// Gating it unconditionally would apply a rule to workspaces whose own
|
|
331
|
+
// templates never state it, so it is version-gated exactly like Stage
|
|
332
|
+
// 2's pairing check — refuse on a scaffold that documents the rule, warn
|
|
333
|
+
// on one that predates it.
|
|
334
|
+
const evidenceGateActive = closureEvidenceGateActive(initialState.scaffoldVersion);
|
|
335
|
+
for (const closure of handoff.open_question_closures) {
|
|
336
|
+
if (questionsById.get(closure.id)?.kind !== "needs-runtime-test")
|
|
337
|
+
continue;
|
|
338
|
+
if (closure.evidence?.trim())
|
|
339
|
+
continue;
|
|
340
|
+
const detail = `open_question_closures closes ${closure.id}, whose kind is needs-runtime-test, without evidence. `
|
|
341
|
+
+ `A runtime question closes on runtime evidence — a spike report or an observation against the running system — not on a source read. `
|
|
342
|
+
+ `Write the closure as an object: { id: ${closure.id}, evidence: <where that evidence lives> }. If you do not have it, leave the question open.`;
|
|
343
|
+
if (evidenceGateActive)
|
|
344
|
+
throw new Error(`Refusing to complete ${validation.phaseId}: ${detail}`);
|
|
345
|
+
warnings.push(`${detail} Warning only: this workspace's scaffold predates the requirement — refresh it `
|
|
346
|
+
+ `(codecarto_refresh_scaffold on MCP, /codecarto-refresh-scaffold on Pi) to make this gating.`);
|
|
347
|
+
}
|
|
270
348
|
}
|
|
271
349
|
// Closure integrity (#122, warning only): a handoff can close a carry-forward
|
|
272
350
|
// or open question the report never addressed — "closed in the handoff,
|
|
273
351
|
// resolved nowhere." The id of every claimed closure should appear somewhere
|
|
274
352
|
// in the primary output that claims to resolve it.
|
|
275
|
-
const warnings = [];
|
|
276
353
|
if (handoff && validation.outputPath) {
|
|
277
|
-
const closures = [...handoff.carry_forward_closures, ...handoff.open_question_closures].filter((id) => id?.trim());
|
|
354
|
+
const closures = [...handoff.carry_forward_closures, ...handoff.open_question_closures.map((closure) => closure.id)].filter((id) => id?.trim());
|
|
278
355
|
if (closures.length > 0) {
|
|
279
356
|
const output = await readFile(validation.outputPath, "utf8").catch(() => "");
|
|
280
357
|
const unmentioned = closures.filter((id) => !output.includes(id));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { WorkspaceState } from "./types.ts";
|
|
2
|
+
/** The section heading whose bullets this module reads. */
|
|
3
|
+
export declare const COVERAGE_SECTION_HEADING = "Coverage and limits";
|
|
4
|
+
/** The ledger's five fixed bullets, verbatim values with the label stripped. */
|
|
5
|
+
export type CoverageLedger = {
|
|
6
|
+
inspected_scope: string;
|
|
7
|
+
skipped_scope: string;
|
|
8
|
+
evidence_basis: string;
|
|
9
|
+
known_blind_spots: string;
|
|
10
|
+
coverage_disposition: string;
|
|
11
|
+
};
|
|
12
|
+
/** One declared gap from one completed phase's ledger. */
|
|
13
|
+
export type CoverageGap = {
|
|
14
|
+
/** The phase that declared it. */
|
|
15
|
+
phaseId: string;
|
|
16
|
+
/** Which bullet it came from: "skipped scope" or "known blind spots". */
|
|
17
|
+
label: string;
|
|
18
|
+
/** The bullet's text, sub-bullets folded onto one line. */
|
|
19
|
+
detail: string;
|
|
20
|
+
/** `.codecarto/`-relative path of the output the ledger was read from. */
|
|
21
|
+
output: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Read a phase output's `## Coverage and limits` ledger.
|
|
25
|
+
*
|
|
26
|
+
* Returns null when the document has no such section. A bullet the author left
|
|
27
|
+
* blank comes back as an empty string, as does a label the section omits, so a
|
|
28
|
+
* caller never has to distinguish "absent" from "empty" — both mean nothing to
|
|
29
|
+
* carry forward.
|
|
30
|
+
*
|
|
31
|
+
* Sub-bullets and wrapped continuation lines under a label belong to that
|
|
32
|
+
* label: a real report writes its blind spots as a nested list, and dropping
|
|
33
|
+
* them would silence exactly the case this exists for. They fold onto one line
|
|
34
|
+
* (sub-bullets joined with "; ") because the consumer is a prompt bullet.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseCoverageAndLimits(content: string): CoverageLedger | null;
|
|
37
|
+
/**
|
|
38
|
+
* Every declared gap from every completed phase whose primary output exists.
|
|
39
|
+
*
|
|
40
|
+
* Walks `phase_order` so the list is deterministic and reads upstream-first.
|
|
41
|
+
* Only `Skipped scope` and `Known blind spots` are collected: those are the
|
|
42
|
+
* two bullets that bind a later phase's claims. Unreadable or unparsable
|
|
43
|
+
* outputs contribute nothing.
|
|
44
|
+
*/
|
|
45
|
+
export declare function collectCoverageGaps(state: WorkspaceState): Promise<CoverageGap[]>;
|