codecartographer-pi 0.10.0 → 0.12.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 +40 -18
- package/.codecarto/README.md +3 -0
- package/.codecarto/findings/goal-synthesis/README.md +3 -0
- package/.codecarto/findings/goal-synthesis-finalize/SKILL.md +30 -0
- package/.codecarto/findings/goal-synthesis-propose/SKILL.md +24 -0
- package/.codecarto/findings/porting/SKILL.md +7 -0
- package/.codecarto/findings/reimplementation-spec/SKILL.md +10 -0
- package/.codecarto/findings/spec-merge/README.md +3 -0
- package/.codecarto/findings/spec-merge/SKILL.md +23 -0
- package/.codecarto/findings/vision-capture/README.md +3 -0
- package/.codecarto/findings/vision-capture/SKILL.md +26 -0
- package/.codecarto/inputs/vision.md +11 -0
- package/.codecarto/templates/architecture-map.md +9 -0
- package/.codecarto/templates/behavioral-contracts.md +9 -0
- package/.codecarto/templates/defect-report.md +9 -0
- package/.codecarto/templates/mechanical-defects.md +9 -0
- package/.codecarto/templates/merged-spec.md +58 -0
- package/.codecarto/templates/phase-checkpoint.md +41 -0
- package/.codecarto/templates/phase-handoff.yaml +22 -0
- package/.codecarto/templates/project-plan.md +70 -0
- package/.codecarto/templates/proposal.md +43 -0
- package/.codecarto/templates/protocols-and-state.md +9 -0
- package/.codecarto/templates/reimplementation-spec-opinionated.md +10 -0
- package/.codecarto/templates/reimplementation-spec.md +10 -0
- package/.codecarto/templates/reverse-engineering-bundle.md +29 -3
- package/.codecarto/templates/semantic-defects.md +9 -0
- package/.codecarto/templates/vision.md +63 -0
- package/.codecarto/workflow/pipeline-architecture-only.yaml +1 -0
- package/.codecarto/workflow/pipeline-defect-scan.yaml +2 -0
- package/.codecarto/workflow/pipeline-full-with-audit.yaml +8 -3
- package/.codecarto/workflow/pipeline-full-with-deep-audit.yaml +9 -5
- package/.codecarto/workflow/pipeline-lite.yaml +3 -0
- package/.codecarto/workflow/pipeline-synthesis.yaml +105 -0
- package/.codecarto/workflow/pipeline.yaml +7 -3
- package/.codecarto/workflow/status.yaml +2 -0
- package/README.md +89 -7
- package/dist/core/completion.d.ts +6 -0
- package/dist/core/completion.js +127 -0
- package/dist/core/dashboard.js +37 -7
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/pipeline.js +1 -0
- package/dist/core/prompts.d.ts +9 -3
- package/dist/core/prompts.js +43 -26
- package/dist/core/status.d.ts +7 -1
- package/dist/core/status.js +187 -2
- package/dist/core/synthesis.d.ts +31 -0
- package/dist/core/synthesis.js +140 -0
- package/dist/core/types.d.ts +29 -1
- package/dist/core/usage.d.ts +11 -0
- package/dist/core/usage.js +64 -46
- package/dist/core/workspace.d.ts +3 -1
- package/dist/core/workspace.js +39 -3
- package/dist/core/yaml.js +24 -0
- package/dist/extensions/codecarto/agent-rewriter.js +0 -1
- package/dist/extensions/codecarto/agent-runner.d.ts +19 -0
- package/dist/extensions/codecarto/agent-runner.js +68 -6
- package/dist/extensions/codecarto/agent-state.d.ts +3 -0
- package/dist/extensions/codecarto/agent-state.js +2 -0
- package/dist/extensions/codecarto/agent-summary.d.ts +5 -0
- package/dist/extensions/codecarto/agent-summary.js +9 -0
- package/dist/extensions/codecarto/agent-widget.js +6 -0
- package/dist/extensions/codecarto/auto-runner.d.ts +3 -1
- package/dist/extensions/codecarto/auto-runner.js +33 -69
- package/dist/extensions/codecarto/dashboard-narrator.js +0 -1
- package/dist/extensions/codecarto/index.d.ts +1 -1
- package/dist/extensions/codecarto/index.js +153 -12
- package/dist/extensions/codecarto/phase-compaction.d.ts +11 -0
- package/dist/extensions/codecarto/phase-compaction.js +115 -0
- package/dist/mcp-server/server.js +24 -68
- package/package.json +4 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
project_name: ""
|
|
2
|
+
schema_version: 1
|
|
2
3
|
# source_location is defined in the active pipeline YAML. Do not duplicate it here.
|
|
3
4
|
pipeline: workflow/pipeline-full-with-deep-audit.yaml
|
|
4
5
|
# ^^^ To switch pipelines, change the line above AND adjust the phases below to match:
|
|
@@ -62,3 +63,4 @@ phases:
|
|
|
62
63
|
carry_forward: []
|
|
63
64
|
next_actions:
|
|
64
65
|
- Begin architecture phase by reading the repository and producing findings/architecture/architecture-map.md
|
|
66
|
+
post_pipeline: []
|
package/README.md
CHANGED
|
@@ -32,8 +32,11 @@
|
|
|
32
32
|
| **HTML dashboard** — single-file aggregate of progress, links, usage, narrative | `.codecarto/dashboard.html` |
|
|
33
33
|
| **Per-phase token tracking** | `/codecarto-usage` |
|
|
34
34
|
| **Opt-in LLM steering** of the next phase's seed prompt | `/codecarto-next --llm-steer` |
|
|
35
|
+
| **Forward synthesis** — vision + confirmed library specs → provenance-backed project plan | `pipeline-synthesis.yaml` |
|
|
35
36
|
|
|
36
|
-
> **Forward-flow synthesis is
|
|
37
|
+
> **Forward-flow synthesis is available on the development branch.** Publish completed reimplementation specs from Pi or MCP, then run the `synthesis` pipeline to turn a product vision and explicitly confirmed library entries into a conflict-aware `project-plan.md` with a decision-level provenance ledger.
|
|
38
|
+
|
|
39
|
+
OpenAI Build Week reviewers: see the [new-vs-existing scope and one-command demo](docs/build-week-2026.md).
|
|
37
40
|
|
|
38
41
|
---
|
|
39
42
|
|
|
@@ -93,7 +96,47 @@ cp -r /path/to/CodeCartographer/.codecarto /path/to/your-repo/
|
|
|
93
96
|
|
|
94
97
|
Then in the LLM session: `Read .codecarto/GUIDE.md and begin the analysis.`
|
|
95
98
|
|
|
96
|
-
> **Limitation.** Drop-in mode runs the analysis pipeline fully, but library + synthesis workflows require executable code
|
|
99
|
+
> **Limitation.** Drop-in mode runs the analysis pipeline fully, but library + synthesis workflows require executable code through Pi or MCP.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Forward synthesis quickstart
|
|
104
|
+
|
|
105
|
+
Analysis turns repositories into reusable specifications. Synthesis runs the other direction: it combines a raw product vision with human-confirmed specifications and produces an implementation-ready plan without losing provenance.
|
|
106
|
+
|
|
107
|
+
1. Configure the library that contains specs published with `/codecarto-publish` or the MCP `codecarto_publish` tool:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
# ~/.codecarto/config.yaml or .codecarto/workflow/config.yaml
|
|
111
|
+
library:
|
|
112
|
+
path: /absolute/path/to/codecarto-library
|
|
113
|
+
namespace: your-namespace # omit for a single-tenant library
|
|
114
|
+
publish_confirm: true
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
2. Initialize a clean planning workspace and fill in its brief:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
/codecarto-init synthesis
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Edit `.codecarto/inputs/vision.md` with the audience, problem, desired outcome, constraints, and non-goals.
|
|
124
|
+
|
|
125
|
+
3. Run until CodeCartographer creates the candidate proposal:
|
|
126
|
+
|
|
127
|
+
```text
|
|
128
|
+
/codecarto-next --auto
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The run intentionally stops before merging. Review `.codecarto/findings/goal-synthesis/proposal.md` and change one or more candidate boxes from `[ ]` to `[x]`.
|
|
132
|
+
|
|
133
|
+
4. Resume:
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
/codecarto-next --auto
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The final `.codecarto/findings/goal-synthesis/project-plan.md` contains product scope, architecture, work packages, acceptance gates, an unresolved-conflict register, and a provenance ledger mapping every load-bearing decision back to the vision or a confirmed specification. Runtime preflight checks prevent merging or finalization before explicit human confirmation.
|
|
97
140
|
|
|
98
141
|
---
|
|
99
142
|
|
|
@@ -114,6 +157,39 @@ For multi-session work, every new session reads `.codecarto/GUIDE.md` (or the li
|
|
|
114
157
|
|
|
115
158
|
---
|
|
116
159
|
|
|
160
|
+
## Progressive distillation and context resilience
|
|
161
|
+
|
|
162
|
+
CodeCartographer is a progressive, evidence-tagged distillation of a codebase. It does not ask one context window to retain the entire investigation. Instead, each phase turns a large body of source evidence into a smaller, more task-specific artifact that the next phase can read:
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
source code
|
|
166
|
+
→ architecture map
|
|
167
|
+
→ behavioral contracts + protocols + defect findings
|
|
168
|
+
→ porting bundle
|
|
169
|
+
→ reimplementation spec
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
This is deliberate distillation, not incidental chat summarization. Each artifact follows a template, preserves evidence levels and known unknowns, and must pass validation before it becomes an input to downstream phases.
|
|
173
|
+
|
|
174
|
+
### What happens when conversation context is compacted?
|
|
175
|
+
|
|
176
|
+
The filesystem, not the conversation, is the durable memory of a run:
|
|
177
|
+
|
|
178
|
+
- Each phase gets a fresh context window. In the Pi extension it runs as an isolated phase sub-agent; MCP and drop-in hosts should use the same one-session-per-phase pattern.
|
|
179
|
+
- Completed findings live under `.codecarto/findings/`. Later phases re-read the specific upstream artifacts declared by the active pipeline instead of relying on conversational recall.
|
|
180
|
+
- `workflow/status.yaml` records progress, terminal `open_questions`, in-pipeline `carry_forward`, and a separate `post_pipeline` backlog for optional spikes, amendments, deltas, decisions, and reruns after completion. Phase agents propose changes in `.codecarto/scratch/handoffs/<phase>.yaml`; completion validates and applies them under a lock with host timestamps, one canonical closeout, and an idempotent `THREAD_LOG.md` entry.
|
|
181
|
+
- Pi phase transcripts are file-backed and remain available through `/resume`, `/tree`, and `/export`, even when the active model context has been compacted.
|
|
182
|
+
- For isolated Pi phase sessions, compaction uses a phase-aware continuation summary that explicitly preserves evidence, files inspected, output progress, open questions, and validation gaps. The resulting summary is also checkpointed atomically at `.codecarto/scratch/checkpoints/<phase>.md`.
|
|
183
|
+
- Pi records successful, failed, and aborted compactions plus their trigger (`threshold`, `overflow`, or `manual`) in local usage data and exposes the totals in the widget, `/codecarto-usage`, completion summaries, and dashboard.
|
|
184
|
+
|
|
185
|
+
As a result, compaction—or even replacement—of the orchestrator session does not erase pipeline progress. A new session can reconstruct the relevant state from disk and continue.
|
|
186
|
+
|
|
187
|
+
The remaining limit is **within a single oversized phase**. Even Pi's phase-aware summary is still a lossy distillation, and MCP/drop-in compaction remains entirely host-controlled. Phase instructions therefore prioritize targeted reads, durable checkpoints, and explicit coverage accounting; if full coverage will not fit, the phase records `PARTIAL` validation and places unresolved work in `open_questions` or `carry_forward`. Cross-phase context loss is largely designed out; intra-phase context pressure is observed and bounded rather than hidden.
|
|
188
|
+
|
|
189
|
+
The porting bundle is the final intentional compression boundary. It carries a source index, load-bearing invariants, defect dispositions, and deep-read triggers. `reimplementation-spec` reads that bundle by default and opens lower-level reports only for a named gap, conflict, missing acceptance detail, or defect rationale.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
117
193
|
## Phases produce these artifacts
|
|
118
194
|
|
|
119
195
|
| Artifact | Description |
|
|
@@ -142,6 +218,7 @@ The default is a 7-phase run that splits the defect scan into a mechanical early
|
|
|
142
218
|
| **Defect scan** | 2 | Maintenance audit to surface latent problems |
|
|
143
219
|
| **Lite** | 3 | You need to understand behavior without porting plans |
|
|
144
220
|
| **Architecture only** | 1 | Quick structural overview |
|
|
221
|
+
| **Synthesis** | 4 | Turn a product vision and confirmed library specifications into a provenance-backed implementation plan |
|
|
145
222
|
|
|
146
223
|
Set the active pipeline by editing `workflow/status.yaml`'s `pipeline:` field, or pass it as the argument to `/codecarto-init`.
|
|
147
224
|
|
|
@@ -155,6 +232,7 @@ Set the active pipeline by editing `workflow/status.yaml`'s `pipeline:` field, o
|
|
|
155
232
|
| Defect scan | `workflow/pipeline-defect-scan.yaml` |
|
|
156
233
|
| Lite | `workflow/pipeline-lite.yaml` |
|
|
157
234
|
| Architecture only | `workflow/pipeline-architecture-only.yaml` |
|
|
235
|
+
| Synthesis | `workflow/pipeline-synthesis.yaml` |
|
|
158
236
|
|
|
159
237
|
---
|
|
160
238
|
|
|
@@ -164,7 +242,7 @@ Every state change re-renders `.codecarto/dashboard.html` — a self-contained s
|
|
|
164
242
|
|
|
165
243
|
- Pipeline progress strip with per-phase status badges
|
|
166
244
|
- Per-phase cards with output links, open questions, carry-forward routing, owner notes, last-run usage
|
|
167
|
-
- Aggregate token
|
|
245
|
+
- Aggregate token and compaction telemetry + per-phase breakdown
|
|
168
246
|
- Activity timeline with session-file links
|
|
169
247
|
- Open questions roll-up grouped by source phase
|
|
170
248
|
- Closeouts list (reverse-chronological) with relative-path links
|
|
@@ -191,25 +269,29 @@ Beyond the slash commands, the Pi extension layers on:
|
|
|
191
269
|
|
|
192
270
|
**File-backed phase sessions.** Phase transcripts persist to the same Pi session directory the orchestrator uses, so `/resume`, `/tree`, and `/export` browse them as first-class sessions. Each appears as `CodeCartographer phase: <id>` with lineage back to the orchestrator's session.
|
|
193
271
|
|
|
272
|
+
**Phase-aware compaction and checkpoints.** Only isolated sessions named `CodeCartographer phase: <id>` receive the specialized compaction prompt. It preserves the phase goal, evidence, inspected files, output progress, open questions, and validation gaps, then writes the resulting summary to `.codecarto/scratch/checkpoints/<phase>.md`. Orchestrator and unrelated Pi sessions retain normal host compaction.
|
|
273
|
+
|
|
194
274
|
**Phase-completion summary in the orchestrator transcript.** When a phase finishes, a Markdown closeout block is appended to the orchestrator's session via `pi.sendMessage(...)`. Visible in the TUI scrollback; available to the orchestrator's LLM as context on your next message. No auto-trigger — you stay in control.
|
|
195
275
|
|
|
196
276
|
**Opt-in LLM-steered seed prompts.** Set `orchestrator.llm_steer_next_phase: true` in `.codecarto/workflow/config.yaml` (or pass `--llm-steer` per invocation), and the orchestrator's LLM rewrites the next phase's seed prompt to highlight relevant prior findings. Off by default — extra orchestrator-side tokens, opt-in. The rewritten prompt is injected into the orchestrator transcript so you can audit what the rewriter chose to emphasize.
|
|
197
277
|
|
|
198
|
-
**Per-phase usage tracking.** Each phase run is appended to `.codecarto/workflow/.usage.local.yaml`. `/codecarto-usage` reports cumulative + per-phase totals.
|
|
278
|
+
**Per-phase usage tracking.** Each phase run is appended to `.codecarto/workflow/.usage.local.yaml`. `/codecarto-usage` reports cumulative + per-phase token, runtime, tool-use, and compaction totals, including threshold/overflow/manual triggers and successful/failed/aborted outcomes.
|
|
199
279
|
|
|
200
|
-
**Tool interception.** `bash` is blocked outright; `edit` and `write` are confined to `.codecarto
|
|
280
|
+
**Tool interception.** `bash` is blocked outright; `edit` and `write` are confined to `.codecarto/`, plus the configured, marker-validated CodeCartographer library when one is configured. Same rules apply to phase sub-agents.
|
|
201
281
|
|
|
202
282
|
### Slash commands
|
|
203
283
|
|
|
204
284
|
| Command | Purpose |
|
|
205
285
|
|---|---|
|
|
206
286
|
| `/codecarto-init [variant]` | Copy `.codecarto/` into the current repository, select pipeline variant |
|
|
287
|
+
| `/codecarto-open` | Activate an existing `.codecarto/` workspace in a new Pi session without resetting durable state |
|
|
207
288
|
| `/codecarto-status` | Current phase, progress, open questions |
|
|
208
289
|
| `/codecarto-next [--auto [--strict]] [--llm-steer \| --no-llm-steer]` | Spawn the next eligible phase as a sub-agent. `--auto` walks the full pipeline end-to-end (auto-validate + auto-complete + advance); `--strict` flips the `PASS WITH GAPS` rule from "advance" to "pause". |
|
|
209
290
|
| `/codecarto-phase <id>` | Force a specific phase, even out of pipeline order |
|
|
210
291
|
| `/codecarto-validate [phase]` | Validate a phase output against completion criteria |
|
|
211
|
-
| `/codecarto-complete [phase]` |
|
|
292
|
+
| `/codecarto-complete [phase]` | Validate and atomically apply the phase handoff, canonical status, closeout, and log entry |
|
|
212
293
|
| `/codecarto-skill <name>` | Run a post-pipeline skill once all phases are complete |
|
|
294
|
+
| `/codecarto-publish` | Publish the reimplementation spec to the configured library after reviewing an explicit confirmation preview |
|
|
213
295
|
| `/codecarto-usage` | Cumulative + per-phase token usage |
|
|
214
296
|
| `/codecarto-dashboard [--narrate]` | Regenerate `.codecarto/dashboard.html`; `--narrate` for the LLM executive summary |
|
|
215
297
|
|
|
@@ -391,7 +473,7 @@ If you're testing a new model, start with `pipeline-architecture-only.yaml` on a
|
|
|
391
473
|
protocols/ # Event streams, state machines, persistence formats.
|
|
392
474
|
porting/ # Reverse-engineering synthesis bundle.
|
|
393
475
|
reimplementation-spec/ # Language-agnostic build spec.
|
|
394
|
-
scratch/ # Disposable
|
|
476
|
+
scratch/ # Disposable notes plus checkpoints and structured phase handoffs.
|
|
395
477
|
templates/ # Output structure templates.
|
|
396
478
|
workflow/ # Pipeline definitions, status, validation, config.
|
|
397
479
|
closeouts/ # Per-session closeout files.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ValidationResult, WorkspaceState } from "./types.ts";
|
|
2
|
+
export type CompletionResult = {
|
|
3
|
+
updatedState: WorkspaceState;
|
|
4
|
+
closeoutNotice?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function completeValidatedPhase(cwd: string, validation: ValidationResult, sourceLabel: string): Promise<CompletionResult>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { appendFile, copyFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getNextEligiblePhase, resolvePhase } from "./pipeline.js";
|
|
4
|
+
import { applyHandoff, autoAssignIds, loadHandoffFile, normalizeStatus } from "./status.js";
|
|
5
|
+
import { dateOnly, pathExists, uniqueStrings } from "./utils.js";
|
|
6
|
+
import { getWorkspaceState, updateStatusAtomically } from "./workspace.js";
|
|
7
|
+
function escapeRegExp(value) {
|
|
8
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9
|
+
}
|
|
10
|
+
async function canonicalCloseoutFile(workspaceDir, phaseId, timestamp) {
|
|
11
|
+
const closeoutsDir = join(workspaceDir, "closeouts");
|
|
12
|
+
await mkdir(closeoutsDir, { recursive: true });
|
|
13
|
+
const pattern = new RegExp(`^\\d{4}-\\d{2}-\\d{2}-${escapeRegExp(phaseId)}\\.md$`);
|
|
14
|
+
const existing = (await readdir(closeoutsDir)).filter((name) => pattern.test(name)).sort();
|
|
15
|
+
return existing.at(-1) ?? `${dateOnly(timestamp)}-${phaseId}.md`;
|
|
16
|
+
}
|
|
17
|
+
async function writeCompletionArtifacts(workspaceDir, phaseId, validation, timestamp, handoff) {
|
|
18
|
+
const closeoutFile = await canonicalCloseoutFile(workspaceDir, phaseId, timestamp);
|
|
19
|
+
const closeoutPath = join(workspaceDir, "closeouts", closeoutFile);
|
|
20
|
+
const suppliedContent = handoff?.closeout_content?.trim();
|
|
21
|
+
if (suppliedContent) {
|
|
22
|
+
const decisions = handoff?.decisions ?? [];
|
|
23
|
+
const decisionsSection = decisions.length > 0
|
|
24
|
+
? `\n\n## Decisions Beyond Prompt\n\n${decisions.map((decision) => `- ${decision}`).join("\n")}`
|
|
25
|
+
: "";
|
|
26
|
+
await writeFile(closeoutPath, `${suppliedContent}${decisionsSection}\n`, "utf8");
|
|
27
|
+
}
|
|
28
|
+
else if (!(await pathExists(closeoutPath))) {
|
|
29
|
+
const templatePath = join(workspaceDir, "templates", "closeout-template.md");
|
|
30
|
+
if (await pathExists(templatePath))
|
|
31
|
+
await copyFile(templatePath, closeoutPath);
|
|
32
|
+
}
|
|
33
|
+
const summary = handoff?.closeout_summary?.trim() || `Validation: ${validation.overall}`;
|
|
34
|
+
const entry = `- ${dateOnly(timestamp)} — ${phaseId} — ${summary} — [closeout](closeouts/${closeoutFile})`;
|
|
35
|
+
const threadLogPath = join(workspaceDir, "THREAD_LOG.md");
|
|
36
|
+
let current = "";
|
|
37
|
+
try {
|
|
38
|
+
current = await readFile(threadLogPath, "utf8");
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Created below when absent.
|
|
42
|
+
}
|
|
43
|
+
const link = `[closeout](closeouts/${closeoutFile})`;
|
|
44
|
+
if (!current.split(/\r?\n/).some((line) => line.includes(link))) {
|
|
45
|
+
await appendFile(threadLogPath, `${entry}\n`, "utf8");
|
|
46
|
+
}
|
|
47
|
+
return `.codecarto/closeouts/${closeoutFile}`;
|
|
48
|
+
}
|
|
49
|
+
export async function completeValidatedPhase(cwd, validation, sourceLabel) {
|
|
50
|
+
const initialState = await getWorkspaceState(cwd);
|
|
51
|
+
if (!initialState)
|
|
52
|
+
throw new Error("CodeCartographer workspace not found. Run /codecarto-init first.");
|
|
53
|
+
const handoff = await loadHandoffFile(validation.phaseId, initialState.workspaceDir);
|
|
54
|
+
if (handoff && handoff.phase_id !== validation.phaseId) {
|
|
55
|
+
throw new Error(`Invalid handoff: phase_id ${handoff.phase_id} does not match ${validation.phaseId}`);
|
|
56
|
+
}
|
|
57
|
+
if (handoff) {
|
|
58
|
+
const activePhases = new Set(initialState.pipeline.phase_order);
|
|
59
|
+
const sourceIndex = initialState.pipeline.phase_order.indexOf(validation.phaseId);
|
|
60
|
+
for (const entry of handoff.carry_forward) {
|
|
61
|
+
const targetIndex = entry.target_phase ? initialState.pipeline.phase_order.indexOf(entry.target_phase) : -1;
|
|
62
|
+
if (!entry.target_phase || !activePhases.has(entry.target_phase) || targetIndex <= sourceIndex) {
|
|
63
|
+
throw new Error(`Invalid handoff: carry_forward target_phase ${entry.target_phase ?? "(missing)"} is not a downstream active pipeline phase; use post_pipeline for work after the pipeline`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const entry of handoff.post_pipeline) {
|
|
67
|
+
if (!entry.id?.trim())
|
|
68
|
+
throw new Error("Invalid handoff: post_pipeline entries require a canonical id");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const completionTimestamp = new Date().toISOString();
|
|
72
|
+
let closeoutPath;
|
|
73
|
+
const updatedState = await updateStatusAtomically(cwd, async (lockedState) => {
|
|
74
|
+
const phase = resolvePhase(lockedState, validation.phaseId);
|
|
75
|
+
if (!phase?.primary_output)
|
|
76
|
+
throw new Error(`Phase ${validation.phaseId} is missing primary_output.`);
|
|
77
|
+
const nextStatus = normalizeStatus(lockedState.status, lockedState.pipeline, lockedState.status.pipeline, lockedState.cwd);
|
|
78
|
+
const existingPhase = nextStatus.phases[validation.phaseId] ?? {
|
|
79
|
+
status: "pending",
|
|
80
|
+
owner_notes: [],
|
|
81
|
+
outputs_present: [],
|
|
82
|
+
open_questions: [],
|
|
83
|
+
carry_forward: [],
|
|
84
|
+
};
|
|
85
|
+
const gapEntries = validation.rows
|
|
86
|
+
.filter((row) => row.result.toUpperCase().includes("PARTIAL"))
|
|
87
|
+
.map((row) => ({
|
|
88
|
+
kind: "needs-maintainer-decision",
|
|
89
|
+
description: row.criterion || "Partial validation gap",
|
|
90
|
+
deferred_reason: row.evidence || "Marked PARTIAL by validation",
|
|
91
|
+
}));
|
|
92
|
+
autoAssignIds(gapEntries, "oq", validation.phaseId);
|
|
93
|
+
const mergedOpenQuestions = [...existingPhase.open_questions];
|
|
94
|
+
for (const candidate of gapEntries) {
|
|
95
|
+
if (!mergedOpenQuestions.some((entry) => entry.description === candidate.description && entry.deferred_reason === candidate.deferred_reason)) {
|
|
96
|
+
mergedOpenQuestions.push(candidate);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
nextStatus.phases[validation.phaseId] = {
|
|
100
|
+
status: "complete",
|
|
101
|
+
owner_notes: uniqueStrings([
|
|
102
|
+
...existingPhase.owner_notes,
|
|
103
|
+
`Completed via ${sourceLabel}.`,
|
|
104
|
+
`Primary output: .codecarto/${validation.primaryOutput}`,
|
|
105
|
+
`Validation: ${validation.overall}`,
|
|
106
|
+
]),
|
|
107
|
+
outputs_present: uniqueStrings([...existingPhase.outputs_present, validation.primaryOutput]),
|
|
108
|
+
open_questions: mergedOpenQuestions,
|
|
109
|
+
carry_forward: existingPhase.carry_forward ?? [],
|
|
110
|
+
};
|
|
111
|
+
if (handoff)
|
|
112
|
+
applyHandoff(nextStatus, handoff);
|
|
113
|
+
nextStatus.last_updated = completionTimestamp;
|
|
114
|
+
const nextWorkspace = { ...lockedState, status: nextStatus };
|
|
115
|
+
const nextEligible = getNextEligiblePhase(nextWorkspace);
|
|
116
|
+
nextStatus.current_phase = nextEligible?.id ?? "complete";
|
|
117
|
+
nextStatus.next_actions = nextEligible
|
|
118
|
+
? [`Begin ${nextEligible.id} phase by producing ${nextEligible.primary_output ?? `findings/${nextEligible.id}/`}`]
|
|
119
|
+
: ["All phases complete. Review findings, open questions, and downstream implementation notes."];
|
|
120
|
+
closeoutPath = await writeCompletionArtifacts(lockedState.workspaceDir, validation.phaseId, validation, completionTimestamp, handoff);
|
|
121
|
+
return { state: { ...nextWorkspace, status: nextStatus } };
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
updatedState,
|
|
125
|
+
closeoutNotice: closeoutPath ? `Closeout: ${closeoutPath}` : undefined,
|
|
126
|
+
};
|
|
127
|
+
}
|
package/dist/core/dashboard.js
CHANGED
|
@@ -22,6 +22,7 @@ export function renderDashboard(inputs) {
|
|
|
22
22
|
renderUsagePanel(inputs),
|
|
23
23
|
renderActivityTimeline(inputs.usage.runs),
|
|
24
24
|
renderOpenQuestionsRollup(inputs.status),
|
|
25
|
+
renderPostPipelineWork(inputs.status),
|
|
25
26
|
renderCloseoutsList(inputs),
|
|
26
27
|
renderFooter(inputs),
|
|
27
28
|
`</div>`,
|
|
@@ -74,6 +75,7 @@ function renderSidebar(inputs) {
|
|
|
74
75
|
`<a class="cc-nav-section" href="#phases">Phases</a>`,
|
|
75
76
|
phaseLinks,
|
|
76
77
|
`<a class="cc-nav-section" href="#usage">Usage</a>`,
|
|
78
|
+
(inputs.status.post_pipeline?.length ?? 0) > 0 ? `<a class="cc-nav-section" href="#post-pipeline">Post-pipeline</a>` : "",
|
|
77
79
|
`<a class="cc-nav-section" href="#closeouts">Closeouts</a>`,
|
|
78
80
|
`</nav>`,
|
|
79
81
|
`<button type="button" class="cc-export" data-export>Export dashboard JSON</button>`,
|
|
@@ -134,6 +136,7 @@ function renderHealthPanel(inputs) {
|
|
|
134
136
|
const issues = collectDashboardIssues(inputs);
|
|
135
137
|
const openQuestionCount = countOpenQuestions(status);
|
|
136
138
|
const carryForwardCount = countCarryForward(status);
|
|
139
|
+
const postPipelineCount = (status.post_pipeline ?? []).filter((entry) => entry.status !== "resolved").length;
|
|
137
140
|
const health = issues.some((i) => i.severity === "blocker") ? "attention required" : issues.length ? "review recommended" : completed === total ? "complete" : "on track";
|
|
138
141
|
const healthClass = issues.some((i) => i.severity === "blocker") ? "bad" : issues.length ? "warn" : "ok";
|
|
139
142
|
const tokenText = usageHasTokenAccounting(usage) ? formatTokenCount(totals.tokens.input + totals.tokens.output) : usage.runs.length ? "unavailable" : "0";
|
|
@@ -150,6 +153,7 @@ function renderHealthPanel(inputs) {
|
|
|
150
153
|
renderHealthMetric("Artifacts needing attention", String(issues.length), issues.length ? "bad" : "ok"),
|
|
151
154
|
renderHealthMetric("Open questions", String(openQuestionCount), openQuestionCount ? "warn" : "ok"),
|
|
152
155
|
renderHealthMetric("Carry-forward items", String(carryForwardCount), carryForwardCount ? "warn" : "ok"),
|
|
156
|
+
renderHealthMetric("Post-pipeline work", String(postPipelineCount), postPipelineCount ? "neutral" : "ok"),
|
|
153
157
|
renderHealthMetric("Tool uses", String(totals.tool_uses), "neutral"),
|
|
154
158
|
renderHealthMetric("Runtime", formatMillis(totals.duration_ms), "neutral"),
|
|
155
159
|
renderHealthMetric("Tokens", tokenText, tokenText === "unavailable" ? "warn" : "neutral"),
|
|
@@ -369,7 +373,8 @@ function renderPhaseLastRun(run) {
|
|
|
369
373
|
const tokensTotal = formatRunTokens(run);
|
|
370
374
|
const sessionLink = run.session_file ? renderSafeLink(run.session_file, "transcript") : undefined;
|
|
371
375
|
const session = sessionLink ? `<dt>Session</dt><dd>${sessionLink}</dd>` : "";
|
|
372
|
-
|
|
376
|
+
const compactions = run.compactions ? `<dt>Compactions</dt><dd>${formatCompactionCounts(run.compactions)}</dd>` : "";
|
|
377
|
+
return [`<div class="cc-phase-section">`, `<h3>Last run</h3>`, `<dl class="cc-run-meta">`, `<dt>Timestamp</dt><dd>${escapeHtml(run.timestamp)}</dd>`, `<dt>Status</dt><dd>${escapeHtml(run.status)}</dd>`, `<dt>Turns</dt><dd>${run.turn_count}</dd>`, `<dt>Tool uses</dt><dd>${run.tool_uses}</dd>`, `<dt>Tokens</dt><dd>${escapeHtml(tokensTotal)}</dd>`, `<dt>Duration</dt><dd>${escapeHtml(formatMillis(run.duration_ms))}</dd>`, compactions, session, `</dl>`, `</div>`].join("");
|
|
373
378
|
}
|
|
374
379
|
function renderUsagePanel(inputs) {
|
|
375
380
|
const { usage, pipeline, status } = inputs;
|
|
@@ -382,10 +387,11 @@ function renderUsagePanel(inputs) {
|
|
|
382
387
|
const t = perPhase.get(phaseId);
|
|
383
388
|
if (!t) {
|
|
384
389
|
const complete = status.phases[phaseId]?.status === "complete";
|
|
385
|
-
return `<tr class="${complete ? "cc-usage-missing" : ""}"><td><a href="#${phaseAnchor(phaseId)}">${escapeHtml(phaseId)}</a></td><td>0</td><td>—</td><td>—</td><td>—</td><td>${complete ? "usage not recorded" : "not run"}</td></tr>`;
|
|
390
|
+
return `<tr class="${complete ? "cc-usage-missing" : ""}"><td><a href="#${phaseAnchor(phaseId)}">${escapeHtml(phaseId)}</a></td><td>0</td><td>—</td><td>—</td><td>—</td><td>—</td><td>${complete ? "usage not recorded" : "not run"}</td></tr>`;
|
|
386
391
|
}
|
|
387
392
|
const tokensTotal = t.tokens.input + t.tokens.output;
|
|
388
|
-
|
|
393
|
+
const compactions = t.compaction_runs > 0 ? formatCompactionTriplet(t.compactions) : "unavailable";
|
|
394
|
+
return `<tr><td><a href="#${phaseAnchor(phaseId)}">${escapeHtml(phaseId)}</a></td><td>${t.runs}</td><td>${escapeHtml(tokenAccounting ? formatTokenCount(tokensTotal) : "unavailable")}</td><td>${renderUsageBar(t.tool_uses, maxTools, String(t.tool_uses))}</td><td>${renderUsageBar(t.duration_ms, maxDuration, formatMillis(t.duration_ms))}</td><td>${compactions}</td><td>${usagePhaseNote(phaseId, status)}</td></tr>`;
|
|
389
395
|
}).join("");
|
|
390
396
|
return [
|
|
391
397
|
`<section class="cc-card cc-usage" id="usage" aria-label="Token usage" data-section data-search-text="usage tokens tool duration">`,
|
|
@@ -393,7 +399,7 @@ function renderUsagePanel(inputs) {
|
|
|
393
399
|
usage.runs.length === 0 ? `<p class="cc-empty">No phase runs recorded yet.</p>` : `<dl class="cc-usage-totals">${renderUsageTotalsList(totals, tokenAccounting)}</dl>`,
|
|
394
400
|
renderUsageInsights(perPhase, status),
|
|
395
401
|
`<table class="cc-usage-table">`,
|
|
396
|
-
`<thead><tr><th>Phase</th><th>Runs</th><th>Tokens</th><th>Tools</th><th>Duration</th><th>State</th></tr></thead>`,
|
|
402
|
+
`<thead><tr><th>Phase</th><th>Runs</th><th>Tokens</th><th>Tools</th><th>Duration</th><th>Compactions</th><th>State</th></tr></thead>`,
|
|
397
403
|
`<tbody>${rows}</tbody>`,
|
|
398
404
|
`</table>`,
|
|
399
405
|
`</section>`,
|
|
@@ -405,7 +411,18 @@ function renderUsageTotalsList(totals, tokenAccounting) {
|
|
|
405
411
|
? `${escapeHtml(formatTokenCount(totals.tokens.input))} / ${escapeHtml(formatTokenCount(totals.tokens.output))} / ${escapeHtml(formatTokenCount(totals.tokens.cache_write))}`
|
|
406
412
|
: `<span class="cc-muted">unavailable — host did not report token counts</span>`;
|
|
407
413
|
const tokenTotal = tokenAccounting ? escapeHtml(formatTokenCount(tokensTotal)) : `<span class="cc-muted">unavailable</span>`;
|
|
408
|
-
|
|
414
|
+
const compactionCounts = totals.compaction_runs > 0 ? formatCompactionCounts(totals.compactions) : `<span class="cc-muted">unavailable — host did not report compaction events</span>`;
|
|
415
|
+
const compactionReasons = totals.compaction_runs > 0 ? `<dt>Reasons</dt><dd>${formatCompactionReasons(totals.compactions)}</dd>` : "";
|
|
416
|
+
return [`<dt>Total runs</dt><dd>${totals.runs}</dd>`, `<dt>Tokens (in / out / cache)</dt><dd>${tokenDetail}</dd>`, `<dt>Total tokens</dt><dd>${tokenTotal}</dd>`, `<dt>Tool uses</dt><dd>${totals.tool_uses}</dd>`, `<dt>Total duration</dt><dd>${escapeHtml(formatMillis(totals.duration_ms))}</dd>`, `<dt>Compactions</dt><dd>${compactionCounts}</dd>`, compactionReasons].join("");
|
|
417
|
+
}
|
|
418
|
+
function formatCompactionCounts(value) {
|
|
419
|
+
return `${value.successful} successful · ${value.failed} failed · ${value.aborted} aborted`;
|
|
420
|
+
}
|
|
421
|
+
function formatCompactionReasons(value) {
|
|
422
|
+
return `threshold ${value.reasons.threshold} · overflow ${value.reasons.overflow} · manual ${value.reasons.manual}`;
|
|
423
|
+
}
|
|
424
|
+
function formatCompactionTriplet(value) {
|
|
425
|
+
return `${value.successful} / ${value.failed} / ${value.aborted}`;
|
|
409
426
|
}
|
|
410
427
|
function renderUsageInsights(perPhase, status) {
|
|
411
428
|
if (perPhase.size === 0)
|
|
@@ -455,7 +472,7 @@ function renderOpenQuestionsRollup(status) {
|
|
|
455
472
|
for (const [phaseId, phaseState] of Object.entries(status.phases)) {
|
|
456
473
|
const questions = [];
|
|
457
474
|
for (const q of phaseState.open_questions ?? []) {
|
|
458
|
-
const key = `${q.kind ?? ""}|${q.description ?? ""}|${q.deferred_reason ?? ""}`;
|
|
475
|
+
const key = q.id ?? `${q.kind ?? ""}|${q.description ?? ""}|${q.deferred_reason ?? ""}`;
|
|
459
476
|
if (seen.has(key))
|
|
460
477
|
continue;
|
|
461
478
|
seen.add(key);
|
|
@@ -473,6 +490,19 @@ function renderOpenQuestionsRollup(status) {
|
|
|
473
490
|
const kindSummary = [...byKind.entries()].sort((a, b) => b[1] - a[1]).map(([kind, count]) => `<span class="cc-kind-chip"><strong>${count}</strong>${escapeHtml(kind)}</span>`).join("");
|
|
474
491
|
return [`<section class="cc-card cc-rollup" aria-label="Open questions roll-up" data-section data-search-text="open questions">`, `<div class="cc-section-head"><h2>Open questions</h2><span>${total} unique</span></div>`, `<div class="cc-kind-summary">${kindSummary}</div>`, buckets.join("\n"), `</section>`].join("\n");
|
|
475
492
|
}
|
|
493
|
+
function renderPostPipelineWork(status) {
|
|
494
|
+
const items = status.post_pipeline ?? [];
|
|
495
|
+
if (items.length === 0)
|
|
496
|
+
return "";
|
|
497
|
+
const pending = items.filter((entry) => entry.status !== "resolved").length;
|
|
498
|
+
const rows = items.map((entry) => {
|
|
499
|
+
const state = entry.status ?? "pending";
|
|
500
|
+
const source = entry.source_phase ? `<span class="cc-pill cc-pill-target">from ${escapeHtml(entry.source_phase)}</span>` : "";
|
|
501
|
+
const kind = entry.kind ? `<span class="cc-kind">${escapeHtml(String(entry.kind))}</span>` : "";
|
|
502
|
+
return `<li>${kind}<strong>${escapeHtml(entry.id ?? "unidentified")}</strong> ${escapeHtml(entry.description ?? "")}${source}<span class="cc-pill">${escapeHtml(state)}</span></li>`;
|
|
503
|
+
}).join("");
|
|
504
|
+
return [`<section class="cc-card cc-rollup" id="post-pipeline" aria-label="Post-pipeline work" data-section data-search-text="post pipeline spikes amendments deltas decisions reruns">`, `<div class="cc-section-head"><h2>Post-pipeline work</h2><span>${pending} pending · ${items.length} total</span></div>`, `<p class="cc-muted">Optional work after the active pipeline; these items do not make pipeline completion partial.</p>`, `<ul class="cc-question-list">${rows}</ul>`, `</section>`].join("\n");
|
|
505
|
+
}
|
|
476
506
|
function renderCloseoutsList(inputs) {
|
|
477
507
|
const closeouts = inputs.closeouts;
|
|
478
508
|
if (closeouts.length === 0)
|
|
@@ -522,7 +552,7 @@ function renderExportData(inputs) {
|
|
|
522
552
|
secondary_outputs: outputs?.secondary ?? [],
|
|
523
553
|
};
|
|
524
554
|
});
|
|
525
|
-
const data = { project: inputs.status.project_name, generatedAt: inputs.generatedAt, packageVersion: inputs.packageVersion, phases, usage: inputs.usage, closeouts: inputs.closeouts };
|
|
555
|
+
const data = { project: inputs.status.project_name, generatedAt: inputs.generatedAt, packageVersion: inputs.packageVersion, phases, post_pipeline: inputs.status.post_pipeline, usage: inputs.usage, closeouts: inputs.closeouts };
|
|
526
556
|
return `<script id="cc-dashboard-data" type="application/json">${escapeJsonForScript(data)}</script>`;
|
|
527
557
|
}
|
|
528
558
|
function renderScripts() {
|
package/dist/core/index.d.ts
CHANGED
|
@@ -5,7 +5,9 @@ export * from "./status.ts";
|
|
|
5
5
|
export * from "./pipeline.ts";
|
|
6
6
|
export * from "./prompts.ts";
|
|
7
7
|
export * from "./workspace.ts";
|
|
8
|
+
export * from "./completion.ts";
|
|
8
9
|
export * from "./orchestrator-config.ts";
|
|
9
10
|
export * from "./usage.ts";
|
|
10
11
|
export * from "./dashboard.ts";
|
|
11
12
|
export * from "./library.ts";
|
|
13
|
+
export * from "./synthesis.ts";
|
package/dist/core/index.js
CHANGED
|
@@ -8,7 +8,9 @@ export * from "./status.js";
|
|
|
8
8
|
export * from "./pipeline.js";
|
|
9
9
|
export * from "./prompts.js";
|
|
10
10
|
export * from "./workspace.js";
|
|
11
|
+
export * from "./completion.js";
|
|
11
12
|
export * from "./orchestrator-config.js";
|
|
12
13
|
export * from "./usage.js";
|
|
13
14
|
export * from "./dashboard.js";
|
|
14
15
|
export * from "./library.js";
|
|
16
|
+
export * from "./synthesis.js";
|
package/dist/core/pipeline.js
CHANGED
|
@@ -9,6 +9,7 @@ export const PIPELINE_ALIASES = {
|
|
|
9
9
|
"defect-scan": "workflow/pipeline-defect-scan.yaml",
|
|
10
10
|
lite: "workflow/pipeline-lite.yaml",
|
|
11
11
|
"architecture-only": "workflow/pipeline-architecture-only.yaml",
|
|
12
|
+
synthesis: "workflow/pipeline-synthesis.yaml",
|
|
12
13
|
};
|
|
13
14
|
export const DEFAULT_PIPELINE_PATH = "workflow/pipeline-full-with-deep-audit.yaml";
|
|
14
15
|
export function getPhaseMap(pipeline) {
|
package/dist/core/prompts.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import type { CarryForwardEntry, OpenQuestionEntry, PipelinePhase,
|
|
1
|
+
import type { CarryForwardEntry, OpenQuestionEntry, PipelinePhase, WorkspaceState } from "./types.ts";
|
|
2
|
+
import { type PhasePreflightResult } from "./synthesis.ts";
|
|
2
3
|
export declare function describeEntry(entry: OpenQuestionEntry | CarryForwardEntry): string;
|
|
3
4
|
export declare function collectRoutedCarryForward(state: WorkspaceState, targetPhaseId: string): CarryForwardEntry[];
|
|
4
5
|
export interface BuildPhasePromptOptions {
|
|
6
|
+
/**
|
|
7
|
+
* A result the caller already validated immediately before prompt building.
|
|
8
|
+
* Pi uses this to preserve its caller-specific preflight error handling
|
|
9
|
+
* without repeating the same filesystem reads. Callers that omit it (MCP
|
|
10
|
+
* and direct/forced prompting) remain self-contained and run preflight here.
|
|
11
|
+
*/
|
|
12
|
+
preflight?: PhasePreflightResult;
|
|
5
13
|
/**
|
|
6
14
|
* Set when the phase is being run inside `/codecarto-next --auto` (or any
|
|
7
15
|
* other non-interactive driver). Suppresses interactive hooks that would
|
|
@@ -14,7 +22,5 @@ export interface BuildPhasePromptOptions {
|
|
|
14
22
|
}
|
|
15
23
|
export declare function buildPhasePrompt(state: WorkspaceState, phase: PipelinePhase, forced: boolean, options?: BuildPhasePromptOptions): Promise<string>;
|
|
16
24
|
export declare function closeoutFileName(date: string, phaseOrModule: string): string;
|
|
17
|
-
export declare function buildThreadLogEntry(phaseOrModule: string, validation: ValidationResult, timestamp: string): string;
|
|
18
|
-
export declare function ensureCloseoutStub(workspaceDir: string, phaseOrModule: string, timestamp: string): Promise<string | null>;
|
|
19
25
|
export declare function listSkillNames(workspaceDir: string): Promise<string[]>;
|
|
20
26
|
export declare function buildSkillPrompt(state: WorkspaceState, skillName: string): Promise<string>;
|