codecartographer-pi 0.11.0 → 0.12.1

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 (39) hide show
  1. package/.codecarto/GUIDE.md +30 -15
  2. package/.codecarto/README.md +3 -0
  3. package/.codecarto/findings/goal-synthesis/README.md +3 -0
  4. package/.codecarto/findings/goal-synthesis-finalize/SKILL.md +30 -0
  5. package/.codecarto/findings/goal-synthesis-propose/SKILL.md +24 -0
  6. package/.codecarto/findings/spec-merge/README.md +3 -0
  7. package/.codecarto/findings/spec-merge/SKILL.md +23 -0
  8. package/.codecarto/findings/vision-capture/README.md +3 -0
  9. package/.codecarto/findings/vision-capture/SKILL.md +26 -0
  10. package/.codecarto/inputs/vision.md +11 -0
  11. package/.codecarto/templates/merged-spec.md +58 -0
  12. package/.codecarto/templates/phase-handoff.yaml +22 -0
  13. package/.codecarto/templates/project-plan.md +70 -0
  14. package/.codecarto/templates/proposal.md +43 -0
  15. package/.codecarto/templates/vision.md +63 -0
  16. package/.codecarto/workflow/pipeline-synthesis.yaml +105 -0
  17. package/.codecarto/workflow/status.yaml +2 -0
  18. package/README.md +56 -8
  19. package/dist/core/completion.d.ts +6 -0
  20. package/dist/core/completion.js +127 -0
  21. package/dist/core/dashboard.js +19 -2
  22. package/dist/core/index.d.ts +2 -0
  23. package/dist/core/index.js +2 -0
  24. package/dist/core/pipeline.js +1 -0
  25. package/dist/core/prompts.d.ts +9 -3
  26. package/dist/core/prompts.js +37 -26
  27. package/dist/core/status.d.ts +7 -1
  28. package/dist/core/status.js +187 -2
  29. package/dist/core/synthesis.d.ts +31 -0
  30. package/dist/core/synthesis.js +140 -0
  31. package/dist/core/types.d.ts +29 -1
  32. package/dist/core/workspace.d.ts +3 -1
  33. package/dist/core/workspace.js +39 -3
  34. package/dist/core/yaml.js +24 -0
  35. package/dist/extensions/codecarto/auto-runner.d.ts +3 -1
  36. package/dist/extensions/codecarto/auto-runner.js +20 -68
  37. package/dist/extensions/codecarto/index.js +125 -11
  38. package/dist/mcp-server/server.js +24 -68
  39. package/package.json +10 -4
