codecartographer-pi 0.12.2 → 0.12.4
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/README.md
CHANGED
|
@@ -86,7 +86,7 @@ Add to your host config (`~/.config/claude-code/config.json`, `claude_desktop_co
|
|
|
86
86
|
}
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
Official MCP Registry
|
|
89
|
+
[Official MCP Registry listing](https://registry.modelcontextprotocol.io/?search=CodeCartographer): `io.github.HuginnIndustries/codecartographer`.
|
|
90
90
|
|
|
91
91
|
### Drop-in template (one-off / evaluation)
|
|
92
92
|
|
|
@@ -288,7 +288,7 @@ Beyond the slash commands, the Pi extension layers on:
|
|
|
288
288
|
| `/codecarto-init [variant]` | Copy `.codecarto/` into the current repository, select pipeline variant |
|
|
289
289
|
| `/codecarto-open` | Activate an existing `.codecarto/` workspace in a new Pi session without resetting durable state |
|
|
290
290
|
| `/codecarto-status` | Current phase, progress, open questions |
|
|
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 (
|
|
291
|
+
| `/codecarto-next [--auto [--strict]] [--llm-steer \| --no-llm-steer]` | Spawn the next eligible phase as a sub-agent. After the sub-agent finishes, auto-validates and auto-completes the phase so `status.yaml` advances without manual steps. `--auto` walks the full pipeline end-to-end (same validate + complete + advance loop, repeated); `--strict` flips the `PASS WITH GAPS` rule from "advance" to "pause". |
|
|
292
292
|
| `/codecarto-phase <id>` | Force a specific phase, even out of pipeline order |
|
|
293
293
|
| `/codecarto-validate [phase]` | Validate a phase output against completion criteria |
|
|
294
294
|
| `/codecarto-complete [phase]` | Validate and atomically apply the phase handoff, canonical status, closeout, and log entry |
|
package/dist/core/synthesis.js
CHANGED
|
@@ -78,25 +78,25 @@ export async function runPhasePreflight(state, phase) {
|
|
|
78
78
|
if (checks.has("requires-vision-input")) {
|
|
79
79
|
const visionPath = join(state.workspaceDir, SYNTHESIS_VISION_INPUT_PATH);
|
|
80
80
|
if (!(await pathExists(visionPath))) {
|
|
81
|
-
throw new PhasePreflightError(phase.id, `the vision brief is missing at .codecarto/${SYNTHESIS_VISION_INPUT_PATH}. Create
|
|
81
|
+
throw new PhasePreflightError(phase.id, `the vision brief is missing at .codecarto/${SYNTHESIS_VISION_INPUT_PATH}. Create that file and describe your product intent (audience, problem, desired outcome, constraints, non-goals). See .codecarto/templates/vision.md for the expected structure.`);
|
|
82
82
|
}
|
|
83
83
|
const rawVision = await readFile(visionPath, "utf8");
|
|
84
84
|
if (!hasMeaningfulVisionContent(rawVision)) {
|
|
85
|
-
throw new PhasePreflightError(phase.id, `the vision brief at .codecarto/${SYNTHESIS_VISION_INPUT_PATH}
|
|
85
|
+
throw new PhasePreflightError(phase.id, `the vision brief at .codecarto/${SYNTHESIS_VISION_INPUT_PATH} appears to be empty or only contains comments. Write your product intent into that file — at minimum: who the product is for, what problem it solves, and what outcome you want. See .codecarto/templates/vision.md for the full structure.`);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
if (checks.has("requires-library")) {
|
|
89
89
|
const config = await loadCodecartoConfig(state.workspaceDir);
|
|
90
90
|
if (!config.library.path) {
|
|
91
|
-
throw new PhasePreflightError(phase.id, "no library.path is configured.
|
|
91
|
+
throw new PhasePreflightError(phase.id, "no library.path is configured. Create a library directory with a .codecarto-library marker file, then set library.path in ~/.codecarto/config.yaml or .codecarto/workflow/config.yaml. Example config:\n library:\n path: ~/codecarto-library\n publish_confirm: true");
|
|
92
92
|
}
|
|
93
93
|
const marker = await discoverLibrary(config.library.path);
|
|
94
94
|
if (!marker) {
|
|
95
|
-
throw new PhasePreflightError(phase.id, `no CodeCartographer library was found at ${config.library.path} (missing .codecarto-library)
|
|
95
|
+
throw new PhasePreflightError(phase.id, `no CodeCartographer library was found at ${config.library.path} (missing .codecarto-library). Create a .codecarto-library marker file in that directory with: {"schema_version": 1, "name": "personal-library", "visibility": "internal", "namespaced": false}`);
|
|
96
96
|
}
|
|
97
97
|
const entries = await listEntries(config.library.path);
|
|
98
98
|
if (entries.length === 0) {
|
|
99
|
-
throw new PhasePreflightError(phase.id, `the configured library at ${config.library.path} has no entries.
|
|
99
|
+
throw new PhasePreflightError(phase.id, `the configured library at ${config.library.path} has no entries. Run a reverse-engineering pipeline (e.g. /codecarto-init full-with-deep-audit) on a source repository, then use /codecarto-publish to publish at least one reimplementation spec into the library before starting synthesis.`);
|
|
100
100
|
}
|
|
101
101
|
result.libraryPath = config.library.path;
|
|
102
102
|
result.libraryName = marker.name;
|
|
@@ -110,13 +110,13 @@ export async function runPhasePreflight(state, phase) {
|
|
|
110
110
|
result.confirmedSelections = parseConfirmedProposalSelections(await readFile(proposalPath, "utf8"));
|
|
111
111
|
result.confirmedEntries = result.confirmedSelections.map((selection) => selection.ref);
|
|
112
112
|
if (result.confirmedSelections.length === 0) {
|
|
113
|
-
throw new PhasePreflightError(phase.id, `no library entries are confirmed in .codecarto/${SYNTHESIS_PROPOSAL_PATH}.
|
|
113
|
+
throw new PhasePreflightError(phase.id, `no library entries are confirmed in .codecarto/${SYNTHESIS_PROPOSAL_PATH}. Open that file and change at least one [ ] checkbox to [x] for the entry you want to include, then retry.`);
|
|
114
114
|
}
|
|
115
115
|
const available = new Map(result.libraryEntries.map((entry) => [entry.ref, entry]));
|
|
116
116
|
for (const selection of result.confirmedSelections) {
|
|
117
117
|
const entry = available.get(selection.ref);
|
|
118
118
|
if (!entry || !entry.versions.includes(selection.version)) {
|
|
119
|
-
throw new PhasePreflightError(phase.id, `confirmed selection ${selection.ref}@v${selection.version} is not present in the configured library.
|
|
119
|
+
throw new PhasePreflightError(phase.id, `confirmed selection ${selection.ref}@v${selection.version} is not present in the configured library. Edit .codecarto/${SYNTHESIS_PROPOSAL_PATH} to correct the checked row (change [x] back to [ ] or update the version), or re-run the goal-synthesis-propose phase to regenerate the proposal.`);
|
|
120
120
|
}
|
|
121
121
|
selection.specPath = specPathForVersion(result.libraryPath, selection.ref, selection.version);
|
|
122
122
|
}
|
|
@@ -66,7 +66,7 @@ function formatTrailer(input) {
|
|
|
66
66
|
lines.push(`Phase transcript: \`${input.sessionFile}\` (open via \`/resume\`).`);
|
|
67
67
|
}
|
|
68
68
|
if (input.status === "completed") {
|
|
69
|
-
lines.push("
|
|
69
|
+
lines.push("Auto-validating and completing the phase — check the status widget for the result.");
|
|
70
70
|
}
|
|
71
71
|
return lines.join("\n");
|
|
72
72
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cp, mkdir, readFile,
|
|
1
|
+
import { cp, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
2
|
import { basename, join, resolve } from "node:path";
|
|
3
3
|
import { autoCompletePhase, buildAutoSummary, isPhaseRunning, runAuto, runSinglePhase } from "./auto-runner.js";
|
|
4
4
|
import { disposeAgentsWidget } from "./agent-widget.js";
|
|
@@ -220,10 +220,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
220
220
|
if (targetExists) {
|
|
221
221
|
const sameWorkspace = normalizeForComparison(await canonicalPath(targetWorkspaceDir)) === normalizeForComparison(await canonicalPath(sourceWorkspaceDir));
|
|
222
222
|
if (!sameWorkspace) {
|
|
223
|
-
const overwrite = await ctx.ui.confirm("CodeCartographer already exists", "A .codecarto/ directory already exists in this repository.
|
|
223
|
+
const overwrite = await ctx.ui.confirm("CodeCartographer already exists — data will be lost", "A .codecarto/ directory already exists in this repository. Re-initializing will back up the existing workspace to .codecarto-backup-TIMESTAMP/ and create a fresh one. All phase findings, handoffs, usage data, closeouts, and progress will be moved to the backup. Consider /codecarto-open to reattach without resetting. Continue?");
|
|
224
224
|
if (!overwrite)
|
|
225
225
|
return;
|
|
226
|
-
|
|
226
|
+
const backupDir = join(ctx.cwd, `.codecarto-backup-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
227
|
+
await rename(targetWorkspaceDir, backupDir);
|
|
228
|
+
if (ctx.hasUI)
|
|
229
|
+
ctx.ui.notify(`Backed up existing workspace to ${basename(backupDir)}/`, "info");
|
|
227
230
|
}
|
|
228
231
|
}
|
|
229
232
|
if (!(await pathExists(targetWorkspaceDir))) {
|
|
@@ -344,7 +347,59 @@ export default function codeCartographerExtension(pi) {
|
|
|
344
347
|
// Fire-and-forget: keep the TUI responsive while the sub-agent works.
|
|
345
348
|
// runSinglePhase handles all side effects (steering message, notify,
|
|
346
349
|
// phase summary, recordUsage, dashboard regen, clearPhase linger).
|
|
350
|
+
// After the sub-agent finishes, auto-validate and auto-complete the
|
|
351
|
+
// phase so status.yaml advances without requiring the user to manually
|
|
352
|
+
// run /codecarto-validate then /codecarto-complete. This mirrors what
|
|
353
|
+
// the auto loop (runAuto) does after each phase.
|
|
347
354
|
void runSinglePhase(ctx, pi, state, phase, { llmSteerEnabled, signal: ctx.signal, preflight })
|
|
355
|
+
.then(async (result) => {
|
|
356
|
+
if (result.status !== "completed")
|
|
357
|
+
return;
|
|
358
|
+
// Refresh state from disk — the sub-agent may have written
|
|
359
|
+
// findings that the validator needs to read.
|
|
360
|
+
const stateForValidation = (await getWorkspaceState(ctx.cwd)) ?? state;
|
|
361
|
+
const validation = await validatePhaseOutput(stateForValidation, phase.id).catch((error) => (error instanceof Error ? error : new Error(String(error))));
|
|
362
|
+
if (validation instanceof Error) {
|
|
363
|
+
if (ctx.hasUI)
|
|
364
|
+
ctx.ui.notify(`Auto-validation error for ${phase.id}: ${validation.message}`, "warning");
|
|
365
|
+
lastFeedbackLines = [`Validation error: ${validation.message}`, "Run `/codecarto-validate` then `/codecarto-complete` manually."];
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (validation.overall === "FAIL" || validation.overall === "MISSING") {
|
|
369
|
+
if (ctx.hasUI)
|
|
370
|
+
ctx.ui.notify(`Phase ${phase.id} validation: ${validation.overall}. Fix the output, then re-run /codecarto-next.`, "warning");
|
|
371
|
+
lastFeedbackLines = buildValidationSummary(validation);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
// PASS or PASS WITH GAPS — auto-complete the phase.
|
|
375
|
+
try {
|
|
376
|
+
const { updatedState, closeoutNotice } = await autoCompletePhase(ctx, validation);
|
|
377
|
+
if (ctx.hasUI) {
|
|
378
|
+
ctx.ui.notify(`Phase ${phase.id} auto-completed (validation: ${validation.overall}).`, validation.overall === "PASS WITH GAPS" ? "warning" : "info");
|
|
379
|
+
if (closeoutNotice)
|
|
380
|
+
ctx.ui.notify(closeoutNotice, "info");
|
|
381
|
+
}
|
|
382
|
+
lastFeedbackLines = [
|
|
383
|
+
`Completed phase: ${validation.phaseId}`,
|
|
384
|
+
`Validation: ${validation.overall}`,
|
|
385
|
+
`Next phase: ${updatedState.status.current_phase}`,
|
|
386
|
+
];
|
|
387
|
+
if (closeoutNotice)
|
|
388
|
+
lastFeedbackLines.push(closeoutNotice);
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
392
|
+
if (ctx.hasUI)
|
|
393
|
+
ctx.ui.notify(`Auto-completion failed for ${phase.id}: ${message}. Run /codecarto-complete manually.`, "warning");
|
|
394
|
+
lastFeedbackLines = [`Auto-completion failed: ${message}`, "Run `/codecarto-complete` manually."];
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
.catch((error) => {
|
|
398
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
399
|
+
if (ctx.hasUI)
|
|
400
|
+
ctx.ui.notify(`Post-phase processing error for ${phase.id}: ${message}`, "warning");
|
|
401
|
+
lastFeedbackLines = [`Post-phase error: ${message}`];
|
|
402
|
+
})
|
|
348
403
|
.finally(() => {
|
|
349
404
|
// Refresh the status widget after the phase resolves so the
|
|
350
405
|
// "Open questions / Carry-forward / Next" lines reflect any
|
|
@@ -486,12 +541,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
486
541
|
return;
|
|
487
542
|
const config = await loadCodecartoConfig(state.workspaceDir);
|
|
488
543
|
if (!config.library.path) {
|
|
489
|
-
ctx.ui.notify("No library.path is configured.
|
|
544
|
+
ctx.ui.notify("No library.path is configured. Create a library directory with a .codecarto-library marker, then set library.path in ~/.codecarto/config.yaml or .codecarto/workflow/config.yaml.", "error");
|
|
490
545
|
return;
|
|
491
546
|
}
|
|
492
547
|
const marker = await discoverLibrary(config.library.path);
|
|
493
548
|
if (!marker) {
|
|
494
|
-
ctx.ui.notify(`No CodeCartographer library at ${config.library.path} (missing .codecarto-library).`, "error");
|
|
549
|
+
ctx.ui.notify(`No CodeCartographer library at ${config.library.path} (missing .codecarto-library). Create a .codecarto-library marker file in that directory.`, "error");
|
|
495
550
|
return;
|
|
496
551
|
}
|
|
497
552
|
const phase = resolvePhase(state, "reimplementation-spec");
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
14
14
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
15
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
16
|
-
import { cp, mkdir, readFile,
|
|
16
|
+
import { cp, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
17
17
|
import { basename, isAbsolute, join } from "node:path";
|
|
18
18
|
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, completeValidatedPhase, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isValidSlug, listEntries, listSkillNames, loadCodecartoConfig, loadYamlFile, normalizeForComparison, PACKAGE_VERSION, packagedWorkspaceDir, pathExists, PhasePreflightError, publishEntry, reindex as libraryReindex, resolvePhase, resolvePipelineChoice, stringifySimpleYaml, validatePhaseOutput, } from "../core/index.js";
|
|
19
19
|
// ---------- input helpers ----------
|
|
@@ -77,9 +77,10 @@ export async function handleInit(args) {
|
|
|
77
77
|
}
|
|
78
78
|
if (targetExists && !sameWorkspace) {
|
|
79
79
|
if (!args.force) {
|
|
80
|
-
throw new McpError(ErrorCode.InvalidRequest, `A .codecarto/ directory already exists at ${targetWorkspaceDir}. Pass force: true to
|
|
80
|
+
throw new McpError(ErrorCode.InvalidRequest, `A .codecarto/ directory already exists at ${targetWorkspaceDir}. Pass force: true to back it up and reinitialize. Warning: this moves all existing findings, handoffs, usage data, closeouts, and phase progress to a .codecarto-backup-TIMESTAMP/ directory.`);
|
|
81
81
|
}
|
|
82
|
-
|
|
82
|
+
const backupDir = join(cwd, `.codecarto-backup-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
83
|
+
await rename(targetWorkspaceDir, backupDir);
|
|
83
84
|
}
|
|
84
85
|
if (!(await pathExists(targetWorkspaceDir))) {
|
|
85
86
|
await mkdir(cwd, { recursive: true });
|
|
@@ -463,7 +464,7 @@ export async function handleLibraryReindex(args) {
|
|
|
463
464
|
const TOOLS = [
|
|
464
465
|
{
|
|
465
466
|
name: "codecarto_init",
|
|
466
|
-
description: "Initialize a CodeCartographer workspace (.codecarto/) in a target repository. Copies the packaged framework template and writes a fresh status.yaml for the chosen pipeline.
|
|
467
|
+
description: "Initialize a CodeCartographer workspace (.codecarto/) in a target repository. Copies the packaged framework template and writes a fresh status.yaml for the chosen pipeline. If .codecarto/ already exists, pass force: true to back up the existing workspace to .codecarto-backup-TIMESTAMP/ and create a fresh one. Warning: backing up moves all existing findings, handoffs, usage data, closeouts, and phase progress to the backup directory.",
|
|
467
468
|
inputSchema: {
|
|
468
469
|
type: "object",
|
|
469
470
|
properties: {
|
|
@@ -474,7 +475,7 @@ const TOOLS = [
|
|
|
474
475
|
},
|
|
475
476
|
force: {
|
|
476
477
|
type: "boolean",
|
|
477
|
-
description: "
|
|
478
|
+
description: "Back up and overwrite an existing .codecarto/ directory if present (default false).",
|
|
478
479
|
},
|
|
479
480
|
},
|
|
480
481
|
required: ["cwd"],
|
|
@@ -625,7 +626,7 @@ const HANDLERS = {
|
|
|
625
626
|
};
|
|
626
627
|
// ---------- server bootstrap ----------
|
|
627
628
|
export function buildServer() {
|
|
628
|
-
const server = new Server({ name: "codecartographer", version:
|
|
629
|
+
const server = new Server({ name: "codecartographer", version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
|
|
629
630
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
630
631
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
631
632
|
const handler = HANDLERS[request.params.name];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codecartographer-pi",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.4",
|
|
4
4
|
"mcpName": "io.github.HuginnIndustries/codecartographer",
|
|
5
5
|
"description": "Evidence-backed reverse engineering and human-gated software planning for Pi and MCP coding agents.",
|
|
6
6
|
"type": "module",
|