@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.
Files changed (55) hide show
  1. package/README.md +7 -7
  2. package/output/package.json +4 -5
  3. package/output/source/design-context.js +512 -0
  4. package/output/source/index.js +45 -35
  5. package/output/source/projitive.js +34 -25
  6. package/output/source/roadmap.js +2 -2
  7. package/output/source/tasks.js +4 -4
  8. package/package.json +4 -5
  9. package/output/designs.js +0 -38
  10. package/output/helpers/artifacts/artifacts.js +0 -10
  11. package/output/helpers/artifacts/artifacts.test.js +0 -18
  12. package/output/helpers/artifacts/index.js +0 -1
  13. package/output/helpers/catch/catch.js +0 -48
  14. package/output/helpers/catch/catch.test.js +0 -43
  15. package/output/helpers/catch/index.js +0 -1
  16. package/output/helpers/files/files.js +0 -62
  17. package/output/helpers/files/files.test.js +0 -32
  18. package/output/helpers/files/index.js +0 -1
  19. package/output/helpers/index.js +0 -6
  20. package/output/helpers/linter/codes.js +0 -25
  21. package/output/helpers/linter/index.js +0 -2
  22. package/output/helpers/linter/linter.js +0 -6
  23. package/output/helpers/linter/linter.test.js +0 -16
  24. package/output/helpers/markdown/index.js +0 -1
  25. package/output/helpers/markdown/markdown.js +0 -33
  26. package/output/helpers/markdown/markdown.test.js +0 -36
  27. package/output/helpers/response/index.js +0 -1
  28. package/output/helpers/response/response.js +0 -73
  29. package/output/helpers/response/response.test.js +0 -50
  30. package/output/hooks.js +0 -49
  31. package/output/hooks.test.js +0 -40
  32. package/output/index.js +0 -227
  33. package/output/projitive.js +0 -488
  34. package/output/projitive.test.js +0 -75
  35. package/output/prompts.js +0 -87
  36. package/output/readme.js +0 -26
  37. package/output/rendering-input-guard.test.js +0 -20
  38. package/output/reports.js +0 -36
  39. package/output/resources.js +0 -95
  40. package/output/roadmap.js +0 -165
  41. package/output/roadmap.test.js +0 -11
  42. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectContext.md +0 -48
  43. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectInit.md +0 -40
  44. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectLocate.md +0 -22
  45. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectNext.md +0 -31
  46. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectScan.md +0 -28
  47. package/output/smoke-reports/2026-02-18T13-18-19-740Z/roadmapContext.md +0 -33
  48. package/output/smoke-reports/2026-02-18T13-18-19-740Z/roadmapList.md +0 -25
  49. package/output/smoke-reports/2026-02-18T13-18-19-740Z/summary.json +0 -90
  50. package/output/smoke-reports/2026-02-18T13-18-19-740Z/summary.md +0 -17
  51. package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskContext.md +0 -47
  52. package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskList.md +0 -27
  53. package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskNext.md +0 -64
  54. package/output/tasks.js +0 -762
  55. package/output/tasks.test.js +0 -152
@@ -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
- "## Core Pattern",
47
- "- Prefer List/Context for primary discovery/detail flows.",
48
- "- Use Next/Scan/Locate for acceleration and bootstrapping.",
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 | Role |",
52
- "|---|---|---|",
53
- "| Project | projectInit | initialize governance directory structure |",
54
- "| Project | projectScan | discover governance projects by marker |",
55
- "| Project | projectNext | rank actionable projects |",
56
- "| Project | projectLocate | resolve nearest governance root |",
57
- "| Project | projectContext | summarize project governance context |",
58
- "| Task | taskList | list tasks with optional filters |",
59
- "| Task | taskNext | select top actionable task |",
60
- "| Task | taskContext | inspect one task with references |",
61
- "| Roadmap | roadmapList | list roadmap IDs and linked tasks |",
62
- "| Roadmap | roadmapContext | inspect one roadmap with references |",
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: "Guide an agent through taskNext -> taskContext -> artifact update -> verification",
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
- "Execution order:",
142
- taskId && projectPath
143
- ? `1) Run taskContext(projectPath=\"${projectPath}\", taskId=\"${taskId}\").`
144
- : `1) Run taskNext(${rootPath ? `rootPath=\"${rootPath}\"` : ""}).`,
145
- "2) Read Suggested Read Order and collect blocking gaps.",
146
- "3) Update markdown artifacts only (tasks/designs/reports/roadmap as needed).",
147
- "4) Re-run taskContext for the selected task and verify references are consistent.",
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: "Template for safe task status transitions and evidence alignment",
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 using Projitive rules.",
173
+ "Perform a safe task status update with evidence alignment.",
167
174
  "",