@@ -0,0 +1,105 @@
1
+ workflow_name: evidence-backed-project-synthesis
2
+ workflow_version: 1
3
+ workflow_goal: Combine a product vision with confirmed library specifications into a provenance-preserving implementation plan.
4
+ source_location: ../
5
+ validation_protocol: workflow/VALIDATE.md
6
+ phase_order:
7
+ - vision-capture
8
+ - goal-synthesis-propose
9
+ - spec-merge
10
+ - goal-synthesis-finalize
11
+ phases:
12
+ - id: vision-capture
13
+ purpose: Turn the user's product intent into a bounded, testable vision without prematurely selecting source specifications.
14
+ skill_path: findings/vision-capture/SKILL.md
15
+ output_template: templates/vision.md
16
+ depends_on: []
17
+ primary_output: findings/vision-capture/vision.md
18
+ secondary_outputs: []
19
+ required_reads:
20
+ - GUIDE.md
21
+ - workflow/status.yaml
22
+ preflight:
23
+ - requires-vision-input
24
+ completion_criteria:
25
+ - The target audience, problem, outcomes, constraints, and non-goals are explicit.
26
+ - Success measures and acceptance scenarios are testable.
27
+ - Assumptions and unresolved decisions are separated from confirmed intent.
28
+ - Coverage and limits name inspected scope, skipped scope, evidence basis, and blind spots.
29
+ handoff_requirements:
30
+ - Run validation per workflow/VALIDATE.md. Append validation block to primary output.
31
+ - Write the canonical phase handoff; do not edit workflow state directly.
32
+ - id: goal-synthesis-propose
33
+ purpose: Shortlist library specifications against the vision and pause for explicit human confirmation.
34
+ skill_path: findings/goal-synthesis-propose/SKILL.md
35
+ output_template: templates/proposal.md
36
+ depends_on:
37
+ - vision-capture
38
+ primary_output: findings/goal-synthesis/proposal.md
39
+ secondary_outputs: []
40
+ required_reads:
41
+ - GUIDE.md
42
+ - workflow/status.yaml
43
+ - findings/vision-capture/vision.md
44
+ preflight:
45
+ - requires-library
46
+ completion_criteria:
47
+ - Candidate library entries are ranked against explicit vision needs.
48
+ - Inclusion benefits, likely conflicts, and missing capabilities are stated for every candidate.
49
+ - At least one entry is presented with an unchecked human-confirmation box.
50
+ - Coverage and limits name inspected scope, skipped scope, evidence basis, and blind spots.
51
+ handoff_requirements:
52
+ - Stop after writing the proposal; the user confirms selections by changing one or more [ ] boxes to [x].
53
+ - Run validation per workflow/VALIDATE.md. Append validation block to primary output.
54
+ - Write the canonical phase handoff; do not edit workflow state directly.
55
+ - id: spec-merge
56
+ purpose: Merge only the human-confirmed specifications into a normalized, conflict-explicit intermediate.
57
+ skill_path: findings/spec-merge/SKILL.md
58
+ output_template: templates/merged-spec.md
59
+ depends_on:
60
+ - goal-synthesis-propose
61
+ primary_output: findings/spec-merge/merged-spec.md
62
+ secondary_outputs: []
63
+ required_reads:
64
+ - GUIDE.md
65
+ - workflow/status.yaml
66
+ - findings/vision-capture/vision.md
67
+ - findings/goal-synthesis/proposal.md
68
+ preflight:
69
+ - requires-library
70
+ - requires-confirmed-proposal
71
+ completion_criteria:
72
+ - Only human-confirmed library entries are merged.
73
+ - Capabilities, invariants, constraints, and acceptance behavior are normalized by concept rather than copied by source structure.
74
+ - Conflicts, gaps, and chosen dispositions are explicit.
75
+ - Every load-bearing merged claim has a provenance reference.
76
+ - Coverage and limits name inspected scope, skipped scope, evidence basis, and blind spots.
77
+ handoff_requirements:
78
+ - Run validation per workflow/VALIDATE.md. Append validation block to primary output.
79
+ - Write the canonical phase handoff; do not edit workflow state directly.
80
+ - id: goal-synthesis-finalize
81
+ purpose: Transform the confirmed vision and merged specifications into an implementation-ready project plan with a provenance ledger.
82
+ skill_path: findings/goal-synthesis-finalize/SKILL.md
83
+ output_template: templates/project-plan.md
84
+ depends_on:
85
+ - spec-merge
86
+ primary_output: findings/goal-synthesis/project-plan.md
87
+ secondary_outputs: []
88
+ required_reads:
89
+ - GUIDE.md
90
+ - workflow/status.yaml
91
+ - findings/vision-capture/vision.md
92
+ - findings/goal-synthesis/proposal.md
93
+ - findings/spec-merge/merged-spec.md
94
+ preflight:
95
+ - requires-library
96
+ - requires-confirmed-proposal
97
+ completion_criteria:
98
+ - The plan defines coherent product scope, architecture, work packages, dependencies, and acceptance gates.
99
+ - Each load-bearing plan decision is traceable through the provenance ledger.
100
+ - Conflicts and unknowns remain visible with explicit dispositions.
101
+ - The implementation sequence identifies an executable first slice.
102
+ - Coverage and limits name inspected scope, skipped scope, evidence basis, and blind spots.
103
+ handoff_requirements:
104
+ - Run validation per workflow/VALIDATE.md. Append validation block to primary output.
105
+ - Write the canonical phase handoff; do not edit workflow state directly.
@@ -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
@@ -9,7 +9,7 @@
9
9
  [![npm version](https://img.shields.io/npm/v/codecartographer-pi.svg)](https://www.npmjs.com/package/codecartographer-pi)
10
10
  [![Node](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](package.json)
11
11
 
12
- > **A structured pipeline for reverse-engineering unfamiliar codebases with an LLM.** Drop it into any repo, point an LLM at the guide, and walk away with a layered analysis: architecture map, behavioral contracts, protocol documentation, defect report, porting bundle, and a language-agnostic reimplementation spec. Every finding is evidence-tagged. Every phase output is validated before the next one starts.
12
+ > **Evidence-backed software cartography for coding agents.** Turn an unfamiliar repository into validated architecture, contracts, defects, and a reimplementation specification—then combine explicitly confirmed specifications with a product vision to produce a provenance-backed implementation plan.
13
13
 
14
14
  ```text
15
15
  ● CodeCartographer
@@ -32,14 +32,17 @@
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 underway.** v0.9.0 adds the experimental library foundation and MCP publish/list/reindex tools for accumulating `reimplementation-spec.md` artifacts in a git-trackable library. The Pi publish UX and synthesis pipeline that turns selected library entries plus a vision into `project-plan.md` are still in progress. See [`docs/synthesis-roadmap.md`](docs/synthesis-roadmap.md) for the implementation tracker.
37
+ 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
 
40
43
  ## Install
41
44
 
42
- Three surfaces, in recommended order. All three share the same `core/` and produce byte-identical phase prompts but the user experience differs by surface, and new features (live widget, dashboard, auto-runner, the upcoming library + synthesis workflows) land on Pi first, MCP second, drop-in last.
45
+ Three surfaces, in recommended order. All three share the same `core/` and produce byte-identical phase prompts. Pi provides the richest orchestration UX; Pi and MCP both support the executable library and synthesis workflows; drop-in mode provides the analysis framework without those runtime operations.
43
46
 
44
47
  1. **Pi extension** — recommended for interactive use. First-class UX.
45
48
  2. **MCP server** — for Claude Code, Codex, opencode, Cursor, Claude Desktop, and any other MCP-capable agent.
@@ -83,6 +86,8 @@ Add to your host config (`~/.config/claude-code/config.json`, `claude_desktop_co
83
86
  }
84
87
  ```
85
88
 
89
+ Official MCP Registry server name: `io.github.huginnindustries/codecartographer`.
90
+
86
91
  ### Drop-in template (one-off / evaluation)
87
92
 
88
93
  Use this to try CodeCartographer in any repo without installing anything, or in environments where neither Pi nor an MCP-capable agent is available. Works with any LLM that can read and write files.
@@ -93,7 +98,47 @@ cp -r /path/to/CodeCartographer/.codecarto /path/to/your-repo/
93
98
 
94
99
  Then in the LLM session: `Read .codecarto/GUIDE.md and begin the analysis.`
95
100
 
96
- > **Limitation.** Drop-in mode runs the analysis pipeline fully, but library + synthesis workflows require executable code. Publishing and reading library entries are currently available through the MCP server; Pi publish UX and project-plan synthesis are still in progress. See [`docs/synthesis-roadmap.md`](docs/synthesis-roadmap.md) for the planned scope.
101
+ > **Limitation.** Drop-in mode runs the analysis pipeline fully, but library + synthesis workflows require executable code through Pi or MCP.
102
+
103
+ ---
104
+
105
+ ## Forward synthesis quickstart
106
+
107
+ 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.
108
+
109
+ 1. Configure the library that contains specs published with `/codecarto-publish` or the MCP `codecarto_publish` tool:
110
+
111
+ ```yaml
112
+ # ~/.codecarto/config.yaml or .codecarto/workflow/config.yaml
113
+ library:
114
+ path: /absolute/path/to/codecarto-library
115
+ namespace: your-namespace # omit for a single-tenant library
116
+ publish_confirm: true
117
+ ```
118
+
119
+ 2. Initialize a clean planning workspace and fill in its brief:
120
+
121
+ ```text
122
+ /codecarto-init synthesis
123
+ ```
124
+
125
+ Edit `.codecarto/inputs/vision.md` with the audience, problem, desired outcome, constraints, and non-goals.
126
+
127
+ 3. Run until CodeCartographer creates the candidate proposal:
128
+
129
+ ```text
130
+ /codecarto-next --auto
131
+ ```
132
+
133
+ The run intentionally stops before merging. Review `.codecarto/findings/goal-synthesis/proposal.md` and change one or more candidate boxes from `[ ]` to `[x]`.
134
+
135
+ 4. Resume:
136
+
137
+ ```text
138
+ /codecarto-next --auto
139
+ ```
140
+
141
+ 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
142
 
98
143
  ---
99
144
 
@@ -134,7 +179,7 @@ The filesystem, not the conversation, is the durable memory of a run:
134
179
 
135
180
  - 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.
136
181
  - 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.
137
- - `workflow/status.yaml` records progress, `open_questions`, and `carry_forward` items routed to later phases. `CONVENTIONS.md`, `DECISIONS.md`, closeouts, and `THREAD_LOG.md` preserve cross-session knowledge and handoffs.
182
+ - `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.
138
183
  - Pi phase transcripts are file-backed and remain available through `/resume`, `/tree`, and `/export`, even when the active model context has been compacted.
139
184
  - 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`.
140
185
  - 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.
@@ -175,6 +220,7 @@ The default is a 7-phase run that splits the defect scan into a mechanical early
175
220
  | **Defect scan** | 2 | Maintenance audit to surface latent problems |
176
221
  | **Lite** | 3 | You need to understand behavior without porting plans |
177
222
  | **Architecture only** | 1 | Quick structural overview |
223
+ | **Synthesis** | 4 | Turn a product vision and confirmed library specifications into a provenance-backed implementation plan |
178
224
 
179
225
  Set the active pipeline by editing `workflow/status.yaml`'s `pipeline:` field, or pass it as the argument to `/codecarto-init`.
180
226
 
@@ -188,6 +234,7 @@ Set the active pipeline by editing `workflow/status.yaml`'s `pipeline:` field, o
188
234
  | Defect scan | `workflow/pipeline-defect-scan.yaml` |
189
235
  | Lite | `workflow/pipeline-lite.yaml` |
190
236
  | Architecture only | `workflow/pipeline-architecture-only.yaml` |
237
+ | Synthesis | `workflow/pipeline-synthesis.yaml` |
191
238
 
192
239
  ---
193
240
 
@@ -232,7 +279,7 @@ Beyond the slash commands, the Pi extension layers on:
232
279
 
233
280
  **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.
234
281
 
235
- **Tool interception.** `bash` is blocked outright; `edit` and `write` are confined to `.codecarto/`. Same rules apply to phase sub-agents.
282
+ **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.
236
283
 
237
284
  ### Slash commands
238
285
 
@@ -244,8 +291,9 @@ Beyond the slash commands, the Pi extension layers on:
244
291
  | `/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". |
245
292
  | `/codecarto-phase <id>` | Force a specific phase, even out of pipeline order |
246
293
  | `/codecarto-validate [phase]` | Validate a phase output against completion criteria |
247
- | `/codecarto-complete [phase]` | Atomically mark a phase complete (validation must pass) |
294
+ | `/codecarto-complete [phase]` | Validate and atomically apply the phase handoff, canonical status, closeout, and log entry |
248
295
  | `/codecarto-skill <name>` | Run a post-pipeline skill once all phases are complete |
296
+ | `/codecarto-publish` | Publish the reimplementation spec to the configured library after reviewing an explicit confirmation preview |
249
297
  | `/codecarto-usage` | Cumulative + per-phase token usage |
250
298
  | `/codecarto-dashboard [--narrate]` | Regenerate `.codecarto/dashboard.html`; `--narrate` for the LLM executive summary |
251
299
 
@@ -427,7 +475,7 @@ If you're testing a new model, start with `pipeline-architecture-only.yaml` on a
427
475
  protocols/ # Event streams, state machines, persistence formats.
428
476
  porting/ # Reverse-engineering synthesis bundle.
429
477
  reimplementation-spec/ # Language-agnostic build spec.
430
- scratch/ # Disposable analysis notes.
478
+ scratch/ # Disposable notes plus checkpoints and structured phase handoffs.
431
479
  templates/ # Output structure templates.
432
480
  workflow/ # Pipeline definitions, status, validation, config.
433
481
  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
+ }
@@ -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"),
@@ -468,7 +472,7 @@ function renderOpenQuestionsRollup(status) {
468
472
  for (const [phaseId, phaseState] of Object.entries(status.phases)) {
469
473
  const questions = [];
470
474
  for (const q of phaseState.open_questions ?? []) {
471
- const key = `${q.kind ?? ""}|${q.description ?? ""}|${q.deferred_reason ?? ""}`;
475
+ const key = q.id ?? `${q.kind ?? ""}|${q.description ?? ""}|${q.deferred_reason ?? ""}`;
472
476
  if (seen.has(key))
473
477
  continue;
474
478
  seen.add(key);
@@ -486,6 +490,19 @@ function renderOpenQuestionsRollup(status) {
486
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("");
487
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");
488
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
+ }
489
506
  function renderCloseoutsList(inputs) {
490
507
  const closeouts = inputs.closeouts;
491
508
  if (closeouts.length === 0)
@@ -535,7 +552,7 @@ function renderExportData(inputs) {
535
552
  secondary_outputs: outputs?.secondary ?? [],
536
553
  };
537
554
  });
538
- 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 };
539
556
  return `<script id="cc-dashboard-data" type="application/json">${escapeJsonForScript(data)}</script>`;
540
557
  }
541
558
  function renderScripts() {
@@ -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";
@@ -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";
@@ -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) {
@@ -1,7 +1,15 @@
1
- import type { CarryForwardEntry, OpenQuestionEntry, PipelinePhase, ValidationResult, WorkspaceState } from "./types.ts";
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>;