@projitive/mcp 1.0.8 → 1.1.2

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 (53) hide show
  1. package/output/package.json +4 -1
  2. package/output/source/{helpers/catch → common}/catch.js +6 -6
  3. package/output/source/{helpers/catch → common}/catch.test.js +6 -6
  4. package/output/source/common/errors.js +120 -0
  5. package/output/source/{helpers/files → common}/files.js +1 -1
  6. package/output/source/{helpers/files → common}/files.test.js +1 -1
  7. package/output/source/common/index.js +9 -0
  8. package/output/source/{helpers/linter/codes.js → common/linter.js} +13 -0
  9. package/output/source/{helpers/markdown → common}/markdown.test.js +1 -1
  10. package/output/source/{helpers/response → common}/response.test.js +1 -1
  11. package/output/source/common/types.js +7 -0
  12. package/output/source/common/utils.js +39 -0
  13. package/output/source/index.js +8 -196
  14. package/output/source/index.test.js +110 -0
  15. package/output/source/prompts/index.js +9 -0
  16. package/output/source/prompts/quickStart.js +94 -0
  17. package/output/source/prompts/taskDiscovery.js +190 -0
  18. package/output/source/prompts/taskExecution.js +161 -0
  19. package/output/source/resources/designs.js +108 -0
  20. package/output/source/resources/designs.test.js +154 -0
  21. package/output/source/resources/governance.js +40 -0
  22. package/output/source/resources/index.js +6 -0
  23. package/output/source/resources/readme.test.js +167 -0
  24. package/output/source/{reports.js → resources/reports.js} +5 -3
  25. package/output/source/resources/reports.test.js +149 -0
  26. package/output/source/tools/index.js +8 -0
  27. package/output/source/{projitive.js → tools/project.js} +4 -6
  28. package/output/source/tools/project.test.js +322 -0
  29. package/output/source/{roadmap.js → tools/roadmap.js} +4 -7
  30. package/output/source/tools/roadmap.test.js +103 -0
  31. package/output/source/{tasks.js → tools/task.js} +470 -23
  32. package/output/source/tools/task.test.js +473 -0
  33. package/output/source/types.js +56 -0
  34. package/package.json +4 -1
  35. package/output/source/design-context.js +0 -515
  36. package/output/source/designs.js +0 -38
  37. package/output/source/helpers/artifacts/index.js +0 -1
  38. package/output/source/helpers/catch/index.js +0 -1
  39. package/output/source/helpers/files/index.js +0 -1
  40. package/output/source/helpers/index.js +0 -6
  41. package/output/source/helpers/linter/index.js +0 -2
  42. package/output/source/helpers/linter/linter.js +0 -6
  43. package/output/source/helpers/markdown/index.js +0 -1
  44. package/output/source/helpers/response/index.js +0 -1
  45. package/output/source/projitive.test.js +0 -111
  46. package/output/source/roadmap.test.js +0 -11
  47. package/output/source/tasks.test.js +0 -152
  48. /package/output/source/{helpers/artifacts → common}/artifacts.js +0 -0
  49. /package/output/source/{helpers/artifacts → common}/artifacts.test.js +0 -0
  50. /package/output/source/{helpers/linter → common}/linter.test.js +0 -0
  51. /package/output/source/{helpers/markdown → common}/markdown.js +0 -0
  52. /package/output/source/{helpers/response → common}/response.js +0 -0
  53. /package/output/source/{readme.js → resources/readme.js} +0 -0