168
175
  `1) Run taskContext(projectPath=\"${projectPath}\", taskId=\"${taskId}\").`,
169
- `2) Plan status transition toward ${targetStatus}.`,
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: "Template to inspect a project and select next actionable governance task",
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 across projects and pick execution target.",
197
+ "Triage governance and pick one execution target quickly.",
190
198
  "",
191
- `1) Run projectNext(${rootPath ? `rootPath=\"${rootPath}\"` : ""}).`,
192
- "2) Select top ranked project.",
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 taskList(projectPath=<selectedProject>, status=IN_PROGRESS).",
195
- "5) If none, run taskNext to select TODO/IN_PROGRESS candidate.",
196
- "6) Continue with taskContext for detailed evidence mapping.",
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 fallback = process.env.PROJITIVE_SCAN_ROOT_PATH;
46
- return normalizePath(inputPath ?? fallback);
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 parseDepthFromEnv(process.env.PROJITIVE_SCAN_MAX_DEPTH) ?? DEFAULT_SCAN_DEPTH;
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 rootPath = normalizePath(inputPath);
241
+ const projectPath = normalizePath(inputPath);
230
242
  const governanceDirName = normalizeGovernanceDirName(governanceDir);
231
- const rootStat = await catchIt(fs.stat(rootPath));
243
+ const rootStat = await catchIt(fs.stat(projectPath));
232
244
  if (rootStat.isError()) {
233
- throw new Error(`Path not found: ${rootPath}`);
245
+ throw new Error(`Path not found: ${projectPath}`);
234
246
  }
235
247
  if (!rootStat.value.isDirectory()) {
236
- throw new Error(`rootPath must be a directory: ${rootPath}`);
248
+ throw new Error(`projectPath must be a directory: ${projectPath}`);
237
249
  }
238
- const governancePath = path.join(rootPath, governanceDirName);
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
- rootPath,
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: "Initialize Projitive governance directory structure manually (default .projitive)",
277
+ description: "Bootstrap governance files when a project has no .projitive yet",
266
278
  inputSchema: {
267
- rootPath: z.string().optional(),
279
+ projectPath: z.string().optional(),
268
280
  governanceDir: z.string().optional(),
269
281
  force: z.boolean().optional(),
270
282
  },
271
- }, async ({ rootPath, governanceDir, force }) => {
272
- const initialized = await initializeProjectStructure(rootPath, governanceDir, force ?? false);
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
- `- rootPath: ${initialized.rootPath}`,
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: "Scan filesystem and discover project governance roots marked by .projitive",
312
- inputSchema: {
313
- rootPath: z.string().optional(),
314
- maxDepth: z.number().int().min(0).max(8).optional(),
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: "Directly list recently actionable projects for immediate agent progression",
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 current project governance root from an in-project path by finding the nearest .projitive marker",
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: "Summarize project governance context for task execution planning",
451
+ description: "Get project-level summary before selecting or executing a task",
443
452
  inputSchema: {
444
453
  projectPath: z.string(),
445
454
  },
@@ -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 related tasks for project planning",
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: "Get one roadmap with related tasks, references, and execution context",
110
+ description: "Inspect one roadmap with linked tasks and reference locations",
111
111
  inputSchema: {
112
112
  projectPath: z.string(),
113
113
  roadmapId: z.string(),
@@ -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 project tasks with optional status filter for agent planning",
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: "One-step discover and select the most actionable task with evidence and start guidance",
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
- : `projectScan(rootPath=\"${root}\", maxDepth=${depth})`),
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 one task with detail, evidence locations, and execution guidance",
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",
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';
@@ -1,6 +0,0 @@
1
- export * from './artifacts/index.js';
2
- export * from './catch/index.js';
3
- export * from './files/index.js';
4
- export * from './linter/index.js';
5
- export * from './markdown/index.js';
6
- export * from './response/index.js';