@projitive/mcp 1.0.3 → 1.0.5
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 +7 -7
- package/output/package.json +4 -5
- package/output/source/design-context.js +512 -0
- package/output/source/index.js +45 -35
- package/output/source/projitive.js +34 -25
- package/output/source/roadmap.js +2 -2
- package/output/source/tasks.js +4 -4
- package/package.json +4 -5
- package/output/designs.js +0 -38
- package/output/helpers/artifacts/artifacts.js +0 -10
- package/output/helpers/artifacts/artifacts.test.js +0 -18
- package/output/helpers/artifacts/index.js +0 -1
- package/output/helpers/catch/catch.js +0 -48
- package/output/helpers/catch/catch.test.js +0 -43
- package/output/helpers/catch/index.js +0 -1
- package/output/helpers/files/files.js +0 -62
- package/output/helpers/files/files.test.js +0 -32
- package/output/helpers/files/index.js +0 -1
- package/output/helpers/index.js +0 -6
- package/output/helpers/linter/codes.js +0 -25
- package/output/helpers/linter/index.js +0 -2
- package/output/helpers/linter/linter.js +0 -6
- package/output/helpers/linter/linter.test.js +0 -16
- package/output/helpers/markdown/index.js +0 -1
- package/output/helpers/markdown/markdown.js +0 -33
- package/output/helpers/markdown/markdown.test.js +0 -36
- package/output/helpers/response/index.js +0 -1
- package/output/helpers/response/response.js +0 -73
- package/output/helpers/response/response.test.js +0 -50
- package/output/hooks.js +0 -49
- package/output/hooks.test.js +0 -40
- package/output/index.js +0 -227
- package/output/projitive.js +0 -488
- package/output/projitive.test.js +0 -75
- package/output/prompts.js +0 -87
- package/output/readme.js +0 -26
- package/output/rendering-input-guard.test.js +0 -20
- package/output/reports.js +0 -36
- package/output/resources.js +0 -95
- package/output/roadmap.js +0 -165
- package/output/roadmap.test.js +0 -11
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectContext.md +0 -48
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectInit.md +0 -40
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectLocate.md +0 -22
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectNext.md +0 -31
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectScan.md +0 -28
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/roadmapContext.md +0 -33
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/roadmapList.md +0 -25
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/summary.json +0 -90
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/summary.md +0 -17
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskContext.md +0 -47
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskList.md +0 -27
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskNext.md +0 -64
- package/output/tasks.js +0 -762
- package/output/tasks.test.js +0 -152
package/output/source/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import packageJson from "../package.json" with { type: "json" };
|
|
|
10
10
|
import { registerProjectTools } from "./projitive.js";
|
|
11
11
|
import { registerTaskTools } from "./tasks.js";
|
|
12
12
|
import { registerRoadmapTools } from "./roadmap.js";
|
|
13
|
+
import { registerDesignContextResources, registerDesignContextPrompts } from "./design-context.js";
|
|
13
14
|
const PROJITIVE_SPEC_VERSION = "1.0.0";
|
|
14
15
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
15
16
|
const sourceDir = path.dirname(currentFilePath);
|
|
@@ -43,23 +44,24 @@ function renderMethodCatalogMarkdown() {
|
|
|
43
44
|
return [
|
|
44
45
|
"# MCP Method Catalog",
|
|
45
46
|
"",
|
|
46
|
-
"##
|
|
47
|
-
"-
|
|
48
|
-
"-
|
|
47
|
+
"## Start Here",
|
|
48
|
+
"- Unknown project path: `projectScan` -> `projectLocate` -> `projectContext` -> `taskNext`.",
|
|
49
|
+
"- Known project path: `projectContext` -> `taskNext` (or `taskList`) -> `taskContext`.",
|
|
50
|
+
"- Need to bootstrap governance: call `projectInit` only when `.projitive` is missing.",
|
|
49
51
|
"",
|
|
50
52
|
"## Methods",
|
|
51
|
-
"| Group | Method |
|
|
52
|
-
"
|
|
53
|
-
"| Project |
|
|
54
|
-
"| Project |
|
|
55
|
-
"| Project |
|
|
56
|
-
"|
|
|
57
|
-
"|
|
|
58
|
-
"| Task |
|
|
59
|
-
"|
|
|
60
|
-
"|
|
|
61
|
-
"|
|
|
62
|
-
"|
|
|
53
|
+
"| Order | Group | Method | Agent Use |",
|
|
54
|
+
"|---|---|---|---|",
|
|
55
|
+
"| 1 | Project | projectScan | discover governance roots when project is unknown |",
|
|
56
|
+
"| 2 | Project | projectLocate | lock nearest governance root from any path |",
|
|
57
|
+
"| 3 | Project | projectContext | load project summary before task decisions |",
|
|
58
|
+
"| 4 | Task | taskNext | auto-pick highest-priority actionable task |",
|
|
59
|
+
"| 5 | Task | taskList | list/filter tasks for manual selection |",
|
|
60
|
+
"| 6 | Task | taskContext | inspect one task with evidence and read order |",
|
|
61
|
+
"| 7 | Roadmap | roadmapList | inspect roadmap-task linkage |",
|
|
62
|
+
"| 8 | Roadmap | roadmapContext | inspect one roadmap with references |",
|
|
63
|
+
"| 9 | Project | projectNext | rank actionable projects across workspace |",
|
|
64
|
+
"| 10 | Project | projectInit | bootstrap governance files if missing |",
|
|
63
65
|
].join("\n");
|
|
64
66
|
}
|
|
65
67
|
function registerGovernanceResources() {
|
|
@@ -128,23 +130,28 @@ function asUserPrompt(text) {
|
|
|
128
130
|
function registerGovernancePrompts() {
|
|
129
131
|
server.registerPrompt("executeTaskWorkflow", {
|
|
130
132
|
title: "Execute Task Workflow",
|
|
131
|
-
description: "
|
|
133
|
+
description: "Primary execution prompt: select one task, execute, and verify evidence consistency",
|
|
132
134
|
argsSchema: {
|
|
133
135
|
rootPath: z.string().optional(),
|
|
134
136
|
projectPath: z.string().optional(),
|
|
135
137
|
taskId: z.string().optional(),
|
|
136
138
|
},
|
|
137
139
|
}, async ({ rootPath, projectPath, taskId }) => {
|
|
140
|
+
const taskEntry = taskId && projectPath
|
|
141
|
+
? `1) Run taskContext(projectPath=\"${projectPath}\", taskId=\"${taskId}\").`
|
|
142
|
+
: `1) Run taskNext(${rootPath ? `rootPath=\"${rootPath}\"` : ""}).`;
|
|
138
143
|
const text = [
|
|
139
|
-
"You are executing Projitive governance workflow.",
|
|
144
|
+
"You are executing Projitive governance workflow in agent-first mode.",
|
|
140
145
|
"",
|
|
141
|
-
"
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
146
|
+
"Fast path:",
|
|
147
|
+
taskEntry,
|
|
148
|
+
"2) Follow Suggested Read Order and identify execution blockers.",
|
|
149
|
+
"3) Edit governance markdown only (tasks/designs/reports/roadmap).",
|
|
150
|
+
"4) Re-run taskContext for the selected task and verify references.",
|
|
151
|
+
"",
|
|
152
|
+
"Fallbacks:",
|
|
153
|
+
"- If taskNext returns no actionable task, follow its no-task checklist and create 1-3 TODO tasks.",
|
|
154
|
+
"- If project is unknown, run projectScan -> projectLocate -> projectContext before task tools.",
|
|
148
155
|
"",
|
|
149
156
|
"Hard rules:",
|
|
150
157
|
"- Keep TASK/ROADMAP IDs immutable.",
|
|
@@ -155,7 +162,7 @@ function registerGovernancePrompts() {
|
|
|
155
162
|
});
|
|
156
163
|
server.registerPrompt("updateTaskStatusWithEvidence", {
|
|
157
164
|
title: "Update Task Status With Evidence",
|
|
158
|
-
description: "
|
|
165
|
+
description: "Safe status transition playbook with mandatory evidence backfill",
|
|
159
166
|
argsSchema: {
|
|
160
167
|
projectPath: z.string(),
|
|
161
168
|
taskId: z.string(),
|
|
@@ -163,13 +170,14 @@ function registerGovernancePrompts() {
|
|
|
163
170
|
},
|
|
164
171
|
}, async ({ projectPath, taskId, targetStatus }) => {
|
|
165
172
|
const text = [
|
|
166
|
-
"Perform a safe task status update
|
|
173
|
+
"Perform a safe task status update with evidence alignment.",
|
|
167
174
|
"",
|
|
168
175
|
`1) Run taskContext(projectPath=\"${projectPath}\", taskId=\"${taskId}\").`,
|
|
169
|
-
`2)
|
|
176
|
+
`2) Confirm transition to ${targetStatus} is valid.`,
|
|
170
177
|
"3) Update tasks.md status and updatedAt.",
|
|
171
178
|
"4) Add or update a report under reports/ with concrete evidence.",
|
|
172
179
|
"5) Re-run taskContext and confirm status/evidence/reference consistency.",
|
|
180
|
+
"6) If lint remains, fix and re-run taskContext once more.",
|
|
173
181
|
"",
|
|
174
182
|
"Checklist:",
|
|
175
183
|
"- Transition is valid per status machine.",
|
|
@@ -180,29 +188,31 @@ function registerGovernancePrompts() {
|
|
|
180
188
|
});
|
|
181
189
|
server.registerPrompt("triageProjectGovernance", {
|
|
182
190
|
title: "Triage Project Governance",
|
|
183
|
-
description: "
|
|
191
|
+
description: "Discovery-first triage prompt to pick project and next executable task",
|
|
184
192
|
argsSchema: {
|
|
185
193
|
rootPath: z.string().optional(),
|
|
186
194
|
},
|
|
187
195
|
}, async ({ rootPath }) => {
|
|
188
196
|
const text = [
|
|
189
|
-
"Triage governance
|
|
197
|
+
"Triage governance and pick one execution target quickly.",
|
|
190
198
|
"",
|
|
191
|
-
|
|
192
|
-
|
|
199
|
+
"1) If project path is unknown, run projectScan() and pick one discovered project.",
|
|
200
|
+
`2) Run projectNext(${rootPath ? `rootPath=\"${rootPath}\"` : ""}) to rank projects.`,
|
|
193
201
|
"3) Run projectContext(projectPath=<selectedProject>).",
|
|
194
|
-
"4) Run
|
|
195
|
-
"5) If
|
|
196
|
-
"6) Continue with taskContext
|
|
202
|
+
"4) Run taskNext(rootPath=<workspaceRootIfNeeded>) for best actionable task.",
|
|
203
|
+
"5) If manual filtering is needed, run taskList(projectPath=<selectedProject>, status=IN_PROGRESS).",
|
|
204
|
+
"6) Continue with taskContext(projectPath=<selectedProject>, taskId=<selectedTaskId>).",
|
|
197
205
|
].join("\n");
|
|
198
206
|
return asUserPrompt(text);
|
|
199
207
|
});
|
|
200
208
|
}
|
|
201
|
-
registerTaskTools(server);
|
|
202
209
|
registerProjectTools(server);
|
|
210
|
+
registerTaskTools(server);
|
|
203
211
|
registerRoadmapTools(server);
|
|
204
212
|
registerGovernanceResources();
|
|
205
213
|
registerGovernancePrompts();
|
|
214
|
+
registerDesignContextResources(server);
|
|
215
|
+
registerDesignContextPrompts(server);
|
|
206
216
|
async function main() {
|
|
207
217
|
console.error(`[projitive-mcp] starting server`);
|
|
208
218
|
console.error(`[projitive-mcp] version=${MCP_RUNTIME_VERSION} spec=${PROJITIVE_SPEC_VERSION} transport=stdio pid=${process.pid}`);
|
|
@@ -41,15 +41,27 @@ function parseDepthFromEnv(rawDepth) {
|
|
|
41
41
|
}
|
|
42
42
|
return Math.min(MAX_SCAN_DEPTH, Math.max(0, parsed));
|
|
43
43
|
}
|
|
44
|
+
function requireEnvVar(name) {
|
|
45
|
+
const value = process.env[name];
|
|
46
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
47
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
48
|
+
}
|
|
49
|
+
return value.trim();
|
|
50
|
+
}
|
|
44
51
|
export function resolveScanRoot(inputPath) {
|
|
45
|
-
const
|
|
46
|
-
return normalizePath(inputPath ??
|
|
52
|
+
const configuredRoot = requireEnvVar("PROJITIVE_SCAN_ROOT_PATH");
|
|
53
|
+
return normalizePath(inputPath ?? configuredRoot);
|
|
47
54
|
}
|
|
48
55
|
export function resolveScanDepth(inputDepth) {
|
|
56
|
+
const configuredDepthRaw = requireEnvVar("PROJITIVE_SCAN_MAX_DEPTH");
|
|
57
|
+
const configuredDepth = parseDepthFromEnv(configuredDepthRaw);
|
|
58
|
+
if (typeof configuredDepth !== "number") {
|
|
59
|
+
throw new Error("Invalid PROJITIVE_SCAN_MAX_DEPTH: expected integer in range 0-8");
|
|
60
|
+
}
|
|
49
61
|
if (typeof inputDepth === "number") {
|
|
50
62
|
return inputDepth;
|
|
51
63
|
}
|
|
52
|
-
return
|
|
64
|
+
return configuredDepth;
|
|
53
65
|
}
|
|
54
66
|
function renderArtifactsMarkdown(artifacts) {
|
|
55
67
|
const rows = artifacts.map((item) => {
|
|
@@ -226,16 +238,16 @@ function defaultNoTaskDiscoveryHookMarkdown() {
|
|
|
226
238
|
].join("\n");
|
|
227
239
|
}
|
|
228
240
|
export async function initializeProjectStructure(inputPath, governanceDir, force = false) {
|
|
229
|
-
const
|
|
241
|
+
const projectPath = normalizePath(inputPath);
|
|
230
242
|
const governanceDirName = normalizeGovernanceDirName(governanceDir);
|
|
231
|
-
const rootStat = await catchIt(fs.stat(
|
|
243
|
+
const rootStat = await catchIt(fs.stat(projectPath));
|
|
232
244
|
if (rootStat.isError()) {
|
|
233
|
-
throw new Error(`Path not found: ${
|
|
245
|
+
throw new Error(`Path not found: ${projectPath}`);
|
|
234
246
|
}
|
|
235
247
|
if (!rootStat.value.isDirectory()) {
|
|
236
|
-
throw new Error(`
|
|
248
|
+
throw new Error(`projectPath must be a directory: ${projectPath}`);
|
|
237
249
|
}
|
|
238
|
-
const governancePath = path.join(
|
|
250
|
+
const governancePath = path.join(projectPath, governanceDirName);
|
|
239
251
|
const directories = [];
|
|
240
252
|
const requiredDirectories = [governancePath, path.join(governancePath, "designs"), path.join(governancePath, "reports"), path.join(governancePath, "hooks")];
|
|
241
253
|
for (const dirPath of requiredDirectories) {
|
|
@@ -252,7 +264,7 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
252
264
|
writeTextFile(path.join(governancePath, "hooks", "task_no_actionable.md"), defaultNoTaskDiscoveryHookMarkdown(), force),
|
|
253
265
|
]);
|
|
254
266
|
return {
|
|
255
|
-
|
|
267
|
+
projectPath,
|
|
256
268
|
governanceDir: governancePath,
|
|
257
269
|
markerPath,
|
|
258
270
|
directories,
|
|
@@ -262,14 +274,14 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
262
274
|
export function registerProjectTools(server) {
|
|
263
275
|
server.registerTool("projectInit", {
|
|
264
276
|
title: "Project Init",
|
|
265
|
-
description: "
|
|
277
|
+
description: "Bootstrap governance files when a project has no .projitive yet",
|
|
266
278
|
inputSchema: {
|
|
267
|
-
|
|
279
|
+
projectPath: z.string().optional(),
|
|
268
280
|
governanceDir: z.string().optional(),
|
|
269
281
|
force: z.boolean().optional(),
|
|
270
282
|
},
|
|
271
|
-
}, async ({
|
|
272
|
-
const initialized = await initializeProjectStructure(
|
|
283
|
+
}, async ({ projectPath, governanceDir, force }) => {
|
|
284
|
+
const initialized = await initializeProjectStructure(projectPath, governanceDir, force ?? false);
|
|
273
285
|
const filesByAction = {
|
|
274
286
|
created: initialized.files.filter((item) => item.action === "created"),
|
|
275
287
|
updated: initialized.files.filter((item) => item.action === "updated"),
|
|
@@ -279,7 +291,7 @@ export function registerProjectTools(server) {
|
|
|
279
291
|
toolName: "projectInit",
|
|
280
292
|
sections: [
|
|
281
293
|
summarySection([
|
|
282
|
-
`-
|
|
294
|
+
`- projectPath: ${initialized.projectPath}`,
|
|
283
295
|
`- governanceDir: ${initialized.governanceDir}`,
|
|
284
296
|
`- markerPath: ${initialized.markerPath}`,
|
|
285
297
|
`- force: ${force === true ? "true" : "false"}`,
|
|
@@ -308,14 +320,11 @@ export function registerProjectTools(server) {
|
|
|
308
320
|
});
|
|
309
321
|
server.registerTool("projectScan", {
|
|
310
322
|
title: "Project Scan",
|
|
311
|
-
description: "
|
|
312
|
-
inputSchema: {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}, async ({ rootPath, maxDepth }) => {
|
|
317
|
-
const root = resolveScanRoot(rootPath);
|
|
318
|
-
const depth = resolveScanDepth(maxDepth);
|
|
323
|
+
description: "Start here when project path is unknown; discover all governance roots",
|
|
324
|
+
inputSchema: {},
|
|
325
|
+
}, async () => {
|
|
326
|
+
const root = resolveScanRoot();
|
|
327
|
+
const depth = resolveScanDepth();
|
|
319
328
|
const projects = await discoverProjects(root, depth);
|
|
320
329
|
const markdown = renderToolResponseMarkdown({
|
|
321
330
|
toolName: "projectScan",
|
|
@@ -345,7 +354,7 @@ export function registerProjectTools(server) {
|
|
|
345
354
|
});
|
|
346
355
|
server.registerTool("projectNext", {
|
|
347
356
|
title: "Project Next",
|
|
348
|
-
description: "
|
|
357
|
+
description: "Rank actionable projects and return the best execution target",
|
|
349
358
|
inputSchema: {
|
|
350
359
|
rootPath: z.string().optional(),
|
|
351
360
|
maxDepth: z.number().int().min(0).max(8).optional(),
|
|
@@ -414,7 +423,7 @@ export function registerProjectTools(server) {
|
|
|
414
423
|
});
|
|
415
424
|
server.registerTool("projectLocate", {
|
|
416
425
|
title: "Project Locate",
|
|
417
|
-
description: "Resolve
|
|
426
|
+
description: "Resolve the nearest governance root from any in-project path",
|
|
418
427
|
inputSchema: {
|
|
419
428
|
inputPath: z.string(),
|
|
420
429
|
},
|
|
@@ -439,7 +448,7 @@ export function registerProjectTools(server) {
|
|
|
439
448
|
});
|
|
440
449
|
server.registerTool("projectContext", {
|
|
441
450
|
title: "Project Context",
|
|
442
|
-
description: "
|
|
451
|
+
description: "Get project-level summary before selecting or executing a task",
|
|
443
452
|
inputSchema: {
|
|
444
453
|
projectPath: z.string(),
|
|
445
454
|
},
|
package/output/source/roadmap.js
CHANGED
|
@@ -73,7 +73,7 @@ async function readRoadmapIds(governanceDir) {
|
|
|
73
73
|
export function registerRoadmapTools(server) {
|
|
74
74
|
server.registerTool("roadmapList", {
|
|
75
75
|
title: "Roadmap List",
|
|
76
|
-
description: "List roadmap IDs and
|
|
76
|
+
description: "List roadmap IDs and task linkage for planning or traceability",
|
|
77
77
|
inputSchema: {
|
|
78
78
|
projectPath: z.string(),
|
|
79
79
|
},
|
|
@@ -107,7 +107,7 @@ export function registerRoadmapTools(server) {
|
|
|
107
107
|
});
|
|
108
108
|
server.registerTool("roadmapContext", {
|
|
109
109
|
title: "Roadmap Context",
|
|
110
|
-
description: "
|
|
110
|
+
description: "Inspect one roadmap with linked tasks and reference locations",
|
|
111
111
|
inputSchema: {
|
|
112
112
|
projectPath: z.string(),
|
|
113
113
|
roadmapId: z.string(),
|
package/output/source/tasks.js
CHANGED
|
@@ -496,7 +496,7 @@ export function validateTransition(from, to) {
|
|
|
496
496
|
export function registerTaskTools(server) {
|
|
497
497
|
server.registerTool("taskList", {
|
|
498
498
|
title: "Task List",
|
|
499
|
-
description: "List
|
|
499
|
+
description: "List tasks for a known project and optionally filter by status",
|
|
500
500
|
inputSchema: {
|
|
501
501
|
projectPath: z.string(),
|
|
502
502
|
status: z.enum(["TODO", "IN_PROGRESS", "BLOCKED", "DONE"]).optional(),
|
|
@@ -543,7 +543,7 @@ export function registerTaskTools(server) {
|
|
|
543
543
|
});
|
|
544
544
|
server.registerTool("taskNext", {
|
|
545
545
|
title: "Task Next",
|
|
546
|
-
description: "
|
|
546
|
+
description: "Start here to auto-select the highest-priority actionable task",
|
|
547
547
|
inputSchema: {
|
|
548
548
|
rootPath: z.string().optional(),
|
|
549
549
|
maxDepth: z.number().int().min(0).max(8).optional(),
|
|
@@ -611,7 +611,7 @@ export function registerTaskTools(server) {
|
|
|
611
611
|
]),
|
|
612
612
|
nextCallSection(preferredProject
|
|
613
613
|
? `projectContext(projectPath=\"${preferredProject.governanceDir}\")`
|
|
614
|
-
:
|
|
614
|
+
: "projectScan()"),
|
|
615
615
|
],
|
|
616
616
|
});
|
|
617
617
|
return asText(markdown);
|
|
@@ -680,7 +680,7 @@ export function registerTaskTools(server) {
|
|
|
680
680
|
});
|
|
681
681
|
server.registerTool("taskContext", {
|
|
682
682
|
title: "Task Context",
|
|
683
|
-
description: "Get
|
|
683
|
+
description: "Get deep context, evidence links, and read order for one task",
|
|
684
684
|
inputSchema: {
|
|
685
685
|
projectPath: z.string(),
|
|
686
686
|
taskId: z.string(),
|
package/package.json
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@projitive/mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Projitive MCP Server for project and task discovery/update",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
9
|
-
"mcp": "output/index.js"
|
|
9
|
+
"mcp": "output/source/index.js"
|
|
10
10
|
},
|
|
11
|
-
"main": "./output/index.js",
|
|
12
|
-
"types": "./output/index.d.ts",
|
|
11
|
+
"main": "./output/source/index.js",
|
|
13
12
|
"publishConfig": {
|
|
14
13
|
"access": "public"
|
|
15
14
|
},
|
|
16
15
|
"scripts": {
|
|
17
16
|
"test": "vitest run",
|
|
18
17
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
19
|
-
"build": "tsc -p tsconfig.json",
|
|
18
|
+
"build": "rm -rf output && tsc -p tsconfig.json",
|
|
20
19
|
"prepublishOnly": "npm run build",
|
|
21
20
|
"dev": "tsc -p tsconfig.json --watch"
|
|
22
21
|
},
|
package/output/designs.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { isValidRoadmapId } from "./roadmap.js";
|
|
2
|
-
import { isValidTaskId } from "./tasks.js";
|
|
3
|
-
export function parseDesignMetadata(markdown) {
|
|
4
|
-
const lines = markdown.split(/\r?\n/);
|
|
5
|
-
const metadata = {};
|
|
6
|
-
for (const line of lines) {
|
|
7
|
-
const [rawKey, ...rawValue] = line.split(":");
|
|
8
|
-
if (!rawKey || rawValue.length === 0) {
|
|
9
|
-
continue;
|
|
10
|
-
}
|
|
11
|
-
const key = rawKey.trim().toLowerCase();
|
|
12
|
-
const value = rawValue.join(":").trim();
|
|
13
|
-
if (key === "task")
|
|
14
|
-
metadata.task = value;
|
|
15
|
-
if (key === "roadmap")
|
|
16
|
-
metadata.roadmap = value;
|
|
17
|
-
if (key === "owner")
|
|
18
|
-
metadata.owner = value;
|
|
19
|
-
if (key === "status")
|
|
20
|
-
metadata.status = value;
|
|
21
|
-
if (key === "last updated")
|
|
22
|
-
metadata.lastUpdated = value;
|
|
23
|
-
}
|
|
24
|
-
return metadata;
|
|
25
|
-
}
|
|
26
|
-
export function validateDesignMetadata(metadata) {
|
|
27
|
-
const errors = [];
|
|
28
|
-
if (!metadata.task) {
|
|
29
|
-
errors.push("Missing Task metadata");
|
|
30
|
-
}
|
|
31
|
-
else if (!isValidTaskId(metadata.task)) {
|
|
32
|
-
errors.push(`Invalid Task metadata format: ${metadata.task}`);
|
|
33
|
-
}
|
|
34
|
-
if (metadata.roadmap && !isValidRoadmapId(metadata.roadmap)) {
|
|
35
|
-
errors.push(`Invalid Roadmap metadata format: ${metadata.roadmap}`);
|
|
36
|
-
}
|
|
37
|
-
return { ok: errors.length === 0, errors };
|
|
38
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export function candidateFilesFromArtifacts(artifacts) {
|
|
2
|
-
return artifacts
|
|
3
|
-
.filter((item) => item.exists)
|
|
4
|
-
.flatMap((item) => {
|
|
5
|
-
if (item.kind === "file") {
|
|
6
|
-
return [item.path];
|
|
7
|
-
}
|
|
8
|
-
return (item.markdownFiles ?? []).map((entry) => entry.path);
|
|
9
|
-
});
|
|
10
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { candidateFilesFromArtifacts } from "./artifacts.js";
|
|
3
|
-
describe("candidateFilesFromArtifacts", () => {
|
|
4
|
-
it("collects existing file artifacts and markdown files from existing directories", () => {
|
|
5
|
-
const candidates = candidateFilesFromArtifacts([
|
|
6
|
-
{ name: "README.md", kind: "file", path: "/a/README.md", exists: true, lineCount: 3 },
|
|
7
|
-
{ name: "tasks.md", kind: "file", path: "/a/tasks.md", exists: false },
|
|
8
|
-
{
|
|
9
|
-
name: "designs",
|
|
10
|
-
kind: "directory",
|
|
11
|
-
path: "/a/designs",
|
|
12
|
-
exists: true,
|
|
13
|
-
markdownFiles: [{ path: "/a/designs/d1.md", lineCount: 10 }],
|
|
14
|
-
},
|
|
15
|
-
]);
|
|
16
|
-
expect(candidates).toEqual(["/a/README.md", "/a/designs/d1.md"]);
|
|
17
|
-
});
|
|
18
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./artifacts.js";
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
// 辅助函数:检查是否为 PromiseLike
|
|
2
|
-
function isPromiseLike(value) {
|
|
3
|
-
return value != null && typeof value === 'object' && 'then' in value && typeof value.then === 'function';
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* 构造成功结果对象
|
|
7
|
-
* isError 始终返回 false
|
|
8
|
-
*/
|
|
9
|
-
function createSuccess(value) {
|
|
10
|
-
return {
|
|
11
|
-
value,
|
|
12
|
-
error: undefined,
|
|
13
|
-
isError() { return false; }
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* 构造失败结果对象
|
|
18
|
-
* isError 始终返回 true
|
|
19
|
-
*/
|
|
20
|
-
function createFailure(error) {
|
|
21
|
-
return {
|
|
22
|
-
error,
|
|
23
|
-
value: undefined,
|
|
24
|
-
isError() { return true; }
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
export async function catchIt(input) {
|
|
28
|
-
try {
|
|
29
|
-
if (isPromiseLike(input)) {
|
|
30
|
-
const result = await Promise.resolve(input);
|
|
31
|
-
return createSuccess(result);
|
|
32
|
-
}
|
|
33
|
-
else if (typeof input === 'function') {
|
|
34
|
-
const result = input();
|
|
35
|
-
if (isPromiseLike(result)) {
|
|
36
|
-
return catchIt(result);
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
return createSuccess(result);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
return createFailure(error);
|
|
45
|
-
}
|
|
46
|
-
// 理论上不会到达这里,兜底类型安全
|
|
47
|
-
return createFailure(new Error('Unexpected input type'));
|
|
48
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { catchIt } from './catch.js';
|
|
3
|
-
describe('catchIt', () => {
|
|
4
|
-
it('同步函数返回值应为 value,error 为 undefined,isError 为 false', async () => {
|
|
5
|
-
const result = await catchIt(() => 123);
|
|
6
|
-
expect(result.value).toBe(123);
|
|
7
|
-
expect(result.error).toBeUndefined();
|
|
8
|
-
expect(result.isError()).toBe(false);
|
|
9
|
-
});
|
|
10
|
-
it('异步函数返回值应为 value,error 为 undefined,isError 为 false', async () => {
|
|
11
|
-
const result = await catchIt(async () => 456);
|
|
12
|
-
expect(result.value).toBe(456);
|
|
13
|
-
expect(result.error).toBeUndefined();
|
|
14
|
-
expect(result.isError()).toBe(false);
|
|
15
|
-
});
|
|
16
|
-
it('同步抛出异常时应返回 error,value 为 undefined,isError 为 true', async () => {
|
|
17
|
-
const error = new Error('fail');
|
|
18
|
-
const result = await catchIt(() => { throw error; });
|
|
19
|
-
expect(result.value).toBeUndefined();
|
|
20
|
-
expect(result.error).toBe(error);
|
|
21
|
-
expect(result.isError()).toBe(true);
|
|
22
|
-
});
|
|
23
|
-
it('异步抛出异常时应返回 error,value 为 undefined,isError 为 true', async () => {
|
|
24
|
-
const error = new Error('fail-async');
|
|
25
|
-
const result = await catchIt(() => Promise.reject(error));
|
|
26
|
-
expect(result.value).toBeUndefined();
|
|
27
|
-
expect(result.error).toBe(error);
|
|
28
|
-
expect(result.isError()).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
it('PromiseLike resolve 时应返回 value,error 为 undefined,isError 为 false', async () => {
|
|
31
|
-
const result = await catchIt(Promise.resolve('ok'));
|
|
32
|
-
expect(result.value).toBe('ok');
|
|
33
|
-
expect(result.error).toBeUndefined();
|
|
34
|
-
expect(result.isError()).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
it('PromiseLike reject 时应返回 error,value 为 undefined,isError 为 true', async () => {
|
|
37
|
-
const error = new Error('promise-fail');
|
|
38
|
-
const result = await catchIt(Promise.reject(error));
|
|
39
|
-
expect(result.value).toBeUndefined();
|
|
40
|
-
expect(result.error).toBe(error);
|
|
41
|
-
expect(result.isError()).toBe(true);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './catch.js';
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { catchIt } from "../catch/index.js";
|
|
4
|
-
const FILE_ARTIFACTS = ["README.md", "roadmap.md", "tasks.md"];
|
|
5
|
-
const DIRECTORY_ARTIFACTS = ["designs", "reports", "hooks"];
|
|
6
|
-
async function fileLineCount(filePath) {
|
|
7
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
8
|
-
if (!content) {
|
|
9
|
-
return 0;
|
|
10
|
-
}
|
|
11
|
-
return content.split(/\r?\n/).length;
|
|
12
|
-
}
|
|
13
|
-
async function listMarkdownFiles(dirPath) {
|
|
14
|
-
const entriesResult = await catchIt(fs.readdir(dirPath, { withFileTypes: true }));
|
|
15
|
-
if (entriesResult.isError()) {
|
|
16
|
-
return [];
|
|
17
|
-
}
|
|
18
|
-
const entries = entriesResult.value;
|
|
19
|
-
const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md"));
|
|
20
|
-
const result = [];
|
|
21
|
-
for (const file of files) {
|
|
22
|
-
const fullPath = path.join(dirPath, file.name);
|
|
23
|
-
result.push({ path: fullPath, lineCount: await fileLineCount(fullPath) });
|
|
24
|
-
}
|
|
25
|
-
return result.sort((a, b) => a.path.localeCompare(b.path));
|
|
26
|
-
}
|
|
27
|
-
export async function discoverGovernanceArtifacts(governanceDir) {
|
|
28
|
-
const result = [];
|
|
29
|
-
for (const artifact of FILE_ARTIFACTS) {
|
|
30
|
-
const artifactPath = path.join(governanceDir, artifact);
|
|
31
|
-
const accessResult = await catchIt(fs.access(artifactPath));
|
|
32
|
-
if (!accessResult.isError()) {
|
|
33
|
-
result.push({
|
|
34
|
-
name: artifact,
|
|
35
|
-
kind: "file",
|
|
36
|
-
path: artifactPath,
|
|
37
|
-
exists: true,
|
|
38
|
-
lineCount: await fileLineCount(artifactPath),
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
result.push({ name: artifact, kind: "file", path: artifactPath, exists: false });
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
for (const artifact of DIRECTORY_ARTIFACTS) {
|
|
46
|
-
const artifactPath = path.join(governanceDir, artifact);
|
|
47
|
-
const accessResult = await catchIt(fs.access(artifactPath));
|
|
48
|
-
if (!accessResult.isError()) {
|
|
49
|
-
result.push({
|
|
50
|
-
name: artifact,
|
|
51
|
-
kind: "directory",
|
|
52
|
-
path: artifactPath,
|
|
53
|
-
exists: true,
|
|
54
|
-
markdownFiles: await listMarkdownFiles(artifactPath),
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
result.push({ name: artifact, kind: "directory", path: artifactPath, exists: false, markdownFiles: [] });
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { discoverGovernanceArtifacts } from "./files.js";
|
|
6
|
-
const tempPaths = [];
|
|
7
|
-
async function createTempDir() {
|
|
8
|
-
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "projitive-mcp-test-"));
|
|
9
|
-
tempPaths.push(dir);
|
|
10
|
-
return dir;
|
|
11
|
-
}
|
|
12
|
-
afterEach(async () => {
|
|
13
|
-
await Promise.all(tempPaths.splice(0).map(async (dir) => {
|
|
14
|
-
await fs.rm(dir, { recursive: true, force: true });
|
|
15
|
-
}));
|
|
16
|
-
});
|
|
17
|
-
describe("files module", () => {
|
|
18
|
-
it("discovers governance artifacts with paths and line counts", async () => {
|
|
19
|
-
const root = await createTempDir();
|
|
20
|
-
await fs.writeFile(path.join(root, "README.md"), "# Readme\n", "utf-8");
|
|
21
|
-
await fs.writeFile(path.join(root, "tasks.md"), "# Tasks\n## TODO\n", "utf-8");
|
|
22
|
-
await fs.mkdir(path.join(root, "designs"), { recursive: true });
|
|
23
|
-
await fs.writeFile(path.join(root, "designs", "feature-design.md"), "# Design\n", "utf-8");
|
|
24
|
-
const artifacts = await discoverGovernanceArtifacts(root);
|
|
25
|
-
const readme = artifacts.find((item) => item.name === "README.md");
|
|
26
|
-
const designs = artifacts.find((item) => item.name === "designs");
|
|
27
|
-
expect(readme?.exists).toBe(true);
|
|
28
|
-
expect(readme?.lineCount).toBe(2);
|
|
29
|
-
expect(designs?.exists).toBe(true);
|
|
30
|
-
expect(designs?.markdownFiles?.[0].path.endsWith("feature-design.md")).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './files.js';
|