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.
- package/.codecarto/GUIDE.md +30 -15
- 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/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/merged-spec.md +58 -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/vision.md +63 -0
- package/.codecarto/workflow/pipeline-synthesis.yaml +105 -0
- package/.codecarto/workflow/status.yaml +2 -0
- package/README.md +56 -8
- package/dist/core/completion.d.ts +6 -0
- package/dist/core/completion.js +127 -0
- package/dist/core/dashboard.js +19 -2
- 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 +37 -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/workspace.d.ts +3 -1
- package/dist/core/workspace.js +39 -3
- package/dist/core/yaml.js +24 -0
- package/dist/extensions/codecarto/auto-runner.d.ts +3 -1
- package/dist/extensions/codecarto/auto-runner.js +20 -68
- package/dist/extensions/codecarto/index.js +125 -11
- package/dist/mcp-server/server.js +24 -68
- 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
|
[](https://www.npmjs.com/package/codecartographer-pi)
|
|
10
10
|
[](package.json)
|
|
11
11
|
|
|
12
|
-
> **
|
|
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
|
-
|
|
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
|
|
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
|
|
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`,
|
|
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
|
|
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]` |
|
|
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
|
|
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
|
+
}
|
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"),
|
|
@@ -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() {
|
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>;
|