@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.
Files changed (55) hide show
  1. package/README.md +4 -4
  2. package/output/package.json +4 -5
  3. package/output/source/design-context.js +515 -0
  4. package/output/source/index.js +50 -41
  5. package/output/source/projitive.js +8 -9
  6. package/output/source/roadmap.js +2 -2
  7. package/output/source/tasks.js +5 -6
  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(projectPath=\"<project-dir>\")` 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
- rootPath: z.string().optional(),
134
135
  projectPath: z.string().optional(),
135
136
  taskId: z.string().optional(),
136
137
  },
137
- }, async ({ rootPath, projectPath, taskId }) => {
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
- "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.",
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: "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,30 @@ 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",
184
- argsSchema: {
185
- rootPath: z.string().optional(),
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 across projects and pick execution target.",
195
+ "Triage governance and pick one execution target quickly.",
190
196
  "",
191
- `1) Run projectNext(${rootPath ? `rootPath=\"${rootPath}\"` : ""}).`,
192
- "2) Select top ranked project.",
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 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.",
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: "Initialize Projitive governance directory structure manually (default .projitive)",
277
+ description: "Bootstrap governance files when a project has no .projitive yet",
278
278
  inputSchema: {
279
- projectPath: z.string().optional(),
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: "Scan filesystem and discover project governance roots marked by .projitive",
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: "Directly list recently actionable projects for immediate agent progression",
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 ({ rootPath, maxDepth, limit }) => {
364
- const root = resolveScanRoot(rootPath);
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 current project governance root from an in-project path by finding the nearest .projitive marker",
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: "Summarize project governance context for task execution planning",
450
+ description: "Get project-level summary before selecting or executing a task",
452
451
  inputSchema: {
453
452
  projectPath: z.string(),
454
453
  },
@@ -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,14 +543,13 @@ 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
- 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 ({ rootPath, maxDepth, topCandidates }) => {
553
- const root = resolveScanRoot(rootPath);
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 one task with detail, evidence locations, and execution guidance",
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.4",
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';
@@ -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';
@@ -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,2 +0,0 @@
1
- export * from './linter.js';
2
- export * from './codes.js';
@@ -1,6 +0,0 @@
1
- export function renderLintSuggestions(suggestions) {
2
- return suggestions.map((item) => {
3
- const suffix = item.fixHint ? ` ${item.fixHint}` : "";
4
- return `- [${item.code}] ${item.message}${suffix}`;
5
- });
6
- }
@@ -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';