@@ -0,0 +1,110 @@
1
+ import { describe, it, expect, beforeAll, afterAll, vi, beforeEach } from "vitest";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import { fileURLToPath } from "node:url";
6
+ const currentFilePath = fileURLToPath(import.meta.url);
7
+ const sourceDir = path.dirname(currentFilePath);
8
+ // Mock McpServer and StdioServerTransport
9
+ vi.mock("@modelcontextprotocol/sdk/server/mcp.js", () => ({
10
+ McpServer: vi.fn().mockImplementation(() => ({
11
+ registerResource: vi.fn(),
12
+ registerPrompt: vi.fn(),
13
+ connect: vi.fn().mockResolvedValue(undefined),
14
+ })),
15
+ }));
16
+ vi.mock("@modelcontextprotocol/sdk/server/stdio.js", () => ({
17
+ StdioServerTransport: vi.fn().mockImplementation(() => ({})),
18
+ }));
19
+ // Mock the registration functions
20
+ vi.mock("./tools/project.js", () => ({
21
+ registerProjectTools: vi.fn(),
22
+ }));
23
+ vi.mock("./tools/task.js", () => ({
24
+ registerTaskTools: vi.fn(),
25
+ }));
26
+ vi.mock("./tools/roadmap.js", () => ({
27
+ registerRoadmapTools: vi.fn(),
28
+ }));
29
+ vi.mock("./resources/governance.js", () => ({
30
+ registerGovernanceResources: vi.fn(),
31
+ }));
32
+ vi.mock("./resources/designs.js", () => ({
33
+ registerDesignFilesResources: vi.fn(),
34
+ }));
35
+ vi.mock("./prompts/governance.js", () => ({
36
+ registerGovernancePrompts: vi.fn(),
37
+ }));
38
+ describe("index module", () => {
39
+ let tempDir;
40
+ let testProjectDir;
41
+ beforeAll(async () => {
42
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "projitive-index-test-"));
43
+ testProjectDir = path.join(tempDir, "test-project");
44
+ await fs.mkdir(testProjectDir);
45
+ });
46
+ afterAll(async () => {
47
+ await fs.rm(tempDir, { recursive: true, force: true });
48
+ });
49
+ beforeEach(() => {
50
+ vi.clearAllMocks();
51
+ });
52
+ it("should export the main server entry point", async () => {
53
+ const indexPath = path.join(sourceDir, "index.ts");
54
+ const exists = await fs.access(indexPath).then(() => true).catch(() => false);
55
+ expect(exists).toBe(true);
56
+ });
57
+ it("should have the correct shebang and imports", async () => {
58
+ const indexPath = path.join(sourceDir, "index.ts");
59
+ const content = await fs.readFile(indexPath, "utf-8");
60
+ expect(content.startsWith("#!/usr/bin/env node")).toBe(true);
61
+ expect(content).toContain("McpServer");
62
+ expect(content).toContain("StdioServerTransport");
63
+ expect(content).toContain("registerTools");
64
+ expect(content).toContain("registerResources");
65
+ expect(content).toContain("registerPrompts");
66
+ });
67
+ it("should define PROJITIVE_SPEC_VERSION as 1.1.0", async () => {
68
+ const indexPath = path.join(sourceDir, "index.ts");
69
+ const content = await fs.readFile(indexPath, "utf-8");
70
+ expect(content).toContain("PROJITIVE_SPEC_VERSION");
71
+ expect(content).toContain('"1.1.0"');
72
+ });
73
+ it("should have main function with server startup", async () => {
74
+ const indexPath = path.join(sourceDir, "index.ts");
75
+ const content = await fs.readFile(indexPath, "utf-8");
76
+ expect(content).toContain("async function main");
77
+ expect(content).toContain("server.connect");
78
+ expect(content).toContain("StdioServerTransport");
79
+ });
80
+ it("should register all tool categories", async () => {
81
+ const indexPath = path.join(sourceDir, "index.ts");
82
+ const content = await fs.readFile(indexPath, "utf-8");
83
+ expect(content).toContain("registerTools(server)");
84
+ expect(content).toContain("registerResources(server, repoRoot)");
85
+ expect(content).toContain("registerPrompts(server)");
86
+ });
87
+ it("should have proper error handling in main", async () => {
88
+ const indexPath = path.join(sourceDir, "index.ts");
89
+ const content = await fs.readFile(indexPath, "utf-8");
90
+ expect(content).toContain("void main().catch");
91
+ expect(content).toContain("console.error(\"Server error:\", error)");
92
+ expect(content).toContain("process.exit(1)");
93
+ });
94
+ it("should log server startup information", async () => {
95
+ const indexPath = path.join(sourceDir, "index.ts");
96
+ const content = await fs.readFile(indexPath, "utf-8");
97
+ expect(content).toContain("console.error(`[projitive-mcp] starting server`)");
98
+ expect(content).toContain("console.error(`[projitive-mcp] version=");
99
+ expect(content).toContain("spec=");
100
+ expect(content).toContain("transport=stdio");
101
+ expect(content).toContain("pid=");
102
+ });
103
+ it("should define MCP server with correct metadata", async () => {
104
+ const indexPath = path.join(sourceDir, "index.ts");
105
+ const content = await fs.readFile(indexPath, "utf-8");
106
+ expect(content).toContain("name: \"projitive\"");
107
+ expect(content).toContain("version: MCP_RUNTIME_VERSION");
108
+ expect(content).toContain("description:");
109
+ });
110
+ });
@@ -0,0 +1,9 @@
1
+ // Governance workflow prompts
2
+ import { registerQuickStartPrompt } from "./quickStart.js";
3
+ import { registerTaskDiscoveryPrompt } from "./taskDiscovery.js";
4
+ import { registerTaskExecutionPrompt } from "./taskExecution.js";
5
+ export function registerPrompts(server) {
6
+ registerQuickStartPrompt(server);
7
+ registerTaskDiscoveryPrompt(server);
8
+ registerTaskExecutionPrompt(server);
9
+ }
@@ -0,0 +1,94 @@
1
+ import { z } from "zod";
2
+ function asUserPrompt(text) {
3
+ return {
4
+ messages: [
5
+ {
6
+ role: "user",
7
+ content: {
8
+ type: "text",
9
+ text,
10
+ },
11
+ },
12
+ ],
13
+ };
14
+ }
15
+ export function registerQuickStartPrompt(server) {
16
+ server.registerPrompt("quickStart", {
17
+ title: "Quick Start",
18
+ description: "Complete workflow to discover project, load context, and start task execution",
19
+ argsSchema: {
20
+ projectPath: z.string().optional(),
21
+ },
22
+ }, async ({ projectPath }) => {
23
+ const text = [
24
+ "# How to Govern a Project",
25
+ "",
26
+ "You are a Projitive governance assistant. Here is the complete workflow:",
27
+ "",
28
+ "## Step 1: Discover the Project",
29
+ "",
30
+ projectPath
31
+ ? [
32
+ `- Known project path: "${projectPath}"`,
33
+ "- Call `projectContext(projectPath=\"" + projectPath + "\")` directly to load project overview",
34
+ ].join("\n")
35
+ : [
36
+ "- Unknown project path:",
37
+ " 1. Call `projectScan()` to discover all governance roots",
38
+ " 2. Select a target project",
39
+ " 3. Call `projectLocate(inputPath=\"<selected-path>\")` to lock governance root",
40
+ " 4. Call `projectContext(projectPath=\"<project-path>\")` to load project overview",
41
+ ].join("\n"),
42
+ "",
43
+ "## Step 2: Understand the Project",
44
+ "",
45
+ "After loading project context, read these resources:",
46
+ "- projitive://governance/workspace - Project overview",
47
+ "- projitive://governance/tasks - Current task pool",
48
+ "- projitive://governance/roadmap - Project roadmap",
49
+ "- projitive://designs/* - Design documents",
50
+ "",
51
+ "## Step 3: Discover Tasks",
52
+ "",
53
+ "Two ways to discover tasks:",
54
+ "",
55
+ "### Option A: Auto-select (Recommended)",
56
+ "Call `taskNext()` to get highest-priority actionable task.",
57
+ "",
58
+ "### Option B: Manual select",
59
+ "1. Call `taskList()` to list all tasks",
60
+ "2. Select a task based on status and priority",
61
+ "3. Call `taskContext(projectPath=\"...\", taskId=\"...\")` for details",
62
+ "",
63
+ "## Step 4: Execute the Task",
64
+ "",
65
+ "After getting task context:",
66
+ "1. Read evidence links in Suggested Read Order",
67
+ "2. Understand task requirements and acceptance criteria",
68
+ "3. Edit governance markdown only (tasks/designs/reports/roadmap)",
69
+ "4. Update task status:",
70
+ " - TODO → IN_PROGRESS (when starting execution)",
71
+ " - IN_PROGRESS → DONE (when completed)",
72
+ " - IN_PROGRESS → BLOCKED (when blocked)",
73
+ "5. Re-run taskContext() to verify changes",
74
+ "",
75
+ "## Special Cases",
76
+ "",
77
+ "### Case 1: No .projitive directory",
78
+ "Call `projectInit(projectPath=\"<project-dir>\")` to initialize governance structure.",
79
+ "",
80
+ "### Case 2: No actionable tasks",
81
+ "1. Check if tasks.md is missing",
82
+ "2. Read design documents in projitive://designs/",
83
+ "3. Create 1-3 new TODO tasks",
84
+ "",
85
+ "## Hard Rules",
86
+ "",
87
+ "- **NEVER modify TASK/ROADMAP IDs** - Keep them immutable once assigned",
88
+ "- **Every status transition must have report evidence** - Create execution reports in reports/ directory",
89
+ "- **Only edit files under .projitive/** - Unless task scope explicitly requires otherwise",
90
+ "- **Always verify after updates** - Re-run taskContext() to confirm reference consistency",
91
+ ].join("\n");
92
+ return asUserPrompt(text);
93
+ });
94
+ }
@@ -0,0 +1,190 @@
1
+ import { z } from "zod";
2
+ function asUserPrompt(text) {
3
+ return {
4
+ messages: [
5
+ {
6
+ role: "user",
7
+ content: {
8
+ type: "text",
9
+ text,
10
+ },
11
+ },
12
+ ],
13
+ };
14
+ }
15
+ export function registerTaskDiscoveryPrompt(server) {
16
+ server.registerPrompt("taskDiscovery", {
17
+ title: "Task Discovery",
18
+ description: "Minimal steps to find, context, and execute the first actionable task",
19
+ argsSchema: {
20
+ projectPath: z.string().optional(),
21
+ },
22
+ }, async ({ projectPath }) => {
23
+ const text = [
24
+ "# How to Discover Tasks",
25
+ "",
26
+ "You are a Projitive task discovery assistant. Here is the complete workflow to discover and execute your first task:",
27
+ "",
28
+ "## Step 1: Locate the Project",
29
+ "",
30
+ projectPath
31
+ ? [
32
+ `- Known project path: "${projectPath}"`,
33
+ "- Call `projectContext(projectPath=\"" + projectPath + "\")` directly to load project context",
34
+ ].join("\n")
35
+ : [
36
+ "- Unknown project path:",
37
+ " 1. Call `projectScan()` to discover all governance roots",
38
+ " 2. Select a project",
39
+ " 3. Call `projectLocate(inputPath=\"<selected-path>\")` to lock governance root",
40
+ " 4. Call `projectContext(projectPath=\"<project-path>\")` to load project context",
41
+ ].join("\n"),
42
+ "",
43
+ "## Step 2: Read Project Context",
44
+ "",
45
+ "After loading project context, focus on:",
46
+ "",
47
+ "### Task Summary",
48
+ "- total: Total tasks",
49
+ "- TODO: Pending tasks (highest priority)",
50
+ "- IN_PROGRESS: In-progress tasks (priority to continue)",
51
+ "- BLOCKED: Blocked tasks (need to resolve first)",
52
+ "- DONE: Completed tasks",
53
+ "",
54
+ "### Artifacts",
55
+ "Check if these files exist:",
56
+ "- \u2705 tasks.md - Task list (required)",
57
+ "- \u2705 roadmap.md - Roadmap",
58
+ "- \u2705 README.md - Project description",
59
+ "- \u2705 designs/ - Design documents directory",
60
+ "- \u2705 reports/ - Reports directory",
61
+ "",
62
+ "## Step 3: Select a Task",
63
+ "",
64
+ "### Priority Order",
65
+ "Select tasks in this priority order:",
66
+ "",
67
+ "1. **IN_PROGRESS** - Priority to continue in-progress tasks first",
68
+ " - These tasks have already started, should continue to finish",
69
+ " - Check updatedAt time, most recent first",
70
+ "",
71
+ "2. **TODO** - Next select pending tasks",
72
+ " - Tasks with roadmapRefs first",
73
+ " - Tasks with explicit owner first",
74
+ "",
75
+ "3. **BLOCKED** - Last consider blocked tasks",
76
+ " - Need to analyze blocker reason first",
77
+ " - May need to create sub-task to resolve blocker",
78
+ "",
79
+ "### Discovery Methods",
80
+ "",
81
+ "#### Method A: Auto-select with taskNext() (Recommended)",
82
+ "",
83
+ "Call `taskNext()`, the tool will automatically:",
84
+ "- Sort all tasks by priority",
85
+ "- Select highest-priority actionable task",
86
+ "- Return task ID and summary",
87
+ "",
88
+ "Then call `taskContext(projectPath=\"...\", taskId=\"<task-id>\")` for task details.",
89
+ "",
90
+ "#### Method B: Manual select with taskList()",
91
+ "",
92
+ "1. Call `taskList()` to get all tasks",
93
+ "2. Select based on these factors:",
94
+ " - status (IN_PROGRESS > TODO > BLOCKED)",
95
+ " - updatedAt time (most recent first)",
96
+ " - owner assigned (with owner first)",
97
+ " - roadmapRefs (with refs first)",
98
+ "3. Call `taskContext(projectPath=\"...\", taskId=\"<selected-id>\")` for details",
99
+ "",
100
+ "## Step 4: Understand Task Context",
101
+ "",
102
+ "After getting taskContext, read carefully:",
103
+ "",
104
+ "### Summary",
105
+ "- taskId: Task ID (NEVER modify this)",
106
+ "- status: Current status",
107
+ "- owner: Assignee",
108
+ "- roadmapRefs: Related roadmap",
109
+ "- updatedAt: Last updated time",
110
+ "",
111
+ "### Evidence",
112
+ "- Candidate Files - Related file list",
113
+ "- Reference Locations - Reference locations",
114
+ "",
115
+ "### Suggested Read Order",
116
+ "Read files in order, understand:",
117
+ "- Task background and motivation",
118
+ "- Task acceptance criteria",
119
+ "- Related design decisions",
120
+ "- Previous execution history",
121
+ "",
122
+ "### Lint Suggestions",
123
+ "Check if any warnings need to be resolved first.",
124
+ "",
125
+ "## Step 5: Execute the Task",
126
+ "",
127
+ "After understanding the task:",
128
+ "",
129
+ "1. **If status is TODO",
130
+ " - First call `taskUpdate()` to change status to IN_PROGRESS",
131
+ " - Set owner (if empty)",
132
+ " - Fill subState (optional, Spec v1.1.0)",
133
+ "",
134
+ "2. **If status is IN_PROGRESS",
135
+ " - Continue previous work",
136
+ " - Reference history reports in reports/",
137
+ "",
138
+ "3. **Execute task content**",
139
+ " - Edit governance files (tasks.md / designs/*.md / reports/*.md / roadmap.md)",
140
+ " - Create execution report (reports/ directory)",
141
+ " - Update task status to DONE",
142
+ "",
143
+ "4. **Verify changes**",
144
+ " - Re-run `taskContext()`",
145
+ " - Confirm all references still valid",
146
+ " - Confirm no new lint suggestions",
147
+ "",
148
+ "## Special Cases",
149
+ "",
150
+ "### Case 1: No tasks at all",
151
+ "",
152
+ "If taskNext() returns empty:",
153
+ "1. Check if tasks.md exists",
154
+ "2. Read design documents in designs/ directory",
155
+ "3. Read roadmap.md to understand project goals",
156
+ "4. Create 1-3 new TODO tasks",
157
+ "5. Each task must have:",
158
+ " - Unique TASK-xxxx ID",
159
+ " - Clear title",
160
+ " - Detailed summary",
161
+ " - Linked roadmapRefs (if applicable)",
162
+ "",
163
+ "### Case 2: All tasks are BLOCKED",
164
+ "",
165
+ "1. Select one blocked task and call `taskContext()`",
166
+ "2. Understand blocker reason (check blocker field, Spec v1.1.0)",
167
+ "3. Create a new TODO task to resolve blocker",
168
+ "4. Execute this new task first",
169
+ "",
170
+ "### Case 3: Missing required governance files",
171
+ "",
172
+ "If tasks.md does not exist:",
173
+ "1. Call `projectInit(projectPath=\"...\")` to initialize governance structure",
174
+ "2. Then restart discovery flow",
175
+ "",
176
+ "## Quick Reference",
177
+ "",
178
+ "| Tool | Purpose |",
179
+ "|------|---------|",
180
+ "| `projectScan()` | Discover all governance roots |",
181
+ "| `projectLocate()` | Lock governance root |",
182
+ "| `projectContext()` | Load project overview |",
183
+ "| `taskNext()` | Auto-select best task |",
184
+ "| `taskList()` | List all tasks |",
185
+ "| `taskContext()` | Get task details |",
186
+ "| `taskUpdate()` | Update task status |",
187
+ ].join("\n");
188
+ return asUserPrompt(text);
189
+ });
190
+ }
@@ -0,0 +1,161 @@
1
+ import { z } from "zod";
2
+ function asUserPrompt(text) {
3
+ return {
4
+ messages: [
5
+ {
6
+ role: "user",
7
+ content: {
8
+ type: "text",
9
+ text,
10
+ },
11
+ },
12
+ ],
13
+ };
14
+ }
15
+ export function registerTaskExecutionPrompt(server) {
16
+ server.registerPrompt("taskExecution", {
17
+ title: "Task Execution",
18
+ description: "Primary execution prompt: select one task, execute, and verify evidence consistency",
19
+ argsSchema: {
20
+ projectPath: z.string().optional(),
21
+ taskId: z.string().optional(),
22
+ },
23
+ }, async ({ projectPath, taskId }) => {
24
+ const taskEntry = taskId && projectPath
25
+ ? `1) Run taskContext(projectPath="${projectPath}", taskId="${taskId}").`
26
+ : "1) Run taskNext().";
27
+ const text = [
28
+ "# Execute Task Workflow",
29
+ "",
30
+ "You are a Projitive task execution assistant. Here is the complete workflow:",
31
+ "",
32
+ "## Step 1: Get the Task",
33
+ "",
34
+ taskEntry,
35
+ "",
36
+ "## Step 2: Understand the Task",
37
+ "",
38
+ "After getting task context, read carefully:",
39
+ "",
40
+ "### Suggested Read Order",
41
+ "Read each file in order:",
42
+ "- Note content and location of each file",
43
+ "- Understand task background and motivation",
44
+ "- Identify task acceptance criteria",
45
+ "- Look for related design decisions",
46
+ "",
47
+ "### Evidence",
48
+ "- Candidate Files - Related file list",
49
+ "- Reference Locations - Where TASK/ROADMAP IDs appear",
50
+ "",
51
+ "### Lint Suggestions",
52
+ "Resolve any warnings before starting task execution.",
53
+ "",
54
+ "## Step 3: Execute the Task",
55
+ "",
56
+ "### Status Management",
57
+ "",
58
+ "| Current Status | Next Step | Description |",
59
+ "|----------------|-----------|-------------|",
60
+ "| TODO | \u2192 IN_PROGRESS | When starting execution |",
61
+ "| IN_PROGRESS | \u2192 DONE | When task is complete |",
62
+ "| IN_PROGRESS | \u2192 BLOCKED | When blocked |",
63
+ "| BLOCKED | \u2192 TODO | When blocker is resolved |",
64
+ "",
65
+ "### Execution Steps",
66
+ "",
67
+ "1. **Prepare (if status is TODO)",
68
+ " - Call `taskUpdate()` to change status to IN_PROGRESS",
69
+ " - Set owner (if empty)",
70
+ " - Fill subState (optional, Spec v1.1.0)",
71
+ "",
72
+ "2. **Execute task content**",
73
+ " - Only edit files under .projitive/",
74
+ " - tasks.md - Update task status and metadata",
75
+ " - designs/*.md - Add or update design documents",
76
+ " - reports/*.md - Create execution reports",
77
+ " - roadmap.md - Update roadmap (if needed)",
78
+ "",
79
+ "3. **Create execution report**",
80
+ " - Create new report file in reports/ directory",
81
+ " - Record what changes were made",
82
+ " - Record why changes were made",
83
+ " - Record verification results",
84
+ "",
85
+ "4. **Complete task (if status is IN_PROGRESS)",
86
+ " - Confirm all acceptance criteria are met",
87
+ " - Call `taskUpdate()` to change status to DONE",
88
+ " - Update updatedAt timestamp",
89
+ "",
90
+ "## Step 4: Verify Changes",
91
+ "",
92
+ "After execution:",
93
+ "",
94
+ "1. **Re-run taskContext()",
95
+ " - Confirm task status updated correctly",
96
+ " - Confirm updatedAt timestamp updated",
97
+ "",
98
+ "2. **Check reference consistency",
99
+ " - Confirm all TASK/ROADMAP IDs still valid",
100
+ " - Confirm no broken links in Reference Locations",
101
+ "",
102
+ "3. **Check Lint Suggestions",
103
+ " - Confirm no new warnings",
104
+ " - If there are warnings, resolve them first",
105
+ "",
106
+ "4. **Continue to next task (optional)",
107
+ " - Call `taskNext()` for next task",
108
+ " - Repeat above workflow",
109
+ "",
110
+ "## Special Cases",
111
+ "",
112
+ "### Case 1: Encountered a blocker",
113
+ "",
114
+ "If unable to continue task execution:",
115
+ "1. Call `taskUpdate()` to change status to BLOCKED",
116
+ "2. Fill blocker field (Spec v1.1.0):",
117
+ " - type: Blocker type (dependency/missing-info/technical-debt/other)",
118
+ " - description: Blocker description",
119
+ " - relatedLinks: Related links (optional)",
120
+ "3. Create a new TODO task to resolve blocker",
121
+ "",
122
+ "### Case 2: No actionable tasks",
123
+ "",
124
+ "If taskNext() returns empty:",
125
+ "1. Call `projectContext()` to recheck project state",
126
+ "2. Read design documents in designs/ directory",
127
+ "3. Create 1-3 new TODO tasks",
128
+ "",
129
+ "### Case 3: Need to initialize governance",
130
+ "",
131
+ "If .projitive directory does not exist:",
132
+ "1. Call `projectInit(projectPath=\"<project-dir>\")`",
133
+ "2. Then restart",
134
+ "",
135
+ "## Hard Rules (NEVER violate)",
136
+ "",
137
+ "1. **NEVER modify TASK/ROADMAP IDs**",
138
+ " - Keep them immutable once assigned",
139
+ " - If you need to reference, keep them as-is",
140
+ "",
141
+ "2. **Every status transition must have report evidence**",
142
+ " - TODO \u2192 IN_PROGRESS: Report not required",
143
+ " - IN_PROGRESS \u2192 DONE: Report REQUIRED",
144
+ " - IN_PROGRESS \u2192 BLOCKED: Report recommended to explain blocker",
145
+ "",
146
+ "3. **Only edit files under .projitive/**",
147
+ " - Unless task scope explicitly requires modifying other files",
148
+ " - Any non-governance file edits must be explained in task summary",
149
+ "",
150
+ "4. **Always verify after updates**",
151
+ " - After every taskUpdate() call",
152
+ " - Re-run taskContext()",
153
+ " - Confirm reference consistency",
154
+ "",
155
+ "5. **Keep updatedAt in sync**",
156
+ " - Every time you update a task",
157
+ " - Must update updatedAt to current time (ISO 8601 format)",
158
+ ].join("\n");
159
+ return asUserPrompt(text);
160
+ });
161
+ }
@@ -0,0 +1,108 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { isValidRoadmapId } from "../tools/roadmap.js";
4
+ import { isValidTaskId } from "../tools/task.js";
5
+ export function parseDesignMetadata(markdown) {
6
+ const lines = markdown.split(/\r?\n/);
7
+ const metadata = {};
8
+ for (const line of lines) {
9
+ // Remove markdown bold markers (**)
10
+ const cleanLine = line.replace(/\*\*/g, "");
11
+ const [rawKey, ...rawValue] = cleanLine.split(":");
12
+ if (!rawKey || rawValue.length === 0) {
13
+ continue;
14
+ }
15
+ const key = rawKey.trim().toLowerCase();
16
+ const value = rawValue.join(":").trim();
17
+ if (key === "task")
18
+ metadata.task = value;
19
+ if (key === "roadmap")
20
+ metadata.roadmap = value;
21
+ if (key === "owner")
22
+ metadata.owner = value;
23
+ if (key === "status")
24
+ metadata.status = value;
25
+ if (key === "last updated")
26
+ metadata.lastUpdated = value;
27
+ }
28
+ return metadata;
29
+ }
30
+ export function validateDesignMetadata(metadata) {
31
+ const errors = [];
32
+ if (!metadata.task) {
33
+ errors.push("Missing Task metadata");
34
+ }
35
+ else if (!isValidTaskId(metadata.task)) {
36
+ errors.push(`Invalid Task metadata format: ${metadata.task}`);
37
+ }
38
+ if (metadata.roadmap && !isValidRoadmapId(metadata.roadmap)) {
39
+ errors.push(`Invalid Roadmap metadata format: ${metadata.roadmap}`);
40
+ }
41
+ return { ok: errors.length === 0, errors };
42
+ }
43
+ async function findAllMarkdownFiles(dir) {
44
+ const result = [];
45
+ async function walk(currentDir, relativeBase) {
46
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
47
+ for (const entry of entries) {
48
+ const fullPath = path.join(currentDir, entry.name);
49
+ if (entry.isDirectory()) {
50
+ await walk(fullPath, path.join(relativeBase, entry.name));
51
+ }
52
+ else if (entry.isFile() && entry.name.endsWith(".md")) {
53
+ result.push({
54
+ filePath: fullPath,
55
+ relativePath: path.join(relativeBase, entry.name),
56
+ });
57
+ }
58
+ }
59
+ }
60
+ await walk(dir, "");
61
+ return result;
62
+ }
63
+ export async function registerDesignFilesResources(server, repoRoot) {
64
+ const designsDir = path.join(repoRoot, ".projitive", "designs");
65
+ try {
66
+ // Check if design documents directory exists
67
+ await fs.access(designsDir);
68
+ // Recursively read all .md files (including subdirectories)
69
+ const markdownFiles = await findAllMarkdownFiles(designsDir);
70
+ // Register each design file as a resource
71
+ for (const { filePath, relativePath } of markdownFiles) {
72
+ // Generate designId from relative path, replace path separators with '-'
73
+ const designId = relativePath
74
+ .slice(0, -3) // Remove .md suffix
75
+ .replace(/[\\/]/g, "-"); // Replace path separators with '-'
76
+ const content = await fs.readFile(filePath, "utf-8");
77
+ // Register resource
78
+ server.registerResource(`design-${designId}`, `projitive://designs/${designId}`, {
79
+ title: designId,
80
+ description: `Design document: ${relativePath}`,
81
+ mimeType: "text/markdown",
82
+ }, async () => ({
83
+ contents: [
84
+ {
85
+ uri: `projitive://designs/${designId}`,
86
+ text: content,
87
+ },
88
+ ],
89
+ }));
90
+ }
91
+ }
92
+ catch (error) {
93
+ // If design documents directory doesn't exist, register a default resource
94
+ console.warn(`Designs directory not found at ${designsDir}, registering default design resource`);
95
+ server.registerResource("designs", "projitive://designs", {
96
+ title: "Designs",
97
+ description: "Design documents directory",
98
+ mimeType: "text/markdown",
99
+ }, async () => ({
100
+ contents: [
101
+ {
102
+ uri: "projitive://designs",
103
+ text: `# Designs Directory\n\nDesign documents not found. Please create design files in .projitive/designs/ directory.`,
104
+ },
105
+ ],
106
+ }));
107
+ }
108
+ }