codecartographer-pi 0.12.4 → 0.12.9
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/findings/vision-capture/INTERVIEW.md +80 -0
- package/README.md +5 -1
- package/dist/core/library.d.ts +21 -0
- package/dist/core/library.js +23 -1
- package/dist/core/orchestrator-config.d.ts +6 -0
- package/dist/core/orchestrator-config.js +26 -1
- package/dist/core/workspace.d.ts +14 -0
- package/dist/core/workspace.js +57 -2
- package/dist/extensions/codecarto/index.js +145 -1
- package/dist/mcp-server/server.d.ts +77 -0
- package/dist/mcp-server/server.js +256 -1
- package/package.json +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: guided-vision-interview
|
|
3
|
+
description: Conduct a structured product discovery interview to extract a rich vision brief from the user before the synthesis pipeline's vision-capture phase.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Guided Vision Interview
|
|
7
|
+
|
|
8
|
+
Your job is to conduct a conversational product discovery interview that draws out the user's product idea and writes it into `inputs/vision.md` as a rich brief. The synthesis pipeline's vision-capture phase will then structure this brief into a testable vision document.
|
|
9
|
+
|
|
10
|
+
## Interview structure
|
|
11
|
+
|
|
12
|
+
Ask questions in this order. Do not dump all questions at once — ask one or two at a time, wait for the answer, then probe deeper based on what the user said. Skip questions the user has already answered unprompted.
|
|
13
|
+
|
|
14
|
+
### 1. Audience and problem (2-3 questions)
|
|
15
|
+
- Who specifically is this for? (Not "everyone" — get them to name a concrete persona or team)
|
|
16
|
+
- What problem does this solve for them? What are they doing today, and why is it painful?
|
|
17
|
+
- Why are existing solutions insufficient? What's the gap?
|
|
18
|
+
|
|
19
|
+
### 2. Desired outcomes (2-3 questions)
|
|
20
|
+
- What should the user be able to do that they can't today?
|
|
21
|
+
- What does success look like? How would they know it worked?
|
|
22
|
+
- What's the most important outcome — the one thing that must work?
|
|
23
|
+
|
|
24
|
+
### 3. Scope and non-goals (2-3 questions)
|
|
25
|
+
- What's explicitly in scope for this version?
|
|
26
|
+
- What are you deliberately NOT building? What's a non-goal?
|
|
27
|
+
- Is there anything you're tempted to include but know you should defer?
|
|
28
|
+
|
|
29
|
+
### 4. Constraints (2-3 questions)
|
|
30
|
+
- Any hard technical constraints? (language, platform, integration, performance)
|
|
31
|
+
- Any operational constraints? (self-hosted, cloud, offline, privacy, latency)
|
|
32
|
+
- Any timeline or team-size constraints?
|
|
33
|
+
|
|
34
|
+
### 5. Success measures and acceptance (1-2 questions)
|
|
35
|
+
- How would you measure success? What metric or observation would tell you it's working?
|
|
36
|
+
- Can you describe one specific scenario where someone uses this and has a good experience?
|
|
37
|
+
|
|
38
|
+
## Writing the brief
|
|
39
|
+
|
|
40
|
+
After the interview, synthesize the user's answers into `inputs/vision.md`. Use this structure:
|
|
41
|
+
|
|
42
|
+
```markdown
|
|
43
|
+
# Vision brief
|
|
44
|
+
|
|
45
|
+
## Audience
|
|
46
|
+
- Who: [specific persona]
|
|
47
|
+
- Current approach: [what they do today]
|
|
48
|
+
- Pain: [why it's painful]
|
|
49
|
+
|
|
50
|
+
## Problem
|
|
51
|
+
[2-3 sentences describing the core problem]
|
|
52
|
+
|
|
53
|
+
## Desired outcomes
|
|
54
|
+
- [outcome 1]
|
|
55
|
+
- [outcome 2]
|
|
56
|
+
- [outcome 3, if any]
|
|
57
|
+
|
|
58
|
+
## Scope
|
|
59
|
+
- In scope: [list]
|
|
60
|
+
- Non-goals: [list]
|
|
61
|
+
|
|
62
|
+
## Constraints
|
|
63
|
+
- [constraint 1]
|
|
64
|
+
- [constraint 2, if any]
|
|
65
|
+
|
|
66
|
+
## Success measures
|
|
67
|
+
- [measure 1]
|
|
68
|
+
- [acceptance scenario, if provided]
|
|
69
|
+
|
|
70
|
+
## Assumptions and open decisions
|
|
71
|
+
- [anything the user was unsure about — mark as assumption or needs-decision]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Rules
|
|
75
|
+
|
|
76
|
+
- Do not invent product decisions the user didn't make. If something is unclear, ask. If they're unsure, record it as an assumption.
|
|
77
|
+
- Keep the interview conversational — not a form. Follow up on vague answers with a specific probe.
|
|
78
|
+
- If the user gives a very detailed answer up front, skip the questions they already covered and go deeper on the gaps.
|
|
79
|
+
- Do not select library entries or discuss implementation in this interview. That comes later in the synthesis pipeline.
|
|
80
|
+
- After writing the brief, tell the user they can now run `/codecarto-init synthesis` followed by `/codecarto-next` to start the synthesis pipeline.
|
package/README.md
CHANGED
|
@@ -222,7 +222,7 @@ The default is a 7-phase run that splits the defect scan into a mechanical early
|
|
|
222
222
|
| **Architecture only** | 1 | Quick structural overview |
|
|
223
223
|
| **Synthesis** | 4 | Turn a product vision and confirmed library specifications into a provenance-backed implementation plan |
|
|
224
224
|
|
|
225
|
-
|
|
225
|
+
Switch the active pipeline with `/codecarto-switch-pipeline <variant>` (Pi) or `codecarto_switch_pipeline` (MCP). This rewrites `status.yaml` in-place without deleting findings, handoffs, usage data, or closeouts. Phases that exist in both the old and new pipelines preserve their completion status.
|
|
226
226
|
|
|
227
227
|
**On disk:**
|
|
228
228
|
|
|
@@ -287,6 +287,8 @@ Beyond the slash commands, the Pi extension layers on:
|
|
|
287
287
|
|---|---|
|
|
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
|
+
| `/codecarto-vision` | Run a guided product discovery interview to produce `inputs/vision.md` for the synthesis pipeline |
|
|
291
|
+
| `/codecarto-switch-pipeline <variant>` | Switch the active pipeline in-place without losing findings or progress |
|
|
290
292
|
| `/codecarto-status` | Current phase, progress, open questions |
|
|
291
293
|
| `/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
294
|
| `/codecarto-phase <id>` | Force a specific phase, even out of pipeline order |
|
|
@@ -294,6 +296,8 @@ Beyond the slash commands, the Pi extension layers on:
|
|
|
294
296
|
| `/codecarto-complete [phase]` | Validate and atomically apply the phase handoff, canonical status, closeout, and log entry |
|
|
295
297
|
| `/codecarto-skill <name>` | Run a post-pipeline skill once all phases are complete |
|
|
296
298
|
| `/codecarto-publish` | Publish the reimplementation spec to the configured library after reviewing an explicit confirmation preview |
|
|
299
|
+
| `/codecarto-library-init <path> [--namespace <name>]` | Create a library directory with marker and write the config — fixes the first-publish dead end |
|
|
300
|
+
| `/codecarto-config` | Show the effective merged configuration (global + workspace) and library marker status |
|
|
297
301
|
| `/codecarto-usage` | Cumulative + per-phase token usage |
|
|
298
302
|
| `/codecarto-dashboard [--narrate]` | Regenerate `.codecarto/dashboard.html`; `--narrate` for the LLM executive summary |
|
|
299
303
|
|
package/dist/core/library.d.ts
CHANGED
|
@@ -78,6 +78,27 @@ export interface LibraryIndex {
|
|
|
78
78
|
export declare function discoverLibrary(libraryPath: string): Promise<LibraryMarker | null>;
|
|
79
79
|
export declare function readMarker(libraryRoot: string): Promise<LibraryMarker | null>;
|
|
80
80
|
export declare function writeMarker(libraryRoot: string, marker: LibraryMarker): Promise<void>;
|
|
81
|
+
export interface InitLibraryOptions {
|
|
82
|
+
/** Library name (defaults to basename of the path). */
|
|
83
|
+
name?: string;
|
|
84
|
+
/** Visibility level. Default "internal". */
|
|
85
|
+
visibility?: LibraryVisibility;
|
|
86
|
+
/** Whether this is a namespaced (shared) library. Default false. */
|
|
87
|
+
namespaced?: boolean;
|
|
88
|
+
}
|
|
89
|
+
export interface InitLibraryResult {
|
|
90
|
+
libraryPath: string;
|
|
91
|
+
marker: LibraryMarker;
|
|
92
|
+
/** True if the marker already existed (idempotent re-run). */
|
|
93
|
+
alreadyExisted: boolean;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Initialize a CodeCartographer library at the given path: create the
|
|
97
|
+
* directory if needed, write the `.codecarto-library` marker if missing,
|
|
98
|
+
* and return the marker. Idempotent — re-running on an existing library
|
|
99
|
+
* is safe and preserves the existing marker.
|
|
100
|
+
*/
|
|
101
|
+
export declare function initLibrary(libraryPath: string, options?: InitLibraryOptions): Promise<InitLibraryResult>;
|
|
81
102
|
export declare function isValidSlug(slug: string): boolean;
|
|
82
103
|
/**
|
|
83
104
|
* Derive a slug from a source repo URL or path. The last meaningful path
|
package/dist/core/library.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import { createHash } from "node:crypto";
|
|
26
26
|
import { spawn } from "node:child_process";
|
|
27
27
|
import { mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
28
|
-
import { join, resolve } from "node:path";
|
|
28
|
+
import { basename, join, resolve } from "node:path";
|
|
29
29
|
import { isPlainObject, pathExists } from "./utils.js";
|
|
30
30
|
import { parseSimpleYaml, stringifySimpleYaml } from "./yaml.js";
|
|
31
31
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
@@ -86,6 +86,28 @@ function normalizeMarker(raw) {
|
|
|
86
86
|
function isVisibility(v) {
|
|
87
87
|
return v === "internal" || v === "shared" || v === "public";
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Initialize a CodeCartographer library at the given path: create the
|
|
91
|
+
* directory if needed, write the `.codecarto-library` marker if missing,
|
|
92
|
+
* and return the marker. Idempotent — re-running on an existing library
|
|
93
|
+
* is safe and preserves the existing marker.
|
|
94
|
+
*/
|
|
95
|
+
export async function initLibrary(libraryPath, options = {}) {
|
|
96
|
+
const existing = await discoverLibrary(libraryPath);
|
|
97
|
+
if (existing) {
|
|
98
|
+
return { libraryPath, marker: existing, alreadyExisted: true };
|
|
99
|
+
}
|
|
100
|
+
const name = options.name?.trim() || basename(libraryPath);
|
|
101
|
+
const marker = {
|
|
102
|
+
schema_version: MARKER_SCHEMA_VERSION,
|
|
103
|
+
name,
|
|
104
|
+
namespaced: options.namespaced ?? false,
|
|
105
|
+
visibility: options.visibility ?? "internal",
|
|
106
|
+
created_at: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
await writeMarker(libraryPath, marker);
|
|
109
|
+
return { libraryPath, marker, alreadyExisted: false };
|
|
110
|
+
}
|
|
89
111
|
// ─── Slug helpers ───────────────────────────────────────────────────────────
|
|
90
112
|
export function isValidSlug(slug) {
|
|
91
113
|
if (typeof slug !== "string")
|
|
@@ -55,4 +55,10 @@ export declare function loadUserConfig(): Promise<CodecartoConfig>;
|
|
|
55
55
|
* a layer in memory (e.g. "what if library_path were X").
|
|
56
56
|
*/
|
|
57
57
|
export declare function mergeConfig(raw: RawConfig | null | undefined): CodecartoConfig;
|
|
58
|
+
/**
|
|
59
|
+
* Write a `library:` block into a config file (user-global or workspace).
|
|
60
|
+
* Creates the file and parent directories if needed. Preserves any existing
|
|
61
|
+
* `orchestrator:` block. Overwrites the `library:` block if present.
|
|
62
|
+
*/
|
|
63
|
+
export declare function writeLibraryConfig(configPath: string, libraryPath: string, namespace?: string | null, publishConfirm?: boolean): Promise<void>;
|
|
58
64
|
export {};
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
// don't have to expand themselves.
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
17
|
import { join, resolve } from "node:path";
|
|
18
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
18
19
|
import { expandTilde, pathExists } from "./utils.js";
|
|
19
|
-
import { loadYamlFile } from "./yaml.js";
|
|
20
|
+
import { loadYamlFile, parseSimpleYaml, stringifySimpleYaml } from "./yaml.js";
|
|
20
21
|
export const CONFIG_RELATIVE_PATH = "workflow/config.yaml";
|
|
21
22
|
export const USER_CONFIG_DIR = join(homedir(), ".codecarto");
|
|
22
23
|
export const USER_CONFIG_PATH = join(USER_CONFIG_DIR, "config.yaml");
|
|
@@ -110,3 +111,27 @@ function cloneDefault() {
|
|
|
110
111
|
library: { ...DEFAULT_CONFIG.library },
|
|
111
112
|
};
|
|
112
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Write a `library:` block into a config file (user-global or workspace).
|
|
116
|
+
* Creates the file and parent directories if needed. Preserves any existing
|
|
117
|
+
* `orchestrator:` block. Overwrites the `library:` block if present.
|
|
118
|
+
*/
|
|
119
|
+
export async function writeLibraryConfig(configPath, libraryPath, namespace = null, publishConfirm = true) {
|
|
120
|
+
let existing = {};
|
|
121
|
+
if (await pathExists(configPath)) {
|
|
122
|
+
try {
|
|
123
|
+
const raw = await readFile(configPath, "utf8");
|
|
124
|
+
existing = parseSimpleYaml(raw);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Malformed file — start fresh
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const library = { path: libraryPath, publish_confirm: publishConfirm };
|
|
131
|
+
if (namespace)
|
|
132
|
+
library.namespace = namespace;
|
|
133
|
+
const updated = { ...existing, library };
|
|
134
|
+
const dir = configPath.includes("/") ? configPath.slice(0, configPath.lastIndexOf("/")) : ".";
|
|
135
|
+
await mkdir(dir, { recursive: true });
|
|
136
|
+
await writeFile(configPath, `${stringifySimpleYaml(updated)}\n`, "utf8");
|
|
137
|
+
}
|
package/dist/core/workspace.d.ts
CHANGED
|
@@ -11,3 +11,17 @@ export declare function updateStatusAtomically(cwd: string, updater: (state: Wor
|
|
|
11
11
|
handoff?: PhaseHandoff;
|
|
12
12
|
threadLogEntry?: string;
|
|
13
13
|
}): Promise<WorkspaceState>;
|
|
14
|
+
/**
|
|
15
|
+
* Switch the active pipeline in-place without deleting findings, handoffs,
|
|
16
|
+
* usage data, closeouts, or checkpoints. Phases that exist in both the old
|
|
17
|
+
* and new pipelines preserve their completion status, owner notes, open
|
|
18
|
+
* questions, and carry-forward entries. Phases unique to the new pipeline
|
|
19
|
+
* start as pending. Phases unique to the old pipeline are dropped from
|
|
20
|
+
* status.yaml (but their findings remain on disk under findings/).
|
|
21
|
+
*/
|
|
22
|
+
export declare function switchPipeline(cwd: string, newPipelinePath: string): Promise<{
|
|
23
|
+
state: WorkspaceState;
|
|
24
|
+
carried: string[];
|
|
25
|
+
dropped: string[];
|
|
26
|
+
newPhases: string[];
|
|
27
|
+
}>;
|
package/dist/core/workspace.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
// atomic status-update primitive used by /codecarto-complete.
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { appendFile, readFile, rename, writeFile } from "node:fs/promises";
|
|
7
|
-
import { dirname, join, relative } from "node:path";
|
|
7
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import { acquireLock, applyHandoff, normalizeStatus, parseHandoff } from "./status.js";
|
|
9
|
+
import { acquireLock, applyHandoff, createEmptyStatus, normalizeStatus, parseHandoff } from "./status.js";
|
|
10
10
|
import { pathExists } from "./utils.js";
|
|
11
11
|
import { loadYamlFile, stringifySimpleYaml } from "./yaml.js";
|
|
12
12
|
// Walk up from the current file to find the package root. Needed because the
|
|
@@ -129,3 +129,58 @@ export async function updateStatusAtomically(cwd, updater) {
|
|
|
129
129
|
await lock.release();
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Switch the active pipeline in-place without deleting findings, handoffs,
|
|
134
|
+
* usage data, closeouts, or checkpoints. Phases that exist in both the old
|
|
135
|
+
* and new pipelines preserve their completion status, owner notes, open
|
|
136
|
+
* questions, and carry-forward entries. Phases unique to the new pipeline
|
|
137
|
+
* start as pending. Phases unique to the old pipeline are dropped from
|
|
138
|
+
* status.yaml (but their findings remain on disk under findings/).
|
|
139
|
+
*/
|
|
140
|
+
export async function switchPipeline(cwd, newPipelinePath) {
|
|
141
|
+
const workspaceDir = join(cwd, ".codecarto");
|
|
142
|
+
const statusPath = join(workspaceDir, "workflow", "status.yaml");
|
|
143
|
+
const lockPath = `${statusPath}.lock`;
|
|
144
|
+
const lock = await acquireLock(lockPath);
|
|
145
|
+
try {
|
|
146
|
+
const currentState = await getWorkspaceState(cwd);
|
|
147
|
+
if (!currentState) {
|
|
148
|
+
throw new Error("CodeCartographer workspace not found. Run /codecarto-init first.");
|
|
149
|
+
}
|
|
150
|
+
const resolvedPipelinePath = join(workspaceDir, newPipelinePath);
|
|
151
|
+
if (!(await pathExists(resolvedPipelinePath))) {
|
|
152
|
+
throw new Error(`Pipeline not found: ${newPipelinePath}`);
|
|
153
|
+
}
|
|
154
|
+
const newPipeline = await loadYamlFile(resolvedPipelinePath);
|
|
155
|
+
const freshStatus = createEmptyStatus(basename(cwd), newPipelinePath, newPipeline);
|
|
156
|
+
// Preserve phase data for phases that exist in both old and new pipelines.
|
|
157
|
+
const carried = [];
|
|
158
|
+
const oldPhases = currentState.status.phases;
|
|
159
|
+
for (const phaseId of newPipeline.phase_order) {
|
|
160
|
+
if (oldPhases[phaseId]) {
|
|
161
|
+
freshStatus.phases[phaseId] = { ...oldPhases[phaseId] };
|
|
162
|
+
if (oldPhases[phaseId].status === "complete") {
|
|
163
|
+
carried.push(phaseId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Track phases that were in the old pipeline but not the new one.
|
|
168
|
+
const dropped = currentState.pipeline.phase_order.filter((phaseId) => !newPipeline.phase_order.includes(phaseId));
|
|
169
|
+
const newPhases = newPipeline.phase_order.filter((phaseId) => !currentState.pipeline.phase_order.includes(phaseId));
|
|
170
|
+
// Preserve post_pipeline entries from the old status.
|
|
171
|
+
freshStatus.post_pipeline = currentState.status.post_pipeline;
|
|
172
|
+
freshStatus.last_updated = new Date().toISOString();
|
|
173
|
+
assertCanonicalStatus(freshStatus);
|
|
174
|
+
const serialized = `${stringifySimpleYaml(freshStatus)}\n`;
|
|
175
|
+
const tempPath = `${statusPath}.${process.pid}.${Date.now()}.tmp`;
|
|
176
|
+
await writeFile(tempPath, serialized, "utf8");
|
|
177
|
+
await rename(tempPath, statusPath);
|
|
178
|
+
const state = await getWorkspaceState(cwd);
|
|
179
|
+
if (!state)
|
|
180
|
+
throw new Error("Failed to reload workspace state after pipeline switch.");
|
|
181
|
+
return { state, carried, dropped, newPhases };
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
await lock.release();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cp, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { basename, join, resolve } from "node:path";
|
|
3
4
|
import { autoCompletePhase, buildAutoSummary, isPhaseRunning, runAuto, runSinglePhase } from "./auto-runner.js";
|
|
4
5
|
import { disposeAgentsWidget } from "./agent-widget.js";
|
|
@@ -7,7 +8,9 @@ import { narrateDashboard } from "./dashboard-narrator.js";
|
|
|
7
8
|
import { writeDashboard } from "./dashboard-writer.js";
|
|
8
9
|
import { parseNextFlags } from "./next-flags.js";
|
|
9
10
|
import { phaseCompactionExtension } from "./phase-compaction.js";
|
|
10
|
-
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPath, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, packagedWorkspaceDir, pathExists, PACKAGE_VERSION, PhasePreflightError, PIPELINE_ALIASES, publishEntry, resolvePhase, resolvePipelineChoice, runPhasePreflight, stringifySimpleYaml, validatePhaseOutput, } from "../../core/index.js";
|
|
11
|
+
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPath, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, packagedWorkspaceDir, pathExists, PACKAGE_VERSION, PhasePreflightError, PIPELINE_ALIASES, publishEntry, resolvePhase, resolvePipelineChoice, runPhasePreflight, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../../core/index.js";
|
|
12
|
+
import { initLibrary } from "../../core/library.js";
|
|
13
|
+
import { resolveUserConfigPath } from "../../core/orchestrator-config.js";
|
|
11
14
|
const STATUS_WIDGET_ID = "codecarto-widget";
|
|
12
15
|
const STATUS_LINE_ID = "codecarto-status";
|
|
13
16
|
const SAFE_TOOL_NAMES = ["read", "grep", "find", "ls", "edit", "write"];
|
|
@@ -195,6 +198,36 @@ export default function codeCartographerExtension(pi) {
|
|
|
195
198
|
}
|
|
196
199
|
},
|
|
197
200
|
});
|
|
201
|
+
pi.registerCommand("codecarto-vision", {
|
|
202
|
+
description: "Run a guided product discovery interview to produce inputs/vision.md for the synthesis pipeline",
|
|
203
|
+
handler: async (_args, ctx) => {
|
|
204
|
+
const interviewPath = join(ctx.cwd, ".codecarto", "findings", "vision-capture", "INTERVIEW.md");
|
|
205
|
+
if (!(await pathExists(interviewPath))) {
|
|
206
|
+
ctx.ui.notify("Vision interview skill not found. Run /codecarto-init synthesis first.", "warning");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const interviewSkill = await readFile(interviewPath, "utf8");
|
|
210
|
+
const inputsDir = join(ctx.cwd, ".codecarto", "inputs");
|
|
211
|
+
const visionPath = join(inputsDir, "vision.md");
|
|
212
|
+
const visionExists = await pathExists(visionPath);
|
|
213
|
+
const prompt = [
|
|
214
|
+
"Read the interview skill below and conduct a guided product discovery interview with the user.",
|
|
215
|
+
"",
|
|
216
|
+
interviewSkill,
|
|
217
|
+
"",
|
|
218
|
+
`The vision brief should be written to .codecarto/inputs/vision.md${visionExists ? " (it already exists — review and improve it based on the interview)" : " (it does not exist yet — create it)"}.`,
|
|
219
|
+
"After the interview, write the synthesized brief and tell the user to run /codecarto-init synthesis followed by /codecarto-next to start the pipeline.",
|
|
220
|
+
].join("\n");
|
|
221
|
+
if (ctx.isIdle()) {
|
|
222
|
+
pi.sendUserMessage(prompt);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
226
|
+
}
|
|
227
|
+
lastFeedbackLines = ["Vision interview started — answer the questions in chat."];
|
|
228
|
+
ctx.ui.notify("Vision interview queued — answer the questions in the chat.", "info");
|
|
229
|
+
},
|
|
230
|
+
});
|
|
198
231
|
pi.registerCommand("codecarto-init", {
|
|
199
232
|
description: "Initialize .codecarto/ in the current repository",
|
|
200
233
|
getArgumentCompletions: (prefix) => {
|
|
@@ -268,6 +301,57 @@ export default function codeCartographerExtension(pi) {
|
|
|
268
301
|
ctx.ui.notify(`CodeCartographer phase: ${nextPhase}`, "info");
|
|
269
302
|
},
|
|
270
303
|
});
|
|
304
|
+
pi.registerCommand("codecarto-switch-pipeline", {
|
|
305
|
+
description: "Switch the active pipeline without losing findings or progress: /codecarto-switch-pipeline <variant>",
|
|
306
|
+
getArgumentCompletions: (prefix) => {
|
|
307
|
+
const items = Object.keys(PIPELINE_ALIASES)
|
|
308
|
+
.filter((value) => value.startsWith(prefix))
|
|
309
|
+
.map((value) => ({ value, label: value }));
|
|
310
|
+
return items.length > 0 ? items : null;
|
|
311
|
+
},
|
|
312
|
+
handler: async (args, ctx) => {
|
|
313
|
+
const trimmedArgs = args.trim();
|
|
314
|
+
if (!trimmedArgs) {
|
|
315
|
+
ctx.ui.notify("Usage: /codecarto-switch-pipeline <variant> (e.g. lite, full, synthesis)", "warning");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const pipelineChoice = resolvePipelineChoice(trimmedArgs);
|
|
319
|
+
if (!pipelineChoice) {
|
|
320
|
+
ctx.ui.notify(`Unknown pipeline: ${trimmedArgs}`, "error");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const state = await ensureWorkspaceState(ctx);
|
|
324
|
+
if (!state)
|
|
325
|
+
return;
|
|
326
|
+
const currentPipeline = state.status.pipeline;
|
|
327
|
+
if (currentPipeline === pipelineChoice) {
|
|
328
|
+
ctx.ui.notify(`Already on pipeline: ${getPipelineLabel(pipelineChoice)}`, "info");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
const result = await switchPipeline(ctx.cwd, pipelineChoice);
|
|
333
|
+
const lines = [
|
|
334
|
+
`Switched pipeline: ${getPipelineLabel(pipelineChoice)}`,
|
|
335
|
+
];
|
|
336
|
+
if (result.carried.length > 0)
|
|
337
|
+
lines.push(`Phases preserved (completed): ${result.carried.join(", ")}`);
|
|
338
|
+
if (result.newPhases.length > 0)
|
|
339
|
+
lines.push(`New phases: ${result.newPhases.join(", ")}`);
|
|
340
|
+
if (result.dropped.length > 0)
|
|
341
|
+
lines.push(`Phases not in new pipeline: ${result.dropped.join(", ")} (findings remain on disk)`);
|
|
342
|
+
lastFeedbackLines = lines;
|
|
343
|
+
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
344
|
+
ctx.ui.notify(`Switched to pipeline: ${getPipelineLabel(pipelineChoice)}`, "info");
|
|
345
|
+
void writeDashboard(ctx.cwd, PACKAGE_VERSION);
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
349
|
+
lastFeedbackLines = [message];
|
|
350
|
+
setUiState(ctx, state, lastFeedbackLines);
|
|
351
|
+
ctx.ui.notify(message, "error");
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
});
|
|
271
355
|
pi.registerCommand("codecarto-next", {
|
|
272
356
|
description: "Run the next eligible CodeCartographer phase as a sub-agent. Flags: --llm-steer / --no-llm-steer / --auto [--strict]",
|
|
273
357
|
getArgumentCompletions: (prefix) => {
|
|
@@ -600,6 +684,66 @@ export default function codeCartographerExtension(pi) {
|
|
|
600
684
|
}
|
|
601
685
|
},
|
|
602
686
|
});
|
|
687
|
+
pi.registerCommand("codecarto-library-init", {
|
|
688
|
+
description: "Initialize a CodeCartographer library and configure it: /codecarto-library-init <path> [--namespace <name>]",
|
|
689
|
+
handler: async (args, ctx) => {
|
|
690
|
+
const parts = args.trim().split(/\s+/);
|
|
691
|
+
const pathArg = parts[0];
|
|
692
|
+
const namespaceIdx = parts.indexOf("--namespace");
|
|
693
|
+
const namespace = namespaceIdx >= 0 ? parts[namespaceIdx + 1] : null;
|
|
694
|
+
if (!pathArg) {
|
|
695
|
+
ctx.ui.notify("Usage: /codecarto-library-init <path> [--namespace <name>]", "warning");
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const libraryPath = pathArg.startsWith("~") ? join(homedir(), pathArg.slice(1)) : resolve(pathArg);
|
|
699
|
+
try {
|
|
700
|
+
const result = await initLibrary(libraryPath, {
|
|
701
|
+
namespaced: !!namespace,
|
|
702
|
+
...(namespace ? {} : {}),
|
|
703
|
+
});
|
|
704
|
+
// Write the config to the user-global location
|
|
705
|
+
const configPath = resolveUserConfigPath();
|
|
706
|
+
await writeLibraryConfig(configPath, libraryPath, namespace);
|
|
707
|
+
const msg = result.alreadyExisted
|
|
708
|
+
? `Library already exists at ${libraryPath} (marker preserved). Config updated.`
|
|
709
|
+
: `Created library at ${libraryPath} with marker "${result.marker.name}".`;
|
|
710
|
+
lastFeedbackLines = [msg, `Config written to ${configPath}`];
|
|
711
|
+
if (ctx.hasUI) {
|
|
712
|
+
ctx.ui.notify(msg, "info");
|
|
713
|
+
ctx.ui.notify(`Config written to ${configPath}`, "info");
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
catch (error) {
|
|
717
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
718
|
+
ctx.ui.notify(`Library init failed: ${message}`, "error");
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
pi.registerCommand("codecarto-config", {
|
|
723
|
+
description: "Show the effective merged CodeCartographer configuration (global + workspace)",
|
|
724
|
+
handler: async (_args, ctx) => {
|
|
725
|
+
const state = await ensureWorkspaceState(ctx);
|
|
726
|
+
const config = await loadCodecartoConfig(state ? state.workspaceDir : join(ctx.cwd, ".codecarto"));
|
|
727
|
+
const lines = [
|
|
728
|
+
"Effective CodeCartographer configuration:",
|
|
729
|
+
` library.path: ${config.library.path ?? "(not set)"}`,
|
|
730
|
+
` library.namespace: ${config.library.namespace ?? "(not set)"}`,
|
|
731
|
+
` library.publish_confirm: ${config.library.publish_confirm}`,
|
|
732
|
+
` orchestrator.llm_steer_next_phase: ${config.orchestrator.llm_steer_next_phase}`,
|
|
733
|
+
"",
|
|
734
|
+
` User-global config: ${resolveUserConfigPath()}`,
|
|
735
|
+
` Workspace config: ${state ? join(state.workspaceDir, "workflow/config.yaml") : "(no workspace)"}`,
|
|
736
|
+
];
|
|
737
|
+
if (config.library.path) {
|
|
738
|
+
const marker = await discoverLibrary(config.library.path);
|
|
739
|
+
lines.push(` Library marker: ${marker ? `found ("${marker.name}", namespaced: ${marker.namespaced})` : "MISSING — run /codecarto-library-init"}`);
|
|
740
|
+
}
|
|
741
|
+
lastFeedbackLines = lines;
|
|
742
|
+
if (state)
|
|
743
|
+
setUiState(ctx, state, lastFeedbackLines);
|
|
744
|
+
ctx.ui.notify("Configuration shown in status widget.", "info");
|
|
745
|
+
},
|
|
746
|
+
});
|
|
603
747
|
pi.registerCommand("codecarto-usage", {
|
|
604
748
|
description: "Show cumulative + per-phase token usage from local phase runs",
|
|
605
749
|
handler: async (_args, ctx) => {
|
|
@@ -19,6 +19,16 @@ export declare function handleStatus(args: {
|
|
|
19
19
|
}>;
|
|
20
20
|
structuredContent?: Record<string, unknown>;
|
|
21
21
|
}>;
|
|
22
|
+
export declare function handleSwitchPipeline(args: {
|
|
23
|
+
cwd: string;
|
|
24
|
+
pipeline: string;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
content: Array<{
|
|
27
|
+
type: "text";
|
|
28
|
+
text: string;
|
|
29
|
+
}>;
|
|
30
|
+
structuredContent?: Record<string, unknown>;
|
|
31
|
+
}>;
|
|
22
32
|
export declare function handleNext(args: {
|
|
23
33
|
cwd: string;
|
|
24
34
|
}): Promise<{
|
|
@@ -89,6 +99,73 @@ export declare function handleLibraryReindex(args: Record<string, unknown>): Pro
|
|
|
89
99
|
}>;
|
|
90
100
|
structuredContent?: Record<string, unknown>;
|
|
91
101
|
}>;
|
|
102
|
+
export declare function handleLibraryInit(args: {
|
|
103
|
+
library_path: string;
|
|
104
|
+
name?: string;
|
|
105
|
+
namespace?: string;
|
|
106
|
+
cwd?: string;
|
|
107
|
+
}): Promise<{
|
|
108
|
+
content: Array<{
|
|
109
|
+
type: "text";
|
|
110
|
+
text: string;
|
|
111
|
+
}>;
|
|
112
|
+
structuredContent?: Record<string, unknown>;
|
|
113
|
+
}>;
|
|
114
|
+
export declare function handleVision(args: {
|
|
115
|
+
cwd: string;
|
|
116
|
+
raw_text: string;
|
|
117
|
+
}): Promise<{
|
|
118
|
+
content: Array<{
|
|
119
|
+
type: "text";
|
|
120
|
+
text: string;
|
|
121
|
+
}>;
|
|
122
|
+
structuredContent?: Record<string, unknown>;
|
|
123
|
+
}>;
|
|
124
|
+
export declare function handleConfig(args: {
|
|
125
|
+
cwd?: string;
|
|
126
|
+
}): Promise<{
|
|
127
|
+
content: Array<{
|
|
128
|
+
type: "text";
|
|
129
|
+
text: string;
|
|
130
|
+
}>;
|
|
131
|
+
structuredContent?: Record<string, unknown>;
|
|
132
|
+
}>;
|
|
133
|
+
export declare function handleOpen(args: {
|
|
134
|
+
cwd: string;
|
|
135
|
+
}): Promise<{
|
|
136
|
+
content: Array<{
|
|
137
|
+
type: "text";
|
|
138
|
+
text: string;
|
|
139
|
+
}>;
|
|
140
|
+
structuredContent?: Record<string, unknown>;
|
|
141
|
+
}>;
|
|
142
|
+
export declare function handleUsage(args: {
|
|
143
|
+
cwd: string;
|
|
144
|
+
}): Promise<{
|
|
145
|
+
content: Array<{
|
|
146
|
+
type: "text";
|
|
147
|
+
text: string;
|
|
148
|
+
}>;
|
|
149
|
+
structuredContent?: Record<string, unknown>;
|
|
150
|
+
}>;
|
|
151
|
+
export declare function handleDashboard(args: {
|
|
152
|
+
cwd: string;
|
|
153
|
+
}): Promise<{
|
|
154
|
+
content: Array<{
|
|
155
|
+
type: "text";
|
|
156
|
+
text: string;
|
|
157
|
+
}>;
|
|
158
|
+
structuredContent?: Record<string, unknown>;
|
|
159
|
+
}>;
|
|
160
|
+
export declare function handleListSkills(args: {
|
|
161
|
+
cwd: string;
|
|
162
|
+
}): Promise<{
|
|
163
|
+
content: Array<{
|
|
164
|
+
type: "text";
|
|
165
|
+
text: string;
|
|
166
|
+
}>;
|
|
167
|
+
structuredContent?: Record<string, unknown>;
|
|
168
|
+
}>;
|
|
92
169
|
export declare function buildServer(): Server<{
|
|
93
170
|
method: string;
|
|
94
171
|
params?: {
|
|
@@ -15,7 +15,10 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
15
15
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
16
16
|
import { cp, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
17
17
|
import { basename, isAbsolute, join } from "node:path";
|
|
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";
|
|
18
|
+
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, completeValidatedPhase, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isValidSlug, listEntries, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, PACKAGE_VERSION, packagedWorkspaceDir, pathExists, PhasePreflightError, publishEntry, reindex as libraryReindex, resolvePhase, resolvePipelineChoice, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../core/index.js";
|
|
19
|
+
import { initLibrary } from "../core/library.js";
|
|
20
|
+
import { loadUserConfig, resolveUserConfigPath } from "../core/orchestrator-config.js";
|
|
21
|
+
import { writeDashboard } from "../extensions/codecarto/dashboard-writer.js";
|
|
19
22
|
// ---------- input helpers ----------
|
|
20
23
|
async function validateCwd(cwd) {
|
|
21
24
|
if (typeof cwd !== "string" || !cwd.trim()) {
|
|
@@ -138,6 +141,31 @@ export async function handleStatus(args) {
|
|
|
138
141
|
nextActions: state.status.next_actions,
|
|
139
142
|
});
|
|
140
143
|
}
|
|
144
|
+
export async function handleSwitchPipeline(args) {
|
|
145
|
+
const cwd = await validateCwd(args.cwd);
|
|
146
|
+
const state = await requireWorkspace(cwd);
|
|
147
|
+
const pipelineChoice = resolvePipelineChoice(args.pipeline);
|
|
148
|
+
if (!pipelineChoice) {
|
|
149
|
+
throw new McpError(ErrorCode.InvalidRequest, `Unknown pipeline: ${args.pipeline}`);
|
|
150
|
+
}
|
|
151
|
+
if (state.status.pipeline === pipelineChoice) {
|
|
152
|
+
return textResult(`Already on pipeline: ${getPipelineLabel(pipelineChoice)}`, { pipeline: getPipelineLabel(pipelineChoice) });
|
|
153
|
+
}
|
|
154
|
+
const result = await switchPipeline(cwd, pipelineChoice);
|
|
155
|
+
const lines = [`Switched pipeline: ${getPipelineLabel(pipelineChoice)}`];
|
|
156
|
+
if (result.carried.length > 0)
|
|
157
|
+
lines.push(`Phases preserved (completed): ${result.carried.join(", ")}`);
|
|
158
|
+
if (result.newPhases.length > 0)
|
|
159
|
+
lines.push(`New phases: ${result.newPhases.join(", ")}`);
|
|
160
|
+
if (result.dropped.length > 0)
|
|
161
|
+
lines.push(`Phases not in new pipeline: ${result.dropped.join(", ")} (findings remain on disk)`);
|
|
162
|
+
return textResult(lines.join("\n"), {
|
|
163
|
+
pipeline: getPipelineLabel(pipelineChoice),
|
|
164
|
+
carried: result.carried,
|
|
165
|
+
newPhases: result.newPhases,
|
|
166
|
+
dropped: result.dropped,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
141
169
|
export async function handleNext(args) {
|
|
142
170
|
const cwd = await validateCwd(args.cwd);
|
|
143
171
|
const state = await requireWorkspace(cwd);
|
|
@@ -460,6 +488,139 @@ export async function handleLibraryReindex(args) {
|
|
|
460
488
|
namespaces: index.namespaces,
|
|
461
489
|
});
|
|
462
490
|
}
|
|
491
|
+
export async function handleLibraryInit(args) {
|
|
492
|
+
if (!args.library_path || typeof args.library_path !== "string") {
|
|
493
|
+
throw new McpError(ErrorCode.InvalidParams, "library_path is required.");
|
|
494
|
+
}
|
|
495
|
+
const libraryPath = args.library_path;
|
|
496
|
+
const namespaced = !!args.namespace;
|
|
497
|
+
const result = await initLibrary(libraryPath, {
|
|
498
|
+
name: args.name,
|
|
499
|
+
namespaced,
|
|
500
|
+
});
|
|
501
|
+
// Write config to user-global location
|
|
502
|
+
const configPath = resolveUserConfigPath();
|
|
503
|
+
await writeLibraryConfig(configPath, libraryPath, args.namespace ?? null);
|
|
504
|
+
const msg = result.alreadyExisted
|
|
505
|
+
? `Library already exists at ${libraryPath} (marker preserved). Config written to ${configPath}.`
|
|
506
|
+
: `Created library at ${libraryPath} with marker "${result.marker.name}". Config written to ${configPath}.`;
|
|
507
|
+
return textResult(msg, {
|
|
508
|
+
libraryPath,
|
|
509
|
+
markerName: result.marker.name,
|
|
510
|
+
namespaced: result.marker.namespaced,
|
|
511
|
+
alreadyExisted: result.alreadyExisted,
|
|
512
|
+
configPath,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
export async function handleVision(args) {
|
|
516
|
+
const cwd = await validateCwd(args.cwd);
|
|
517
|
+
const workspaceDir = join(cwd, ".codecarto");
|
|
518
|
+
const interviewPath = join(workspaceDir, "findings", "vision-capture", "INTERVIEW.md");
|
|
519
|
+
const visionPath = join(workspaceDir, "inputs", "vision.md");
|
|
520
|
+
if (!(await pathExists(interviewPath))) {
|
|
521
|
+
throw new McpError(ErrorCode.InvalidRequest, "Vision interview skill not found. Run codecarto_init with the synthesis pipeline first.");
|
|
522
|
+
}
|
|
523
|
+
const interviewSkill = await readFile(interviewPath, "utf8");
|
|
524
|
+
const prompt = [
|
|
525
|
+
"Read the interview skill below and use it to structure the user's raw product text into a vision brief.",
|
|
526
|
+
"",
|
|
527
|
+
interviewSkill,
|
|
528
|
+
"",
|
|
529
|
+
"The user's raw product text is:",
|
|
530
|
+
"",
|
|
531
|
+
args.raw_text,
|
|
532
|
+
"",
|
|
533
|
+
`Write the synthesized brief to ${visionPath}.`,
|
|
534
|
+
].join("\n");
|
|
535
|
+
return textResult(prompt, {
|
|
536
|
+
cwd,
|
|
537
|
+
visionPath,
|
|
538
|
+
interviewPath,
|
|
539
|
+
note: "Feed this prompt to your agent. The agent will write the structured vision brief to inputs/vision.md.",
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
export async function handleConfig(args) {
|
|
543
|
+
const config = args.cwd
|
|
544
|
+
? await loadCodecartoConfig(join(args.cwd, ".codecarto"))
|
|
545
|
+
: await loadUserConfig();
|
|
546
|
+
const userConfigPath = resolveUserConfigPath();
|
|
547
|
+
const workspaceConfigPath = args.cwd ? join(args.cwd, ".codecarto", "workflow", "config.yaml") : null;
|
|
548
|
+
let markerStatus = "not configured";
|
|
549
|
+
if (config.library.path) {
|
|
550
|
+
const marker = await discoverLibrary(config.library.path);
|
|
551
|
+
markerStatus = marker ? `found ("${marker.name}", namespaced: ${marker.namespaced})` : "MISSING";
|
|
552
|
+
}
|
|
553
|
+
return textResult([
|
|
554
|
+
"Effective CodeCartographer configuration:",
|
|
555
|
+
` library.path: ${config.library.path ?? "(not set)"}`,
|
|
556
|
+
` library.namespace: ${config.library.namespace ?? "(not set)"}`,
|
|
557
|
+
` library.publish_confirm: ${config.library.publish_confirm}`,
|
|
558
|
+
` orchestrator.llm_steer_next_phase: ${config.orchestrator.llm_steer_next_phase}`,
|
|
559
|
+
` Library marker: ${markerStatus}`,
|
|
560
|
+
` User-global config: ${userConfigPath}`,
|
|
561
|
+
` Workspace config: ${workspaceConfigPath ?? "(no cwd provided)"}`,
|
|
562
|
+
].join("\n"), {
|
|
563
|
+
libraryPath: config.library.path,
|
|
564
|
+
libraryNamespace: config.library.namespace,
|
|
565
|
+
publishConfirm: config.library.publish_confirm,
|
|
566
|
+
llmSteerNextPhase: config.orchestrator.llm_steer_next_phase,
|
|
567
|
+
userConfigPath,
|
|
568
|
+
workspaceConfigPath,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
// ---------- MCP parity handlers: open, usage, dashboard, list_skills ----------
|
|
572
|
+
export async function handleOpen(args) {
|
|
573
|
+
const cwd = await validateCwd(args.cwd);
|
|
574
|
+
const workspaceDir = join(cwd, ".codecarto");
|
|
575
|
+
if (!(await pathExists(join(workspaceDir, "workflow", "status.yaml")))) {
|
|
576
|
+
throw new McpError(ErrorCode.InvalidRequest, "No existing CodeCartographer workspace found. Run codecarto_init first.");
|
|
577
|
+
}
|
|
578
|
+
const state = await requireWorkspace(cwd);
|
|
579
|
+
const nextPhase = getNextEligiblePhase(state)?.id ?? "complete";
|
|
580
|
+
return textResult(`Opened existing CodeCartographer workspace: ${getPipelineLabel(state.status.pipeline)}. Current phase: ${nextPhase}.`, { pipeline: getPipelineLabel(state.status.pipeline), currentPhase: nextPhase });
|
|
581
|
+
}
|
|
582
|
+
export async function handleUsage(args) {
|
|
583
|
+
const cwd = await validateCwd(args.cwd);
|
|
584
|
+
const state = await requireWorkspace(cwd);
|
|
585
|
+
const usage = await loadUsage(state.workspaceDir);
|
|
586
|
+
if (usage.runs.length === 0) {
|
|
587
|
+
return textResult("No phase runs recorded yet.", { runs: 0 });
|
|
588
|
+
}
|
|
589
|
+
const totals = computeTotals(usage);
|
|
590
|
+
const perPhase = computePerPhaseTotals(usage);
|
|
591
|
+
const lines = [
|
|
592
|
+
`Total runs: ${totals.runs}`,
|
|
593
|
+
`Total tokens: ${totals.tokens.input} in / ${totals.tokens.output} out / ${totals.tokens.cache_write} cache-write`,
|
|
594
|
+
`Total duration: ${totals.duration_ms}ms / ${totals.tool_uses} tool uses`,
|
|
595
|
+
"",
|
|
596
|
+
"Per-phase totals:",
|
|
597
|
+
];
|
|
598
|
+
for (const [phaseId, t] of perPhase) {
|
|
599
|
+
lines.push(` ${phaseId}: ${t.runs} run(s), ${t.tokens.input + t.tokens.output} tokens, ${t.tool_uses} tool uses, ${t.duration_ms}ms`);
|
|
600
|
+
}
|
|
601
|
+
return textResult(lines.join("\n"), {
|
|
602
|
+
runs: totals.runs,
|
|
603
|
+
tokens: totals.tokens,
|
|
604
|
+
toolUses: totals.tool_uses,
|
|
605
|
+
durationMs: totals.duration_ms,
|
|
606
|
+
perPhase: Object.fromEntries(perPhase),
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
export async function handleDashboard(args) {
|
|
610
|
+
const cwd = await validateCwd(args.cwd);
|
|
611
|
+
await requireWorkspace(cwd);
|
|
612
|
+
await writeDashboard(cwd, PACKAGE_VERSION);
|
|
613
|
+
return textResult("Dashboard regenerated: .codecarto/dashboard.html", { path: ".codecarto/dashboard.html" });
|
|
614
|
+
}
|
|
615
|
+
export async function handleListSkills(args) {
|
|
616
|
+
const cwd = await validateCwd(args.cwd);
|
|
617
|
+
const state = await requireWorkspace(cwd);
|
|
618
|
+
const skills = await listSkillNames(state.workspaceDir);
|
|
619
|
+
const lines = skills.length > 0
|
|
620
|
+
? [`Available skills (${skills.length}):`, ...skills.map((s) => ` - ${s}`)]
|
|
621
|
+
: ["No skills installed."];
|
|
622
|
+
return textResult(lines.join("\n"), { skills });
|
|
623
|
+
}
|
|
463
624
|
// ---------- tool registry ----------
|
|
464
625
|
const TOOLS = [
|
|
465
626
|
{
|
|
@@ -481,6 +642,21 @@ const TOOLS = [
|
|
|
481
642
|
required: ["cwd"],
|
|
482
643
|
},
|
|
483
644
|
},
|
|
645
|
+
{
|
|
646
|
+
name: "codecarto_switch_pipeline",
|
|
647
|
+
description: "Switch the active pipeline in-place without losing findings, handoffs, usage data, or phase progress. Phases that exist in both the old and new pipelines preserve their completion status. Phases unique to the new pipeline start as pending. Pass a pipeline alias (e.g. lite, full, synthesis) or a workflow/*.yaml path.",
|
|
648
|
+
inputSchema: {
|
|
649
|
+
type: "object",
|
|
650
|
+
properties: {
|
|
651
|
+
cwd: { type: "string", description: "Absolute path to the target repository." },
|
|
652
|
+
pipeline: {
|
|
653
|
+
type: "string",
|
|
654
|
+
description: "Pipeline alias (e.g. lite, full, synthesis, architecture-only, defect-scan) or workflow/*.yaml path.",
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
required: ["cwd", "pipeline"],
|
|
658
|
+
},
|
|
659
|
+
},
|
|
484
660
|
{
|
|
485
661
|
name: "codecarto_status",
|
|
486
662
|
description: "Show the current CodeCartographer phase, active pipeline, and progress for a target repository.",
|
|
@@ -611,10 +787,82 @@ const TOOLS = [
|
|
|
611
787
|
},
|
|
612
788
|
},
|
|
613
789
|
},
|
|
790
|
+
{
|
|
791
|
+
name: "codecarto_vision",
|
|
792
|
+
description: "Generate a structured vision brief from raw product text using the guided interview skill. Returns a prompt the host should feed to its agent to write inputs/vision.md. Requires a synthesis workspace (run codecarto_init with the synthesis pipeline first).",
|
|
793
|
+
inputSchema: {
|
|
794
|
+
type: "object",
|
|
795
|
+
properties: {
|
|
796
|
+
cwd: { type: "string", description: "Absolute path to the target repository with a .codecarto/ synthesis workspace." },
|
|
797
|
+
raw_text: { type: "string", description: "The user's raw product text — audience, problem, desired outcomes, constraints, non-goals." },
|
|
798
|
+
},
|
|
799
|
+
required: ["cwd", "raw_text"],
|
|
800
|
+
},
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
name: "codecarto_library_init",
|
|
804
|
+
description: "Initialize a CodeCartographer library at the given path: create the directory, write the .codecarto-library marker, and write the library.path into the user-global config. Idempotent — safe to re-run on an existing library. Pass a namespace to create a namespaced (shared) library.",
|
|
805
|
+
inputSchema: {
|
|
806
|
+
type: "object",
|
|
807
|
+
properties: {
|
|
808
|
+
library_path: { type: "string", description: "Absolute path for the library directory." },
|
|
809
|
+
name: { type: "string", description: "Library name (defaults to the directory basename)." },
|
|
810
|
+
namespace: { type: "string", description: "Default namespace for a namespaced (shared) library." },
|
|
811
|
+
},
|
|
812
|
+
required: ["library_path"],
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
name: "codecarto_config",
|
|
817
|
+
description: "Show the effective merged CodeCartographer configuration (library.path, library.namespace, publish_confirm, llm_steer_next_phase) and whether the library marker was found. Pass cwd to include workspace-level config in the merge.",
|
|
818
|
+
inputSchema: {
|
|
819
|
+
type: "object",
|
|
820
|
+
properties: {
|
|
821
|
+
cwd: { type: "string", description: "Absolute path to a repository with a .codecarto/ workspace (optional)." },
|
|
822
|
+
},
|
|
823
|
+
},
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
name: "codecarto_open",
|
|
827
|
+
description: "Activate an existing CodeCartographer workspace without resetting state. Returns the current pipeline and phase.",
|
|
828
|
+
inputSchema: {
|
|
829
|
+
type: "object",
|
|
830
|
+
properties: { cwd: { type: "string", description: "Absolute path to the target repository." } },
|
|
831
|
+
required: ["cwd"],
|
|
832
|
+
},
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
name: "codecarto_usage",
|
|
836
|
+
description: "Show cumulative and per-phase token usage from local phase runs.",
|
|
837
|
+
inputSchema: {
|
|
838
|
+
type: "object",
|
|
839
|
+
properties: { cwd: { type: "string", description: "Absolute path to the target repository." } },
|
|
840
|
+
required: ["cwd"],
|
|
841
|
+
},
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
name: "codecarto_dashboard",
|
|
845
|
+
description: "Regenerate .codecarto/dashboard.html from the current workspace state.",
|
|
846
|
+
inputSchema: {
|
|
847
|
+
type: "object",
|
|
848
|
+
properties: { cwd: { type: "string", description: "Absolute path to the target repository." } },
|
|
849
|
+
required: ["cwd"],
|
|
850
|
+
},
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
name: "codecarto_list_skills",
|
|
854
|
+
description: "List available post-pipeline skills installed in the workspace.",
|
|
855
|
+
inputSchema: {
|
|
856
|
+
type: "object",
|
|
857
|
+
properties: { cwd: { type: "string", description: "Absolute path to the target repository." } },
|
|
858
|
+
required: ["cwd"],
|
|
859
|
+
},
|
|
860
|
+
},
|
|
614
861
|
];
|
|
615
862
|
const HANDLERS = {
|
|
616
863
|
codecarto_init: handleInit,
|
|
617
864
|
codecarto_status: handleStatus,
|
|
865
|
+
codecarto_switch_pipeline: handleSwitchPipeline,
|
|
618
866
|
codecarto_next: handleNext,
|
|
619
867
|
codecarto_phase: handlePhase,
|
|
620
868
|
codecarto_validate: handleValidate,
|
|
@@ -623,6 +871,13 @@ const HANDLERS = {
|
|
|
623
871
|
codecarto_publish: handlePublish,
|
|
624
872
|
codecarto_library_list: handleLibraryList,
|
|
625
873
|
codecarto_library_reindex: handleLibraryReindex,
|
|
874
|
+
codecarto_library_init: handleLibraryInit,
|
|
875
|
+
codecarto_config: handleConfig,
|
|
876
|
+
codecarto_vision: handleVision,
|
|
877
|
+
codecarto_open: handleOpen,
|
|
878
|
+
codecarto_usage: handleUsage,
|
|
879
|
+
codecarto_dashboard: handleDashboard,
|
|
880
|
+
codecarto_list_skills: handleListSkills,
|
|
626
881
|
};
|
|
627
882
|
// ---------- server bootstrap ----------
|
|
628
883
|
export function buildServer() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codecartographer-pi",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.9",
|
|
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",
|