codecartographer-pi 0.12.11 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codecarto/CONTRIBUTING.md +1 -1
- package/.codecarto/GUIDE.md +4 -3
- package/.codecarto/NEW_THREAD_BLURB.md +9 -10
- package/.codecarto/findings/defect-scan-mechanical/SKILL.md +1 -1
- package/.codecarto/skills/spec-delta-application/SKILL.md +1 -1
- package/.codecarto/templates/architecture-map.md +2 -1
- package/.codecarto/templates/behavioral-contracts.md +2 -1
- package/.codecarto/templates/closeout-template.md +5 -2
- package/.codecarto/templates/mechanical-defects.md +4 -2
- package/.codecarto/templates/protocols-and-state.md +2 -1
- package/.codecarto/templates/reverse-engineering-bundle.md +2 -1
- package/.codecarto/templates/semantic-defects.md +3 -1
- package/.codecarto/templates/thread-log-entry-template.md +11 -13
- package/.codecarto/workflow/VALIDATE.md +17 -14
- package/.codecarto/workflow/pipeline-architecture-only.yaml +2 -2
- package/.codecarto/workflow/pipeline-defect-scan.yaml +4 -4
- package/.codecarto/workflow/pipeline-full-with-audit.yaml +12 -12
- package/.codecarto/workflow/pipeline-full-with-deep-audit.yaml +15 -15
- package/.codecarto/workflow/pipeline-lite.yaml +6 -6
- package/.codecarto/workflow/pipeline.yaml +10 -10
- package/.codecarto/workflow/scaffold-version.yaml +6 -0
- package/README.md +25 -1
- package/agent-skill/codecartographer/SKILL.md +143 -0
- package/agent-skill/codecartographer/references/carrying-results-forward.md +53 -0
- package/agent-skill/codecartographer/references/deep-audit-synthesis.md +54 -0
- package/agent-skill/codecartographer/references/executors.md +75 -0
- package/agent-skill/codecartographer/references/handoff-contract.md +80 -0
- package/agent-skill/codecartographer/references/kernel-first-rewrite.md +56 -0
- package/agent-skill/codecartographer/references/phase-recovery.md +48 -0
- package/agent-skill/codecartographer/references/pipeline-selection.md +38 -0
- package/dist/core/completion.js +13 -0
- package/dist/core/guide.d.ts +18 -0
- package/dist/core/guide.js +50 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/prompts.js +20 -1
- package/dist/core/types.d.ts +2 -0
- package/dist/core/workspace.d.ts +11 -0
- package/dist/core/workspace.js +56 -1
- package/dist/extensions/codecarto/index.js +4 -1
- package/dist/mcp-server/server.d.ts +14 -0
- package/dist/mcp-server/server.js +47 -10
- package/package.json +12 -3
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Packaged agent-skill directory. Wrappers serve its contents through codecarto_guide. */
|
|
2
|
+
export declare const packagedAgentSkillDir: string;
|
|
3
|
+
/** A guide document: the main skill or one of its topic references. */
|
|
4
|
+
export type GuideDocument = {
|
|
5
|
+
/** `overview` for SKILL.md, otherwise the reference's basename without `.md`. */
|
|
6
|
+
topic: string;
|
|
7
|
+
/** Markdown body, with SKILL.md's installer frontmatter stripped. */
|
|
8
|
+
content: string;
|
|
9
|
+
};
|
|
10
|
+
/** Topic names available to {@link readGuide}, `overview` first. */
|
|
11
|
+
export declare function listGuideTopics(): Promise<string[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Read one guide document.
|
|
14
|
+
* @param topic - `overview` (default) for the main skill, or a reference name from {@link listGuideTopics}.
|
|
15
|
+
* @returns the requested document.
|
|
16
|
+
* @throws when the packaged skill is missing, or the topic is not one of {@link listGuideTopics}.
|
|
17
|
+
*/
|
|
18
|
+
export declare function readGuide(topic?: string): Promise<GuideDocument>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Serves the packaged agent skill — the instructions for driving this server —
|
|
2
|
+
// so an MCP client can read them without installing the skill files. The shipped
|
|
3
|
+
// markdown under agent-skill/ is the single source: nothing here duplicates its
|
|
4
|
+
// prose, and a drift test would have nothing to compare.
|
|
5
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
6
|
+
import { basename, join } from "node:path";
|
|
7
|
+
import { pathExists } from "./utils.js";
|
|
8
|
+
import { packageRoot } from "./workspace.js";
|
|
9
|
+
/** Packaged agent-skill directory. Wrappers serve its contents through codecarto_guide. */
|
|
10
|
+
export const packagedAgentSkillDir = join(packageRoot, "agent-skill", "codecartographer");
|
|
11
|
+
/**
|
|
12
|
+
* Strip a leading YAML frontmatter block. The frontmatter is skill-installer
|
|
13
|
+
* metadata (name, version, license) that carries no instruction value for an
|
|
14
|
+
* agent reading the guide through the tool.
|
|
15
|
+
*/
|
|
16
|
+
function stripFrontmatter(markdown) {
|
|
17
|
+
const match = /^---\r?\n[\s\S]*?\r?\n---\r?\n/.exec(markdown);
|
|
18
|
+
return match ? markdown.slice(match[0].length).trimStart() : markdown;
|
|
19
|
+
}
|
|
20
|
+
/** Topic names available to {@link readGuide}, `overview` first. */
|
|
21
|
+
export async function listGuideTopics() {
|
|
22
|
+
const referencesDir = join(packagedAgentSkillDir, "references");
|
|
23
|
+
if (!(await pathExists(referencesDir)))
|
|
24
|
+
return ["overview"];
|
|
25
|
+
const names = (await readdir(referencesDir))
|
|
26
|
+
.filter((name) => name.endsWith(".md"))
|
|
27
|
+
.map((name) => basename(name, ".md"))
|
|
28
|
+
.sort();
|
|
29
|
+
return ["overview", ...names];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Read one guide document.
|
|
33
|
+
* @param topic - `overview` (default) for the main skill, or a reference name from {@link listGuideTopics}.
|
|
34
|
+
* @returns the requested document.
|
|
35
|
+
* @throws when the packaged skill is missing, or the topic is not one of {@link listGuideTopics}.
|
|
36
|
+
*/
|
|
37
|
+
export async function readGuide(topic = "overview") {
|
|
38
|
+
const requested = topic.trim() || "overview";
|
|
39
|
+
const available = await listGuideTopics();
|
|
40
|
+
if (!available.includes(requested)) {
|
|
41
|
+
throw new Error(`Unknown guide topic ${requested}. Available: ${available.join(", ")}.`);
|
|
42
|
+
}
|
|
43
|
+
const path = requested === "overview"
|
|
44
|
+
? join(packagedAgentSkillDir, "SKILL.md")
|
|
45
|
+
: join(packagedAgentSkillDir, "references", `${requested}.md`);
|
|
46
|
+
if (!(await pathExists(path))) {
|
|
47
|
+
throw new Error(`Packaged agent skill is missing at ${path}. Reinstall codecartographer-pi.`);
|
|
48
|
+
}
|
|
49
|
+
return { topic: requested, content: stripFrontmatter(await readFile(path, "utf8")) };
|
|
50
|
+
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./workspace.ts";
|
|
|
8
8
|
export * from "./completion.ts";
|
|
9
9
|
export * from "./orchestrator-config.ts";
|
|
10
10
|
export * from "./usage.ts";
|
|
11
|
+
export * from "./guide.ts";
|
|
11
12
|
export * from "./dashboard.ts";
|
|
12
13
|
export * from "./library.ts";
|
|
13
14
|
export * from "./synthesis.ts";
|
package/dist/core/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export * from "./workspace.js";
|
|
|
11
11
|
export * from "./completion.js";
|
|
12
12
|
export * from "./orchestrator-config.js";
|
|
13
13
|
export * from "./usage.js";
|
|
14
|
+
export * from "./guide.js";
|
|
14
15
|
export * from "./dashboard.js";
|
|
15
16
|
export * from "./library.js";
|
|
16
17
|
export * from "./synthesis.js";
|
package/dist/core/prompts.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { readdir } from "node:fs/promises";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { pathExists } from "./utils.js";
|
|
7
|
+
import { describeScaffoldStaleness } from "./workspace.js";
|
|
7
8
|
import { runPhasePreflight } from "./synthesis.js";
|
|
8
9
|
export function describeEntry(entry) {
|
|
9
10
|
const parts = [];
|
|
@@ -30,6 +31,7 @@ export function collectRoutedCarryForward(state, targetPhaseId) {
|
|
|
30
31
|
export async function buildPhasePrompt(state, phase, forced, options = {}) {
|
|
31
32
|
const preflight = options.preflight ?? await runPhasePreflight(state, phase);
|
|
32
33
|
const synthesisWorkflow = state.pipeline.workflow_name === "evidence-backed-project-synthesis";
|
|
34
|
+
const handoffTemplateExists = await pathExists(join(state.workspaceDir, "templates", "phase-handoff.yaml"));
|
|
33
35
|
const lines = [
|
|
34
36
|
`Read .codecarto/GUIDE.md and continue the CodeCartographer workflow for the phase \`${phase.id}\`.`,
|
|
35
37
|
synthesisWorkflow
|
|
@@ -39,8 +41,10 @@ export async function buildPhasePrompt(state, phase, forced, options = {}) {
|
|
|
39
41
|
"Required reads before analysis:",
|
|
40
42
|
"- .codecarto/GUIDE.md",
|
|
41
43
|
"- .codecarto/workflow/status.yaml",
|
|
42
|
-
"- .codecarto/templates/phase-handoff.yaml",
|
|
43
44
|
];
|
|
45
|
+
if (handoffTemplateExists) {
|
|
46
|
+
lines.push("- .codecarto/templates/phase-handoff.yaml");
|
|
47
|
+
}
|
|
44
48
|
const primaryOutput = phase.primary_output ? `.codecarto/${phase.primary_output}` : undefined;
|
|
45
49
|
if (primaryOutput) {
|
|
46
50
|
lines.push(`- ${primaryOutput} if it already exists (continue instead of duplicating work)`);
|
|
@@ -69,6 +73,21 @@ export async function buildPhasePrompt(state, phase, forced, options = {}) {
|
|
|
69
73
|
if (await pathExists(decisionsPath)) {
|
|
70
74
|
lines.push("- .codecarto/DECISIONS.md (numbered project decisions; new entries are appended in your closeout)");
|
|
71
75
|
}
|
|
76
|
+
// Stale-scaffold warnings (issue #85): a workspace copied from an old
|
|
77
|
+
// template can contradict the runtime contract. Both surfaces render this
|
|
78
|
+
// prompt, so warn here rather than per-host.
|
|
79
|
+
const scaffoldWarnings = [];
|
|
80
|
+
if (!handoffTemplateExists) {
|
|
81
|
+
scaffoldWarnings.push(`.codecarto/templates/phase-handoff.yaml is missing — this scaffold predates the v0.12.0 handoff contract. Completion still requires a phase handoff at .codecarto/scratch/handoffs/${phase.id}.yaml; refresh the framework-owned files (GUIDE.md, templates/, workflow/VALIDATE.md, workflow/pipeline*.yaml) from the current CodeCartographer template, and trust this prompt over the workspace GUIDE.md where they disagree.`);
|
|
82
|
+
}
|
|
83
|
+
const staleness = describeScaffoldStaleness(state);
|
|
84
|
+
if (staleness)
|
|
85
|
+
scaffoldWarnings.push(staleness);
|
|
86
|
+
if (scaffoldWarnings.length > 0) {
|
|
87
|
+
lines.push("");
|
|
88
|
+
for (const warning of scaffoldWarnings)
|
|
89
|
+
lines.push(`WARNING: ${warning}`);
|
|
90
|
+
}
|
|
72
91
|
const routed = collectRoutedCarryForward(state, phase.id);
|
|
73
92
|
if (routed.length > 0) {
|
|
74
93
|
lines.push("", `Items routed to \`${phase.id}\` for closure (carry_forward from earlier phases):`);
|
package/dist/core/types.d.ts
CHANGED
|
@@ -67,6 +67,8 @@ export type WorkspaceState = {
|
|
|
67
67
|
pipelinePath: string;
|
|
68
68
|
status: NormalizedStatus;
|
|
69
69
|
pipeline: PipelineFile;
|
|
70
|
+
/** From workflow/scaffold-version.yaml; undefined for scaffolds that predate the marker. */
|
|
71
|
+
scaffoldVersion?: string;
|
|
70
72
|
};
|
|
71
73
|
export type ValidationOverall = "PASS" | "PASS WITH GAPS" | "FAIL" | "MISSING";
|
|
72
74
|
export type ValidationResult = {
|
package/dist/core/workspace.d.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import type { PhaseHandoff, WorkspaceState } from "./types.ts";
|
|
2
|
+
/** Installed package root. Anchors packaged assets served to clients (template, agent skill). */
|
|
3
|
+
export declare const packageRoot: string;
|
|
2
4
|
export declare const packagedWorkspaceDir: string;
|
|
3
5
|
export declare const PACKAGE_VERSION: string;
|
|
4
6
|
export declare function getWorkspaceState(cwd: string): Promise<WorkspaceState | null>;
|
|
7
|
+
/**
|
|
8
|
+
* Human-readable staleness notice for the workspace's .codecarto/ scaffold,
|
|
9
|
+
* or null when the scaffold matches the running framework. A missing marker
|
|
10
|
+
* means the scaffold was copied from a release that predates it — those
|
|
11
|
+
* scaffolds may also predate the v0.12.0 handoff contract, whose GUIDE and
|
|
12
|
+
* pipelines instruct the exact opposite completion protocol. Warn, never
|
|
13
|
+
* fail: unversioned workspaces must keep working.
|
|
14
|
+
*/
|
|
15
|
+
export declare function describeScaffoldStaleness(state: WorkspaceState): string | null;
|
|
5
16
|
export declare function updateStatusAtomically(cwd: string, updater: (state: WorkspaceState) => Promise<{
|
|
6
17
|
state: WorkspaceState;
|
|
7
18
|
handoff?: PhaseHandoff;
|
package/dist/core/workspace.js
CHANGED
|
@@ -26,7 +26,8 @@ function findPackageRoot(start) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
const coreDir = dirname(fileURLToPath(import.meta.url));
|
|
29
|
-
|
|
29
|
+
/** Installed package root. Anchors packaged assets served to clients (template, agent skill). */
|
|
30
|
+
export const packageRoot = findPackageRoot(coreDir);
|
|
30
31
|
// Path to the packaged framework template directory. Wrappers copy this on
|
|
31
32
|
// /codecarto-init.
|
|
32
33
|
export const packagedWorkspaceDir = join(packageRoot, ".codecarto");
|
|
@@ -76,6 +77,17 @@ export async function getWorkspaceState(cwd) {
|
|
|
76
77
|
}
|
|
77
78
|
const pipeline = await loadYamlFile(pipelinePath);
|
|
78
79
|
const status = normalizeStatus(rawStatus, pipeline, pipelineRelativePath, cwd);
|
|
80
|
+
const scaffoldVersionPath = join(workspaceDir, "workflow", "scaffold-version.yaml");
|
|
81
|
+
let scaffoldVersion;
|
|
82
|
+
if (await pathExists(scaffoldVersionPath)) {
|
|
83
|
+
const marker = await loadYamlFile(scaffoldVersionPath);
|
|
84
|
+
if (typeof marker.scaffold_version === "string" && marker.scaffold_version.trim()) {
|
|
85
|
+
scaffoldVersion = marker.scaffold_version.trim();
|
|
86
|
+
}
|
|
87
|
+
else if (typeof marker.scaffold_version === "number") {
|
|
88
|
+
scaffoldVersion = String(marker.scaffold_version);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
79
91
|
return {
|
|
80
92
|
cwd,
|
|
81
93
|
workspaceDir,
|
|
@@ -83,8 +95,51 @@ export async function getWorkspaceState(cwd) {
|
|
|
83
95
|
pipelinePath,
|
|
84
96
|
pipeline,
|
|
85
97
|
status,
|
|
98
|
+
...(scaffoldVersion !== undefined && { scaffoldVersion }),
|
|
86
99
|
};
|
|
87
100
|
}
|
|
101
|
+
// Numeric x.y.z comparison; null when either side is not a plain dotted triple.
|
|
102
|
+
function compareDottedVersions(a, b) {
|
|
103
|
+
const parse = (version) => {
|
|
104
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version.trim());
|
|
105
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
106
|
+
};
|
|
107
|
+
const left = parse(a);
|
|
108
|
+
const right = parse(b);
|
|
109
|
+
if (!left || !right)
|
|
110
|
+
return null;
|
|
111
|
+
for (let i = 0; i < 3; i++) {
|
|
112
|
+
if (left[i] !== right[i])
|
|
113
|
+
return left[i] < right[i] ? -1 : 1;
|
|
114
|
+
}
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Human-readable staleness notice for the workspace's .codecarto/ scaffold,
|
|
119
|
+
* or null when the scaffold matches the running framework. A missing marker
|
|
120
|
+
* means the scaffold was copied from a release that predates it — those
|
|
121
|
+
* scaffolds may also predate the v0.12.0 handoff contract, whose GUIDE and
|
|
122
|
+
* pipelines instruct the exact opposite completion protocol. Warn, never
|
|
123
|
+
* fail: unversioned workspaces must keep working.
|
|
124
|
+
*/
|
|
125
|
+
export function describeScaffoldStaleness(state) {
|
|
126
|
+
const scaffold = state.scaffoldVersion;
|
|
127
|
+
if (!scaffold) {
|
|
128
|
+
return "This workspace's .codecarto/ scaffold has no workflow/scaffold-version.yaml marker (introduced after v0.12.11), so its framework-owned files (GUIDE.md, templates/, workflow/ pipelines and VALIDATE.md) may predate the v0.12.0 handoff contract. Refresh them from the packaged CodeCartographer template.";
|
|
129
|
+
}
|
|
130
|
+
const comparison = compareDottedVersions(scaffold, PACKAGE_VERSION);
|
|
131
|
+
if (comparison === 0)
|
|
132
|
+
return null;
|
|
133
|
+
if (comparison === null) {
|
|
134
|
+
return scaffold === PACKAGE_VERSION
|
|
135
|
+
? null
|
|
136
|
+
: `This workspace's scaffold version (${scaffold}) does not match the running framework (${PACKAGE_VERSION}). Refresh the framework-owned files (GUIDE.md, templates/, workflow/) from the packaged template.`;
|
|
137
|
+
}
|
|
138
|
+
if (comparison < 0) {
|
|
139
|
+
return `This workspace's scaffold (v${scaffold}) is older than the running framework (v${PACKAGE_VERSION}). Refresh the framework-owned files (GUIDE.md, templates/, workflow/) from the packaged template to pick up pipeline and template fixes.`;
|
|
140
|
+
}
|
|
141
|
+
return `This workspace's scaffold (v${scaffold}) is newer than the running framework (v${PACKAGE_VERSION}). Upgrade CodeCartographer to at least v${scaffold}.`;
|
|
142
|
+
}
|
|
88
143
|
export async function updateStatusAtomically(cwd, updater) {
|
|
89
144
|
const workspaceDir = join(cwd, ".codecarto");
|
|
90
145
|
const statusPath = join(workspaceDir, "workflow", "status.yaml");
|
|
@@ -8,7 +8,7 @@ import { narrateDashboard } from "./dashboard-narrator.js";
|
|
|
8
8
|
import { writeDashboard } from "./dashboard-writer.js";
|
|
9
9
|
import { parseNextFlags } from "./next-flags.js";
|
|
10
10
|
import { phaseCompactionExtension } from "./phase-compaction.js";
|
|
11
|
-
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPathResolved, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, packagedWorkspaceDir, pathExists, PACKAGE_VERSION, PhasePreflightError, PIPELINE_ALIASES, publishEntry, resolvePhase, resolvePipelineChoice, runPhasePreflight, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../../core/index.js";
|
|
11
|
+
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, describeScaffoldStaleness, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPathResolved, 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
12
|
import { initLibrary } from "../../core/library.js";
|
|
13
13
|
import { resolveUserConfigPath } from "../../core/orchestrator-config.js";
|
|
14
14
|
const STATUS_WIDGET_ID = "codecarto-widget";
|
|
@@ -71,6 +71,9 @@ function buildStatusLines(state, extraLines = []) {
|
|
|
71
71
|
`Post-pipeline work: ${postPipelinePending} pending`,
|
|
72
72
|
`Next: ${nextAction}`,
|
|
73
73
|
];
|
|
74
|
+
const scaffoldNotice = describeScaffoldStaleness(state);
|
|
75
|
+
if (scaffoldNotice)
|
|
76
|
+
lines.push(`Scaffold: ${scaffoldNotice}`);
|
|
74
77
|
if (extraLines.length > 0) {
|
|
75
78
|
lines.push("", ...extraLines);
|
|
76
79
|
}
|
|
@@ -78,6 +78,11 @@ export declare function handleSkill(args: {
|
|
|
78
78
|
}>;
|
|
79
79
|
structuredContent?: Record<string, unknown>;
|
|
80
80
|
}>;
|
|
81
|
+
export declare function readSpecArg(args: {
|
|
82
|
+
spec?: unknown;
|
|
83
|
+
spec_path?: unknown;
|
|
84
|
+
cwd?: unknown;
|
|
85
|
+
}, allowedRoots: string[]): Promise<string>;
|
|
81
86
|
export declare function handlePublish(args: Record<string, unknown>): Promise<{
|
|
82
87
|
content: Array<{
|
|
83
88
|
type: "text";
|
|
@@ -166,6 +171,15 @@ export declare function handleListSkills(args: {
|
|
|
166
171
|
}>;
|
|
167
172
|
structuredContent?: Record<string, unknown>;
|
|
168
173
|
}>;
|
|
174
|
+
export declare function handleGuide(args: {
|
|
175
|
+
topic?: string;
|
|
176
|
+
}): Promise<{
|
|
177
|
+
content: Array<{
|
|
178
|
+
type: "text";
|
|
179
|
+
text: string;
|
|
180
|
+
}>;
|
|
181
|
+
structuredContent?: Record<string, unknown>;
|
|
182
|
+
}>;
|
|
169
183
|
export declare function buildServer(): Server<{
|
|
170
184
|
method: string;
|
|
171
185
|
params?: {
|
|
@@ -15,7 +15,7 @@ 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, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isValidSlug, isWithinPathResolved, 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";
|
|
18
|
+
import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, completeValidatedPhase, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, describeScaffoldStaleness, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isValidSlug, isWithinPathResolved, listEntries, listGuideTopics, readGuide, 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
19
|
import { initLibrary } from "../core/library.js";
|
|
20
20
|
import { loadUserConfig, resolveUserConfigPath } from "../core/orchestrator-config.js";
|
|
21
21
|
import { writeDashboard } from "../extensions/codecarto/dashboard-writer.js";
|
|
@@ -118,7 +118,8 @@ export async function handleStatus(args) {
|
|
|
118
118
|
const currentOpenQuestions = currentPhase === "complete" ? 0 : state.status.phases[currentPhase]?.open_questions.length ?? 0;
|
|
119
119
|
const terminalOpenQuestions = Object.values(state.status.phases).reduce((sum, phase) => sum + (phase.open_questions?.length ?? 0), 0);
|
|
120
120
|
const postPipelinePending = state.status.post_pipeline.filter((entry) => entry.status !== "resolved").length;
|
|
121
|
-
const
|
|
121
|
+
const scaffoldNotice = describeScaffoldStaleness(state);
|
|
122
|
+
const summaryLines = [
|
|
122
123
|
`Phase: ${currentPhase}`,
|
|
123
124
|
`Pipeline state: ${currentPhase === "complete" ? "complete" : "in progress"}`,
|
|
124
125
|
`Pipeline: ${getPipelineLabel(state.status.pipeline)} (${state.status.pipeline})`,
|
|
@@ -127,8 +128,12 @@ export async function handleStatus(args) {
|
|
|
127
128
|
`Carry-forward (pipeline phases): ${totalCarryForward}`,
|
|
128
129
|
`Post-pipeline work: ${postPipelinePending} pending`,
|
|
129
130
|
`Next: ${state.status.next_actions[0] ?? (nextPhase ? `Begin ${nextPhase.id}` : "All phases complete.")}`,
|
|
130
|
-
]
|
|
131
|
+
];
|
|
132
|
+
if (scaffoldNotice)
|
|
133
|
+
summaryLines.push(`Scaffold: ${scaffoldNotice}`);
|
|
134
|
+
const summary = summaryLines.join("\n");
|
|
131
135
|
return textResult(summary, {
|
|
136
|
+
...(scaffoldNotice ? { scaffoldNotice } : {}),
|
|
132
137
|
currentPhase,
|
|
133
138
|
pipeline: state.status.pipeline,
|
|
134
139
|
pipelineLabel: getPipelineLabel(state.status.pipeline),
|
|
@@ -325,25 +330,32 @@ function buildGenerationFromArg(model_metadata) {
|
|
|
325
330
|
out.notes = m.notes;
|
|
326
331
|
return out;
|
|
327
332
|
}
|
|
328
|
-
|
|
333
|
+
// `allowedRoots` is deliberately required and must be non-empty whenever
|
|
334
|
+
// spec_path is used. It previously defaulted to `[]`, which made containment
|
|
335
|
+
// opt-in: a caller that omitted it would read any absolute path the client
|
|
336
|
+
// asked for, silently reopening the arbitrary-file-read class of bug fixed in
|
|
337
|
+
// v0.12.11. Containment is now the default posture and an empty root set is a
|
|
338
|
+
// programming error rather than a bypass.
|
|
339
|
+
export async function readSpecArg(args, allowedRoots) {
|
|
329
340
|
if (typeof args.spec === "string" && args.spec.length > 0)
|
|
330
341
|
return args.spec;
|
|
331
342
|
if (typeof args.spec_path === "string" && args.spec_path.length > 0) {
|
|
332
343
|
if (!isAbsolute(args.spec_path)) {
|
|
333
344
|
throw new McpError(ErrorCode.InvalidParams, `spec_path must be absolute, got: ${args.spec_path}`);
|
|
334
345
|
}
|
|
346
|
+
if (!Array.isArray(allowedRoots) || allowedRoots.length === 0) {
|
|
347
|
+
throw new McpError(ErrorCode.InternalError, "refusing to read spec_path without a containment root — this is a caller bug, not a client error");
|
|
348
|
+
}
|
|
335
349
|
if (!(await pathExists(args.spec_path))) {
|
|
336
350
|
throw new McpError(ErrorCode.InvalidParams, `spec_path does not exist: ${args.spec_path}`);
|
|
337
351
|
}
|
|
338
352
|
// Enforce path containment: spec_path must be within an allowed root
|
|
339
353
|
// (cwd's .codecarto/ or the configured library path) to prevent
|
|
340
354
|
// arbitrary file reads.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
throw new McpError(ErrorCode.InvalidParams, `spec_path must be within the workspace (.codecarto/) or the configured library path. Got: ${args.spec_path}`);
|
|
346
|
-
}
|
|
355
|
+
const resolvedSpecPath = await canonicalPath(args.spec_path);
|
|
356
|
+
const withinAllowed = await Promise.all(allowedRoots.map((root) => isWithinPathResolved(resolvedSpecPath, root)));
|
|
357
|
+
if (!withinAllowed.some((result) => result)) {
|
|
358
|
+
throw new McpError(ErrorCode.InvalidParams, `spec_path must be within the workspace (.codecarto/) or the configured library path. Got: ${args.spec_path}`);
|
|
347
359
|
}
|
|
348
360
|
return readFile(args.spec_path, "utf8");
|
|
349
361
|
}
|
|
@@ -864,6 +876,19 @@ const TOOLS = [
|
|
|
864
876
|
required: ["cwd"],
|
|
865
877
|
},
|
|
866
878
|
},
|
|
879
|
+
{
|
|
880
|
+
name: "codecarto_guide",
|
|
881
|
+
description: "Return the instructions for driving this server: the status/next/execute/validate/complete loop, the phase-handoff contract, pipeline selection, executor choice, and recovery. Call this first when you have not run a CodeCartographer pipeline before. Takes no workspace.",
|
|
882
|
+
inputSchema: {
|
|
883
|
+
type: "object",
|
|
884
|
+
properties: {
|
|
885
|
+
topic: {
|
|
886
|
+
type: "string",
|
|
887
|
+
description: "Guide topic. Omit for the overview; other topics are listed in every response.",
|
|
888
|
+
},
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
},
|
|
867
892
|
{
|
|
868
893
|
name: "codecarto_list_skills",
|
|
869
894
|
description: "List available post-pipeline skills installed in the workspace.",
|
|
@@ -893,7 +918,19 @@ const HANDLERS = {
|
|
|
893
918
|
codecarto_usage: handleUsage,
|
|
894
919
|
codecarto_dashboard: handleDashboard,
|
|
895
920
|
codecarto_list_skills: handleListSkills,
|
|
921
|
+
codecarto_guide: handleGuide,
|
|
896
922
|
};
|
|
923
|
+
export async function handleGuide(args) {
|
|
924
|
+
const topics = await listGuideTopics();
|
|
925
|
+
const document = await readGuide(args.topic).catch((error) => {
|
|
926
|
+
throw new McpError(ErrorCode.InvalidParams, error instanceof Error ? error.message : String(error));
|
|
927
|
+
});
|
|
928
|
+
const other = topics.filter((name) => name !== document.topic);
|
|
929
|
+
const footer = other.length > 0
|
|
930
|
+
? `\n\n---\nOther guide topics: ${other.join(", ")} (call codecarto_guide with topic).`
|
|
931
|
+
: "";
|
|
932
|
+
return textResult(`${document.content}${footer}`, { topic: document.topic, topics });
|
|
933
|
+
}
|
|
897
934
|
// ---------- server bootstrap ----------
|
|
898
935
|
export function buildServer() {
|
|
899
936
|
const server = new Server({ name: "codecartographer", version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codecartographer-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"mcpName": "io.github.HuginnIndustries/codecartographer",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Turn an unfamiliar codebase into a validated reimplementation spec, then synthesize confirmed specs and a product vision into a traceable plan.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"pi-package",
|
|
@@ -13,7 +13,12 @@
|
|
|
13
13
|
"mcp",
|
|
14
14
|
"software-planning",
|
|
15
15
|
"code-analysis",
|
|
16
|
-
"synthesis"
|
|
16
|
+
"synthesis",
|
|
17
|
+
"codebase",
|
|
18
|
+
"ai-agent",
|
|
19
|
+
"context-engineering",
|
|
20
|
+
"code-understanding",
|
|
21
|
+
"spec-driven"
|
|
17
22
|
],
|
|
18
23
|
"license": "MIT",
|
|
19
24
|
"author": "James Sesler",
|
|
@@ -30,6 +35,7 @@
|
|
|
30
35
|
},
|
|
31
36
|
"files": [
|
|
32
37
|
".codecarto/**/*",
|
|
38
|
+
"agent-skill/**/*",
|
|
33
39
|
"dist/**/*",
|
|
34
40
|
"assets/logo.svg",
|
|
35
41
|
"README.md",
|
|
@@ -59,5 +65,8 @@
|
|
|
59
65
|
},
|
|
60
66
|
"devDependencies": {
|
|
61
67
|
"typescript": "^5.9.3"
|
|
68
|
+
},
|
|
69
|
+
"overrides": {
|
|
70
|
+
"undici": "^8.10.0"
|
|
62
71
|
}
|
|
63
72
|
}
|