@projitive/mcp 1.0.4 → 1.0.6
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/output/package.json +4 -5
- package/output/source/design-context.js +515 -0
- package/output/source/index.js +50 -41
- package/output/source/projitive.js +8 -9
- package/output/source/roadmap.js +2 -2
- package/output/source/tasks.js +5 -6
- 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(projectPath=\"<project-dir>\")` 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
|
-
rootPath: z.string().optional(),
|
|
134
135
|
projectPath: z.string().optional(),
|
|
135
136
|
taskId: z.string().optional(),
|
|
136
137
|
},
|
|
137
|
-
}, async ({
|
|
138
|
+
}, async ({ projectPath, taskId }) => {
|
|
139
|
+
const taskEntry = taskId && projectPath
|
|
140
|
+
? `1) Run taskContext(projectPath=\"${projectPath}\", taskId=\"${taskId}\").`
|
|
141
|
+
: "1) Run taskNext().";
|
|
138
142
|
const text = [
|
|
139
|
-
"You are executing Projitive governance workflow.",
|
|
143
|
+
"You are executing Projitive governance workflow in agent-first mode.",
|
|
140
144
|
"",
|
|
141
|
-
"
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
145
|
+
"Fast path:",
|
|
146
|
+
taskEntry,
|
|
147
|
+
"2) Follow Suggested Read Order and identify execution blockers.",
|
|
148
|
+
"3) Edit governance markdown only (tasks/designs/reports/roadmap).",
|
|
149
|
+
"4) Re-run taskContext for the selected task and verify references.",
|
|
150
|
+
"",
|
|
151
|
+
"Fallbacks:",
|
|
152
|
+
"- If `.projitive` is missing for a known project, run `projectInit(projectPath=\"<project-dir>\")` first.",
|
|
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,30 @@ function registerGovernancePrompts() {
|
|
|
180
188
|
});
|
|
181
189
|
server.registerPrompt("triageProjectGovernance", {
|
|
182
190
|
title: "Triage Project Governance",
|
|
183
|
-
description: "
|
|
184
|
-
argsSchema: {
|
|
185
|
-
|
|
186
|
-
},
|
|
187
|
-
}, async ({ rootPath }) => {
|
|
191
|
+
description: "Discovery-first triage prompt to pick project and next executable task",
|
|
192
|
+
argsSchema: {},
|
|
193
|
+
}, async () => {
|
|
188
194
|
const text = [
|
|
189
|
-
"Triage governance
|
|
195
|
+
"Triage governance and pick one execution target quickly.",
|
|
190
196
|
"",
|
|
191
|
-
|
|
192
|
-
"
|
|
197
|
+
"0) If known project has no `.projitive`, run projectInit(projectPath=<project-dir>) first.",
|
|
198
|
+
"1) If project path is unknown, run projectScan() and pick one discovered project.",
|
|
199
|
+
"2) Run projectNext() to rank projects.",
|
|
193
200
|
"3) Run projectContext(projectPath=<selectedProject>).",
|
|
194
|
-
"4) Run
|
|
195
|
-
"5) If
|
|
196
|
-
"6) Continue with taskContext
|
|
201
|
+
"4) Run taskNext() for best actionable task.",
|
|
202
|
+
"5) If manual filtering is needed, run taskList(projectPath=<selectedProject>, status=IN_PROGRESS).",
|
|
203
|
+
"6) Continue with taskContext(projectPath=<selectedProject>, taskId=<selectedTaskId>).",
|
|
197
204
|
].join("\n");
|
|
198
205
|
return asUserPrompt(text);
|
|
199
206
|
});
|
|
200
207
|
}
|
|
201
|
-
registerTaskTools(server);
|
|
202
208
|
registerProjectTools(server);
|
|
209
|
+
registerTaskTools(server);
|
|
203
210
|
registerRoadmapTools(server);
|
|
204
211
|
registerGovernanceResources();
|
|
205
212
|
registerGovernancePrompts();
|
|
213
|
+
registerDesignContextResources(server);
|
|
214
|
+
registerDesignContextPrompts(server);
|
|
206
215
|
async function main() {
|
|
207
216
|
console.error(`[projitive-mcp] starting server`);
|
|
208
217
|
console.error(`[projitive-mcp] version=${MCP_RUNTIME_VERSION} spec=${PROJITIVE_SPEC_VERSION} transport=stdio pid=${process.pid}`);
|
|
@@ -274,9 +274,9 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
274
274
|
export function registerProjectTools(server) {
|
|
275
275
|
server.registerTool("projectInit", {
|
|
276
276
|
title: "Project Init",
|
|
277
|
-
description: "
|
|
277
|
+
description: "Bootstrap governance files when a project has no .projitive yet",
|
|
278
278
|
inputSchema: {
|
|
279
|
-
projectPath: z.string()
|
|
279
|
+
projectPath: z.string(),
|
|
280
280
|
governanceDir: z.string().optional(),
|
|
281
281
|
force: z.boolean().optional(),
|
|
282
282
|
},
|
|
@@ -320,7 +320,7 @@ export function registerProjectTools(server) {
|
|
|
320
320
|
});
|
|
321
321
|
server.registerTool("projectScan", {
|
|
322
322
|
title: "Project Scan",
|
|
323
|
-
description: "
|
|
323
|
+
description: "Start here when project path is unknown; discover all governance roots",
|
|
324
324
|
inputSchema: {},
|
|
325
325
|
}, async () => {
|
|
326
326
|
const root = resolveScanRoot();
|
|
@@ -354,14 +354,13 @@ export function registerProjectTools(server) {
|
|
|
354
354
|
});
|
|
355
355
|
server.registerTool("projectNext", {
|
|
356
356
|
title: "Project Next",
|
|
357
|
-
description: "
|
|
357
|
+
description: "Rank actionable projects and return the best execution target",
|
|
358
358
|
inputSchema: {
|
|
359
|
-
rootPath: z.string().optional(),
|
|
360
359
|
maxDepth: z.number().int().min(0).max(8).optional(),
|
|
361
360
|
limit: z.number().int().min(1).max(50).optional(),
|
|
362
361
|
},
|
|
363
|
-
}, async ({
|
|
364
|
-
const root = resolveScanRoot(
|
|
362
|
+
}, async ({ maxDepth, limit }) => {
|
|
363
|
+
const root = resolveScanRoot();
|
|
365
364
|
const depth = resolveScanDepth(maxDepth);
|
|
366
365
|
const projects = await discoverProjects(root, depth);
|
|
367
366
|
const snapshots = await Promise.all(projects.map(async (governanceDir) => {
|
|
@@ -423,7 +422,7 @@ export function registerProjectTools(server) {
|
|
|
423
422
|
});
|
|
424
423
|
server.registerTool("projectLocate", {
|
|
425
424
|
title: "Project Locate",
|
|
426
|
-
description: "Resolve
|
|
425
|
+
description: "Resolve the nearest governance root from any in-project path",
|
|
427
426
|
inputSchema: {
|
|
428
427
|
inputPath: z.string(),
|
|
429
428
|
},
|
|
@@ -448,7 +447,7 @@ export function registerProjectTools(server) {
|
|
|
448
447
|
});
|
|
449
448
|
server.registerTool("projectContext", {
|
|
450
449
|
title: "Project Context",
|
|
451
|
-
description: "
|
|
450
|
+
description: "Get project-level summary before selecting or executing a task",
|
|
452
451
|
inputSchema: {
|
|
453
452
|
projectPath: z.string(),
|
|
454
453
|
},
|
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,14 +543,13 @@ 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
|
-
rootPath: z.string().optional(),
|
|
549
548
|
maxDepth: z.number().int().min(0).max(8).optional(),
|
|
550
549
|
topCandidates: z.number().int().min(1).max(20).optional(),
|
|
551
550
|
},
|
|
552
|
-
}, async ({
|
|
553
|
-
const root = resolveScanRoot(
|
|
551
|
+
}, async ({ maxDepth, topCandidates }) => {
|
|
552
|
+
const root = resolveScanRoot();
|
|
554
553
|
const depth = resolveScanDepth(maxDepth);
|
|
555
554
|
const projects = await discoverProjects(root, depth);
|
|
556
555
|
const rankedCandidates = rankActionableTaskCandidates(await readActionableTaskCandidates(projects));
|
|
@@ -680,7 +679,7 @@ export function registerTaskTools(server) {
|
|
|
680
679
|
});
|
|
681
680
|
server.registerTool("taskContext", {
|
|
682
681
|
title: "Task Context",
|
|
683
|
-
description: "Get
|
|
682
|
+
description: "Get deep context, evidence links, and read order for one task",
|
|
684
683
|
inputSchema: {
|
|
685
684
|
projectPath: z.string(),
|
|
686
685
|
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.6",
|
|
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';
|
package/output/helpers/index.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export const TASK_LINT_CODES = {
|
|
2
|
-
DUPLICATE_ID: "TASK_DUPLICATE_ID",
|
|
3
|
-
IN_PROGRESS_OWNER_EMPTY: "TASK_IN_PROGRESS_OWNER_EMPTY",
|
|
4
|
-
DONE_LINKS_MISSING: "TASK_DONE_LINKS_MISSING",
|
|
5
|
-
BLOCKED_SUMMARY_EMPTY: "TASK_BLOCKED_SUMMARY_EMPTY",
|
|
6
|
-
UPDATED_AT_INVALID: "TASK_UPDATED_AT_INVALID",
|
|
7
|
-
ROADMAP_REFS_EMPTY: "TASK_ROADMAP_REFS_EMPTY",
|
|
8
|
-
OUTSIDE_MARKER: "TASK_OUTSIDE_MARKER",
|
|
9
|
-
LINK_TARGET_MISSING: "TASK_LINK_TARGET_MISSING",
|
|
10
|
-
HOOK_FILE_MISSING: "TASK_HOOK_FILE_MISSING",
|
|
11
|
-
FILTER_EMPTY: "TASK_FILTER_EMPTY",
|
|
12
|
-
CONTEXT_HOOK_HEAD_MISSING: "TASK_CONTEXT_HOOK_HEAD_MISSING",
|
|
13
|
-
CONTEXT_HOOK_FOOTER_MISSING: "TASK_CONTEXT_HOOK_FOOTER_MISSING",
|
|
14
|
-
};
|
|
15
|
-
export const ROADMAP_LINT_CODES = {
|
|
16
|
-
IDS_EMPTY: "ROADMAP_IDS_EMPTY",
|
|
17
|
-
TASKS_EMPTY: "ROADMAP_TASKS_EMPTY",
|
|
18
|
-
TASK_REFS_EMPTY: "ROADMAP_TASK_REFS_EMPTY",
|
|
19
|
-
UNKNOWN_REFS: "ROADMAP_UNKNOWN_REFS",
|
|
20
|
-
ZERO_LINKED_TASKS: "ROADMAP_ZERO_LINKED_TASKS",
|
|
21
|
-
CONTEXT_RELATED_TASKS_EMPTY: "ROADMAP_CONTEXT_RELATED_TASKS_EMPTY",
|
|
22
|
-
};
|
|
23
|
-
export const PROJECT_LINT_CODES = {
|
|
24
|
-
TASKS_FILE_MISSING: "PROJECT_TASKS_FILE_MISSING",
|
|
25
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { renderLintSuggestions } from "./linter.js";
|
|
3
|
-
describe("renderLintSuggestions", () => {
|
|
4
|
-
it("renders lint lines with code and message", () => {
|
|
5
|
-
const lines = renderLintSuggestions([
|
|
6
|
-
{ code: "TASK_001", message: "Example lint" },
|
|
7
|
-
]);
|
|
8
|
-
expect(lines).toEqual(["- [TASK_001] Example lint"]);
|
|
9
|
-
});
|
|
10
|
-
it("appends fixHint when provided", () => {
|
|
11
|
-
const lines = renderLintSuggestions([
|
|
12
|
-
{ code: "TASK_002", message: "Missing field.", fixHint: "Set owner." },
|
|
13
|
-
]);
|
|
14
|
-
expect(lines).toEqual(["- [TASK_002] Missing field. Set owner."]);
|
|
15
|
-
});
|
|
16
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './markdown.js';
|