@jakkrichm/create-nexus-devflow 2.13.0 → 2.15.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/README.md +4 -4
- package/dist/bin/create-nexus-devflow.d.ts +1 -1
- package/dist/bin/create-nexus-devflow.js +4 -3
- package/dist/bin/create-nexus-devflow.js.map +1 -1
- package/dist/lib/branch-context.d.ts +1 -0
- package/dist/lib/branch-context.js +8 -3
- package/dist/lib/branch-context.js.map +1 -1
- package/dist/lib/dashboard-page.d.ts +5 -2
- package/dist/lib/dashboard-page.js +7 -388
- package/dist/lib/dashboard-page.js.map +1 -1
- package/dist/lib/dashboard-snapshot.d.ts +22 -3
- package/dist/lib/dashboard-snapshot.js +154 -3
- package/dist/lib/dashboard-snapshot.js.map +1 -1
- package/dist/lib/delivery-lifecycle-engine.d.ts +1 -1
- package/dist/lib/delivery-lifecycle-engine.js +1 -1
- package/dist/lib/delivery-lifecycle-engine.js.map +1 -1
- package/dist/lib/mcp-server-engine.d.ts +57 -0
- package/dist/lib/mcp-server-engine.js +678 -0
- package/dist/lib/mcp-server-engine.js.map +1 -0
- package/dist/lib/mcp.d.ts +15 -27
- package/dist/lib/mcp.js +20 -646
- package/dist/lib/mcp.js.map +1 -1
- package/dist/lib/project-status-engine.d.ts +110 -0
- package/dist/lib/project-status-engine.js +493 -0
- package/dist/lib/project-status-engine.js.map +1 -0
- package/dist/lib/skill-manager.d.ts +2 -82
- package/dist/lib/skill-manager.js +15 -856
- package/dist/lib/skill-manager.js.map +1 -1
- package/dist/lib/skill-registry-engine.d.ts +121 -0
- package/dist/lib/skill-registry-engine.js +824 -0
- package/dist/lib/skill-registry-engine.js.map +1 -0
- package/dist/lib/status.d.ts +1 -90
- package/dist/lib/status.js +7 -467
- package/dist/lib/status.js.map +1 -1
- package/dist/lib/studio-view-renderer.d.ts +17 -1
- package/dist/lib/studio-view-renderer.js +855 -3
- package/dist/lib/studio-view-renderer.js.map +1 -1
- package/dist/lib/tooling/commands/scan-doc-contract.js +1 -0
- package/dist/lib/tooling/commands/scan-doc-contract.js.map +1 -1
- package/dist/lib/update.js +1 -1
- package/dist/lib/update.js.map +1 -1
- package/package.json +1 -1
- package/template/.agents/skills/complete/SKILL.md +14 -13
- package/template/.agents/skills/implement/SKILL.md +36 -2
- package/template/.claude/skills/complete/SKILL.md +14 -13
- package/template/.claude/skills/implement/SKILL.md +36 -2
- package/template/AGENTS.md +1 -1
- package/template/devflow/build-plan.md +5 -4
- package/template/devflow/context/ai-interaction.md +4 -2
- package/template/devflow/docs/USAGE.md +145 -0
- package/template/devflow/docs/diagrams/nexus-devflow-architecture.html +14821 -0
- package/template/devflow/docs/diagrams/nexus-devflow-architecture.json +80 -0
- package/template/devflow/docs/diagrams/nexus-devflow-lifecycle.html +14860 -0
- package/template/devflow/docs/diagrams/nexus-devflow-lifecycle.json +88 -0
- package/template/devflow/docs/examples/living-spec/adr.example.md +43 -0
- package/template/devflow/docs/examples/living-spec/current-feature.example.md +113 -0
- package/template/devflow/docs/examples/living-spec/discovery.example.md +45 -0
- package/template/devflow/docs/examples/living-spec/ideas.example.md +22 -0
- package/template/devflow/docs/governance-rules.md +39 -0
- package/template/devflow/docs/manual-review-workflow-spec.md +116 -0
- package/template/devflow/docs/markdown-metadata-contract.md +140 -0
- package/template/devflow/docs/quickstart.md +119 -0
- package/template/devflow/docs/release-process.md +52 -0
- package/template/devflow/docs/skill-selection-policy.md +64 -0
- package/template/devflow/docs/workflow-surface-map.md +85 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import readline from "node:readline";
|
|
4
|
+
import { readProjectStatus } from "./status.js";
|
|
5
|
+
import { ActiveContextEngine } from "./active-context-engine.js";
|
|
6
|
+
import { GatekeeperEngine, formatGateReport } from "./gatekeeper.js";
|
|
7
|
+
import { StudioViewRenderer } from "./studio-view-renderer.js";
|
|
8
|
+
import { DashboardStateEngine } from "./dashboard-engine.js";
|
|
9
|
+
import { addIdea, readIdeas } from "./ideas.js";
|
|
10
|
+
import { addFinding, resolveFinding } from "./findings.js";
|
|
11
|
+
import { sliceContextForStage } from "./context-slicer.js";
|
|
12
|
+
import { buildCodeGraph, calculateBlastRadius } from "./code-graph.js";
|
|
13
|
+
import { generateSwarmPlan } from "./swarm-orchestrator.js";
|
|
14
|
+
export const PROTOCOL_VERSION = "2024-11-05";
|
|
15
|
+
export const SERVER_NAME = "nexus-devflow-mcp";
|
|
16
|
+
export const SERVER_VERSION = "2.14.0";
|
|
17
|
+
export const DEVFLOW_MCP_TOOLS = [
|
|
18
|
+
{
|
|
19
|
+
name: "devflow_get_status",
|
|
20
|
+
description: "Get current project status, active living spec, next recommended action, git state, and quality blockers.",
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "devflow_add_idea",
|
|
28
|
+
description: "Add a new idea to the centralized Idea Inbox (devflow/ideas.md) with automated feasibility and value assessment.",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
text: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Full description of the idea to capture"
|
|
35
|
+
},
|
|
36
|
+
title: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "Optional short title for the idea"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
required: ["text"]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "devflow_record_finding",
|
|
46
|
+
description: "Record a quality defect, security vulnerability, regression risk, or architectural debt finding to findings.md.",
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
title: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description: "Finding title / short description"
|
|
53
|
+
},
|
|
54
|
+
severity: {
|
|
55
|
+
type: "string",
|
|
56
|
+
enum: ["P0", "P1", "P2", "P3"],
|
|
57
|
+
description: "Finding severity level: P0 (Critical Blocker), P1 (High Blocker), P2 (Medium), P3 (Low / Polish)"
|
|
58
|
+
},
|
|
59
|
+
status: {
|
|
60
|
+
type: "string",
|
|
61
|
+
enum: ["open", "unverified"],
|
|
62
|
+
description: "Initial status (default: open)"
|
|
63
|
+
},
|
|
64
|
+
id: {
|
|
65
|
+
type: "string",
|
|
66
|
+
description: "Optional custom ID (e.g. SEC-001)"
|
|
67
|
+
},
|
|
68
|
+
location: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "Optional file path and line location"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
required: ["title"]
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "devflow_resolve_finding",
|
|
78
|
+
description: "Update the status of an existing finding in findings.md (e.g. mark fixed, closed, accepted, or invalid).",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
id: {
|
|
83
|
+
type: "string",
|
|
84
|
+
description: "Finding ID to update (e.g. F-001 or SEC-001)"
|
|
85
|
+
},
|
|
86
|
+
status: {
|
|
87
|
+
type: "string",
|
|
88
|
+
enum: ["open", "fixed", "closed", "accepted", "invalid"],
|
|
89
|
+
description: "New status to set"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
required: ["id", "status"]
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "devflow_evaluate_gate",
|
|
97
|
+
description: "Run DevFlow Quality Gatekeeper evaluation (checks for unchecked tasks, blocking findings, and verification state).",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
strict: {
|
|
102
|
+
type: "boolean",
|
|
103
|
+
description: "When true, strictly blocks unverified living specs that need /check"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "devflow_get_context",
|
|
110
|
+
description: "Retrieve content of a DevFlow context document (overview, stage, standards, findings, ideas, or active spec).",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {
|
|
114
|
+
document: {
|
|
115
|
+
type: "string",
|
|
116
|
+
enum: [
|
|
117
|
+
"overview",
|
|
118
|
+
"stage",
|
|
119
|
+
"standards",
|
|
120
|
+
"findings",
|
|
121
|
+
"ideas",
|
|
122
|
+
"feature",
|
|
123
|
+
"build-plan",
|
|
124
|
+
"project-plan"
|
|
125
|
+
],
|
|
126
|
+
description: "Name of the DevFlow context document to read"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
required: ["document"]
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "devflow_get_sliced_context",
|
|
134
|
+
description: "Retrieve a JIT Stage-Aware sliced context for AI coding (reduces tokens by 60-70% by extracting only relevant rules, tasks, and constraints for the current stage).",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: {
|
|
138
|
+
stage: {
|
|
139
|
+
type: "string",
|
|
140
|
+
enum: ["implement", "check", "explore", "feature", "status"],
|
|
141
|
+
description: "Target DevFlow workflow stage"
|
|
142
|
+
},
|
|
143
|
+
maxTokens: {
|
|
144
|
+
type: "number",
|
|
145
|
+
description: "Optional maximum token budget limit"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
required: ["stage"]
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "devflow_detect_drift",
|
|
153
|
+
description: "Detect git drift between actual repository modified files and the Living Spec (identifies undocumented files and phantom files).",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: "object",
|
|
156
|
+
properties: {}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "devflow_reconcile_state",
|
|
161
|
+
description: "Automatically reconcile and heal workspace drift (syncs undocumented files into living spec and heals stage alignment).",
|
|
162
|
+
inputSchema: {
|
|
163
|
+
type: "object",
|
|
164
|
+
properties: {
|
|
165
|
+
autoAddUndocumented: {
|
|
166
|
+
type: "boolean",
|
|
167
|
+
description: "When true, automatically adds undocumented files into active living spec (default: true)"
|
|
168
|
+
},
|
|
169
|
+
healStage: {
|
|
170
|
+
type: "boolean",
|
|
171
|
+
description: "When true, heals stage pointer to match active branch (default: true)"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "devflow_get_studio_html",
|
|
178
|
+
description: "Retrieve a self-contained, interactive HTML/CSS/JS Webview Studio for embedding inside IDE panels (VS Code, Cursor, Antigravity) or browsers.",
|
|
179
|
+
inputSchema: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {
|
|
182
|
+
theme: {
|
|
183
|
+
type: "string",
|
|
184
|
+
enum: ["auto", "dark", "light"],
|
|
185
|
+
description: "Theme mode (default: auto)"
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "devflow_swarm_plan",
|
|
192
|
+
description: "Generate a multi-agent specialized swarm execution plan (Coder, QA, Security, Architect) with task allocations and context requirements.",
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: "object",
|
|
195
|
+
properties: {}
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "devflow_query_code_graph",
|
|
200
|
+
description: "Query the codebase semantic dependency graph to analyze imports, exports, and calculate the Blast Radius of file changes.",
|
|
201
|
+
inputSchema: {
|
|
202
|
+
type: "object",
|
|
203
|
+
properties: {
|
|
204
|
+
file: {
|
|
205
|
+
type: "string",
|
|
206
|
+
description: "Optional target file path to calculate Blast Radius and impacted dependents"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
];
|
|
212
|
+
export class McpServerEngine {
|
|
213
|
+
projectRoot;
|
|
214
|
+
tools = new Map();
|
|
215
|
+
activeContextEngine;
|
|
216
|
+
gatekeeperEngine;
|
|
217
|
+
constructor(projectRoot = process.cwd()) {
|
|
218
|
+
this.projectRoot = path.resolve(projectRoot);
|
|
219
|
+
this.activeContextEngine = new ActiveContextEngine(this.projectRoot);
|
|
220
|
+
this.gatekeeperEngine = new GatekeeperEngine(this.projectRoot);
|
|
221
|
+
this.registerDefaultTools();
|
|
222
|
+
}
|
|
223
|
+
registerTool(definition, handler) {
|
|
224
|
+
this.tools.set(definition.name, { definition, handler });
|
|
225
|
+
}
|
|
226
|
+
getTools() {
|
|
227
|
+
return Array.from(this.tools.values()).map((t) => t.definition);
|
|
228
|
+
}
|
|
229
|
+
registerDefaultTools() {
|
|
230
|
+
// 1. devflow_get_status
|
|
231
|
+
this.registerTool(DEVFLOW_MCP_TOOLS.find((t) => t.name === "devflow_get_status"), async () => {
|
|
232
|
+
const status = await readProjectStatus(this.projectRoot);
|
|
233
|
+
return {
|
|
234
|
+
content: [{ type: "text", text: JSON.stringify(status, null, 2) }]
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
// 2. devflow_get_active_context (dynamic tool)
|
|
238
|
+
this.registerTool({
|
|
239
|
+
name: "devflow_get_active_context",
|
|
240
|
+
description: "Retrieve structured active task context (workspace paths, task checklist progress, workflow stage, discoveries).",
|
|
241
|
+
inputSchema: { type: "object", properties: {} }
|
|
242
|
+
}, async () => {
|
|
243
|
+
const context = await this.activeContextEngine.getActiveContext();
|
|
244
|
+
return {
|
|
245
|
+
content: [{ type: "text", text: JSON.stringify(context, null, 2) }]
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
// 3. devflow_add_idea
|
|
249
|
+
this.registerTool(DEVFLOW_MCP_TOOLS.find((t) => t.name === "devflow_add_idea"), async (args) => {
|
|
250
|
+
const text = typeof args.text === "string" ? args.text.trim() : "";
|
|
251
|
+
if (!text) {
|
|
252
|
+
return {
|
|
253
|
+
content: [{ type: "text", text: "Error: 'text' parameter is required for devflow_add_idea" }],
|
|
254
|
+
isError: true
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const title = typeof args.title === "string" ? args.title.trim() : undefined;
|
|
258
|
+
const created = await addIdea(this.projectRoot, { text, title });
|
|
259
|
+
return {
|
|
260
|
+
content: [
|
|
261
|
+
{
|
|
262
|
+
type: "text",
|
|
263
|
+
text: `Idea added successfully:\n- ID: ${created.id}\n- Title: ${created.title}\n- Value: ${created.value}\n- Feasibility: ${created.feasibility}`
|
|
264
|
+
}
|
|
265
|
+
]
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
// 4. devflow_record_finding
|
|
269
|
+
this.registerTool(DEVFLOW_MCP_TOOLS.find((t) => t.name === "devflow_record_finding"), async (args) => {
|
|
270
|
+
const title = typeof args.title === "string" ? args.title.trim() : "";
|
|
271
|
+
if (!title) {
|
|
272
|
+
return {
|
|
273
|
+
content: [{ type: "text", text: "Error: 'title' parameter is required for devflow_record_finding" }],
|
|
274
|
+
isError: true
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const severity = typeof args.severity === "string" ? args.severity.toUpperCase() : undefined;
|
|
278
|
+
const status = typeof args.status === "string" ? args.status.toLowerCase() : undefined;
|
|
279
|
+
const id = typeof args.id === "string" ? args.id.trim() : undefined;
|
|
280
|
+
const location = typeof args.location === "string" ? args.location.trim() : undefined;
|
|
281
|
+
const result = await addFinding(this.projectRoot, title, { id, severity, status, location });
|
|
282
|
+
if (!result.success || !result.finding) {
|
|
283
|
+
return {
|
|
284
|
+
content: [{ type: "text", text: `Failed to record finding: ${result.message}` }],
|
|
285
|
+
isError: true
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
content: [
|
|
290
|
+
{
|
|
291
|
+
type: "text",
|
|
292
|
+
text: `Finding recorded successfully:\n- ID: ${result.finding.id}\n- Severity: [${result.finding.severity}]\n- Status: ${result.finding.status}\n- Title: ${result.finding.title}`
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
};
|
|
296
|
+
});
|
|
297
|
+
// 5. devflow_resolve_finding
|
|
298
|
+
this.registerTool(DEVFLOW_MCP_TOOLS.find((t) => t.name === "devflow_resolve_finding"), async (args) => {
|
|
299
|
+
const id = typeof args.id === "string" ? args.id.trim() : "";
|
|
300
|
+
const status = typeof args.status === "string" ? args.status.toLowerCase() : undefined;
|
|
301
|
+
if (!id || !status) {
|
|
302
|
+
return {
|
|
303
|
+
content: [{ type: "text", text: "Error: 'id' and 'status' parameters are required for devflow_resolve_finding" }],
|
|
304
|
+
isError: true
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const result = await resolveFinding(this.projectRoot, id, status);
|
|
308
|
+
if (!result.success || !result.finding) {
|
|
309
|
+
return {
|
|
310
|
+
content: [{ type: "text", text: result.message }],
|
|
311
|
+
isError: true
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
content: [{ type: "text", text: `Finding ${result.finding.id} updated to status: ${result.finding.status}` }]
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
// 6. devflow_evaluate_gate
|
|
319
|
+
this.registerTool(DEVFLOW_MCP_TOOLS.find((t) => t.name === "devflow_evaluate_gate"), async (args) => {
|
|
320
|
+
const strict = args.strict === true;
|
|
321
|
+
const report = await this.gatekeeperEngine.evaluate({ strict });
|
|
322
|
+
const humanText = formatGateReport(report);
|
|
323
|
+
return {
|
|
324
|
+
content: [
|
|
325
|
+
{
|
|
326
|
+
type: "text",
|
|
327
|
+
text: `${humanText}\n\nJSON Report:\n${JSON.stringify(report, null, 2)}`
|
|
328
|
+
}
|
|
329
|
+
],
|
|
330
|
+
isError: !report.passed
|
|
331
|
+
};
|
|
332
|
+
});
|
|
333
|
+
// 7. devflow_get_context
|
|
334
|
+
this.registerTool(DEVFLOW_MCP_TOOLS.find((t) => t.name === "devflow_get_context"), async (args) => {
|
|
335
|
+
const doc = typeof args.document === "string" ? args.document.toLowerCase() : "";
|
|
336
|
+
const contextPaths = await this.activeContextEngine.getPaths();
|
|
337
|
+
let fullPath;
|
|
338
|
+
switch (doc) {
|
|
339
|
+
case "overview":
|
|
340
|
+
fullPath = path.join(this.projectRoot, "devflow", "context", "project-overview.md");
|
|
341
|
+
break;
|
|
342
|
+
case "stage":
|
|
343
|
+
fullPath = contextPaths.stagePath;
|
|
344
|
+
break;
|
|
345
|
+
case "standards":
|
|
346
|
+
fullPath = path.join(this.projectRoot, "devflow", "context", "coding-standards.md");
|
|
347
|
+
break;
|
|
348
|
+
case "findings":
|
|
349
|
+
fullPath = contextPaths.findingsPath || path.join(this.projectRoot, "devflow", "context", "findings.md");
|
|
350
|
+
break;
|
|
351
|
+
case "ideas":
|
|
352
|
+
fullPath = path.join(this.projectRoot, "devflow", "ideas.md");
|
|
353
|
+
break;
|
|
354
|
+
case "feature":
|
|
355
|
+
fullPath = contextPaths.featureSpecPath;
|
|
356
|
+
break;
|
|
357
|
+
case "build-plan":
|
|
358
|
+
fullPath = path.join(this.projectRoot, "devflow", "build-plan.md");
|
|
359
|
+
break;
|
|
360
|
+
case "project-plan":
|
|
361
|
+
fullPath = path.join(this.projectRoot, "devflow", "project-plan.md");
|
|
362
|
+
break;
|
|
363
|
+
default:
|
|
364
|
+
return {
|
|
365
|
+
content: [{ type: "text", text: `Error: Unknown document type '${doc}'` }],
|
|
366
|
+
isError: true
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
const content = await fs.readFile(fullPath, "utf8");
|
|
371
|
+
return { content: [{ type: "text", text: content }] };
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
return {
|
|
375
|
+
content: [{ type: "text", text: `Document '${doc}' not found or empty at ${fullPath}.` }],
|
|
376
|
+
isError: false
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
// 8. devflow_get_sliced_context
|
|
381
|
+
this.registerTool(DEVFLOW_MCP_TOOLS.find((t) => t.name === "devflow_get_sliced_context"), async (args) => {
|
|
382
|
+
const stage = (typeof args.stage === "string" ? args.stage.toLowerCase() : "status");
|
|
383
|
+
const maxTokens = typeof args.maxTokens === "number" ? args.maxTokens : undefined;
|
|
384
|
+
const slice = await sliceContextForStage(this.projectRoot, stage, { maxTokens });
|
|
385
|
+
return {
|
|
386
|
+
content: [
|
|
387
|
+
{
|
|
388
|
+
type: "text",
|
|
389
|
+
text: `[JIT Context Slice - Stage: ${slice.stage}] (Tokens: ~${slice.estimatedTokens}, Saved: ${slice.reductionPercentage}%)\n\n${slice.content}`
|
|
390
|
+
}
|
|
391
|
+
]
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
// 9. devflow_detect_drift
|
|
395
|
+
this.registerTool(DEVFLOW_MCP_TOOLS.find((t) => t.name === "devflow_detect_drift"), async () => {
|
|
396
|
+
const drift = await this.gatekeeperEngine.getDrift();
|
|
397
|
+
return {
|
|
398
|
+
content: [{ type: "text", text: JSON.stringify(drift, null, 2) }]
|
|
399
|
+
};
|
|
400
|
+
});
|
|
401
|
+
// 10. devflow_reconcile_state
|
|
402
|
+
this.registerTool(DEVFLOW_MCP_TOOLS.find((t) => t.name === "devflow_reconcile_state"), async (args) => {
|
|
403
|
+
const autoAdd = args.autoAddUndocumented !== false;
|
|
404
|
+
const heal = args.healStage !== false;
|
|
405
|
+
const result = await this.gatekeeperEngine.reconcile({
|
|
406
|
+
autoAddUndocumented: autoAdd,
|
|
407
|
+
healStage: heal
|
|
408
|
+
});
|
|
409
|
+
return {
|
|
410
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
// 11. devflow_get_studio_html
|
|
414
|
+
this.registerTool(DEVFLOW_MCP_TOOLS.find((t) => t.name === "devflow_get_studio_html"), async (args) => {
|
|
415
|
+
const theme = (typeof args.theme === "string" ? args.theme : "auto");
|
|
416
|
+
const dashboardEngine = new DashboardStateEngine(this.projectRoot);
|
|
417
|
+
const snapshot = await dashboardEngine.getSnapshot();
|
|
418
|
+
const ideas = await readIdeas(this.projectRoot);
|
|
419
|
+
const renderer = new StudioViewRenderer({ defaultTheme: theme });
|
|
420
|
+
const html = renderer.renderWebviewStudio(snapshot, ideas, { theme });
|
|
421
|
+
return {
|
|
422
|
+
content: [{ type: "text", text: html }]
|
|
423
|
+
};
|
|
424
|
+
});
|
|
425
|
+
// 12. devflow_swarm_plan
|
|
426
|
+
this.registerTool(DEVFLOW_MCP_TOOLS.find((t) => t.name === "devflow_swarm_plan"), async () => {
|
|
427
|
+
const plan = await generateSwarmPlan(this.projectRoot);
|
|
428
|
+
return {
|
|
429
|
+
content: [{ type: "text", text: JSON.stringify(plan, null, 2) }]
|
|
430
|
+
};
|
|
431
|
+
});
|
|
432
|
+
// 13. devflow_query_code_graph
|
|
433
|
+
this.registerTool(DEVFLOW_MCP_TOOLS.find((t) => t.name === "devflow_query_code_graph"), async (args) => {
|
|
434
|
+
const graph = await buildCodeGraph(this.projectRoot);
|
|
435
|
+
if (typeof args.file === "string" && args.file.trim().length > 0) {
|
|
436
|
+
const blast = calculateBlastRadius(graph, args.file.trim());
|
|
437
|
+
return {
|
|
438
|
+
content: [{ type: "text", text: JSON.stringify(blast, null, 2) }]
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
content: [
|
|
443
|
+
{
|
|
444
|
+
type: "text",
|
|
445
|
+
text: JSON.stringify({
|
|
446
|
+
totalFiles: graph.totalFiles,
|
|
447
|
+
totalEdges: graph.totalEdges,
|
|
448
|
+
files: Object.keys(graph.nodes)
|
|
449
|
+
}, null, 2)
|
|
450
|
+
}
|
|
451
|
+
]
|
|
452
|
+
};
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
async executeTool(name, args = {}) {
|
|
456
|
+
const entry = this.tools.get(name);
|
|
457
|
+
if (!entry) {
|
|
458
|
+
return {
|
|
459
|
+
content: [{ type: "text", text: `Error: Unknown tool name '${name}'` }],
|
|
460
|
+
isError: true
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
try {
|
|
464
|
+
return await entry.handler(args);
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
return {
|
|
468
|
+
content: [
|
|
469
|
+
{
|
|
470
|
+
type: "text",
|
|
471
|
+
text: `Tool execution failed: ${err instanceof Error ? err.message : String(err)}`
|
|
472
|
+
}
|
|
473
|
+
],
|
|
474
|
+
isError: true
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async handleMessage(request) {
|
|
479
|
+
const { id = null, method, params = {} } = request;
|
|
480
|
+
switch (method) {
|
|
481
|
+
case "initialize": {
|
|
482
|
+
return {
|
|
483
|
+
jsonrpc: "2.0",
|
|
484
|
+
id,
|
|
485
|
+
result: {
|
|
486
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
487
|
+
capabilities: {
|
|
488
|
+
tools: {},
|
|
489
|
+
resources: {}
|
|
490
|
+
},
|
|
491
|
+
serverInfo: {
|
|
492
|
+
name: SERVER_NAME,
|
|
493
|
+
version: SERVER_VERSION
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
case "ping": {
|
|
499
|
+
return {
|
|
500
|
+
jsonrpc: "2.0",
|
|
501
|
+
id,
|
|
502
|
+
result: {}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
case "tools/list": {
|
|
506
|
+
return {
|
|
507
|
+
jsonrpc: "2.0",
|
|
508
|
+
id,
|
|
509
|
+
result: {
|
|
510
|
+
tools: DEVFLOW_MCP_TOOLS
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
case "tools/call": {
|
|
515
|
+
const toolName = typeof params.name === "string" ? params.name : "";
|
|
516
|
+
const toolArgs = params.arguments || {};
|
|
517
|
+
const result = await this.executeTool(toolName, toolArgs);
|
|
518
|
+
return {
|
|
519
|
+
jsonrpc: "2.0",
|
|
520
|
+
id,
|
|
521
|
+
result
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
case "resources/list": {
|
|
525
|
+
return {
|
|
526
|
+
jsonrpc: "2.0",
|
|
527
|
+
id,
|
|
528
|
+
result: {
|
|
529
|
+
resources: [
|
|
530
|
+
{
|
|
531
|
+
uri: "devflow://overview",
|
|
532
|
+
name: "Project Overview",
|
|
533
|
+
mimeType: "text/markdown",
|
|
534
|
+
description: "Living project overview and architecture source of truth"
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
uri: "devflow://current-stage",
|
|
538
|
+
name: "Current Stage",
|
|
539
|
+
mimeType: "text/markdown",
|
|
540
|
+
description: "Active delivery run and stage root switch"
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
uri: "devflow://findings",
|
|
544
|
+
name: "Findings Ledger",
|
|
545
|
+
mimeType: "text/markdown",
|
|
546
|
+
description: "Recorded quality and security findings"
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
uri: "devflow://ideas",
|
|
550
|
+
name: "Ideas Inbox",
|
|
551
|
+
mimeType: "text/markdown",
|
|
552
|
+
description: "Idea backlog with feasibility and value assessment"
|
|
553
|
+
}
|
|
554
|
+
]
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
case "resources/read": {
|
|
559
|
+
const uri = typeof params.uri === "string" ? params.uri : "";
|
|
560
|
+
let targetFile = "";
|
|
561
|
+
if (uri === "devflow://overview")
|
|
562
|
+
targetFile = path.join("devflow", "context", "project-overview.md");
|
|
563
|
+
else if (uri === "devflow://current-stage")
|
|
564
|
+
targetFile = path.join("devflow", "context", "current-stage.md");
|
|
565
|
+
else if (uri === "devflow://findings")
|
|
566
|
+
targetFile = path.join("devflow", "context", "findings.md");
|
|
567
|
+
else if (uri === "devflow://ideas")
|
|
568
|
+
targetFile = path.join("devflow", "ideas.md");
|
|
569
|
+
if (!targetFile) {
|
|
570
|
+
return {
|
|
571
|
+
jsonrpc: "2.0",
|
|
572
|
+
id,
|
|
573
|
+
error: {
|
|
574
|
+
code: -32602,
|
|
575
|
+
message: `Invalid resource URI: ${uri}`
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
const text = await fs.readFile(path.join(this.projectRoot, targetFile), "utf8");
|
|
581
|
+
return {
|
|
582
|
+
jsonrpc: "2.0",
|
|
583
|
+
id,
|
|
584
|
+
result: {
|
|
585
|
+
contents: [
|
|
586
|
+
{
|
|
587
|
+
uri,
|
|
588
|
+
mimeType: "text/markdown",
|
|
589
|
+
text
|
|
590
|
+
}
|
|
591
|
+
]
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
catch {
|
|
596
|
+
return {
|
|
597
|
+
jsonrpc: "2.0",
|
|
598
|
+
id,
|
|
599
|
+
result: {
|
|
600
|
+
contents: [
|
|
601
|
+
{
|
|
602
|
+
uri,
|
|
603
|
+
mimeType: "text/markdown",
|
|
604
|
+
text: ""
|
|
605
|
+
}
|
|
606
|
+
]
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
default: {
|
|
612
|
+
return {
|
|
613
|
+
jsonrpc: "2.0",
|
|
614
|
+
id,
|
|
615
|
+
error: {
|
|
616
|
+
code: -32601,
|
|
617
|
+
message: `Method not found: ${method}`
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
async processRawMessage(rawMessage) {
|
|
624
|
+
const trimmed = rawMessage.trim();
|
|
625
|
+
if (!trimmed)
|
|
626
|
+
return null;
|
|
627
|
+
let request;
|
|
628
|
+
try {
|
|
629
|
+
request = JSON.parse(trimmed);
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
return {
|
|
633
|
+
jsonrpc: "2.0",
|
|
634
|
+
id: null,
|
|
635
|
+
error: {
|
|
636
|
+
code: -32700,
|
|
637
|
+
message: "Parse error: Invalid JSON"
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
if (request.method === "initialized" || request.method === "notifications/initialized") {
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
return this.handleMessage(request);
|
|
645
|
+
}
|
|
646
|
+
listen(inStream = process.stdin, outStream = process.stdout) {
|
|
647
|
+
const rl = readline.createInterface({
|
|
648
|
+
input: inStream,
|
|
649
|
+
output: undefined,
|
|
650
|
+
terminal: false
|
|
651
|
+
});
|
|
652
|
+
const sendResponse = (response) => {
|
|
653
|
+
outStream.write(`${JSON.stringify(response)}\n`);
|
|
654
|
+
};
|
|
655
|
+
rl.on("line", async (line) => {
|
|
656
|
+
try {
|
|
657
|
+
const response = await this.processRawMessage(line);
|
|
658
|
+
if (response) {
|
|
659
|
+
sendResponse(response);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch (err) {
|
|
663
|
+
sendResponse({
|
|
664
|
+
jsonrpc: "2.0",
|
|
665
|
+
id: null,
|
|
666
|
+
error: {
|
|
667
|
+
code: -32603,
|
|
668
|
+
message: `Internal error: ${err instanceof Error ? err.message : String(err)}`
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
return () => {
|
|
674
|
+
rl.close();
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
//# sourceMappingURL=mcp-server-engine.js.map
|