@projitive/mcp 1.0.6 → 1.0.7
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 +1 -1
- package/output/package.json +1 -1
- package/output/source/projitive.js +52 -7
- package/output/source/projitive.test.js +36 -0
- package/output/source/roadmap.js +3 -3
- package/output/source/tasks.js +6 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Language: English | [简体中文](README_CN.md)
|
|
4
4
|
|
|
5
|
-
**Current Spec Version: projitive-spec v1.0.0 | MCP Version: 1.0.
|
|
5
|
+
**Current Spec Version: projitive-spec v1.0.0 | MCP Version: 1.0.7**
|
|
6
6
|
|
|
7
7
|
Projitive MCP server (semantic interface edition) helps agents discover projects, select tasks, locate evidence, and execute under governance workflows.
|
|
8
8
|
|
package/output/package.json
CHANGED
|
@@ -133,6 +133,41 @@ function parentDir(dirPath) {
|
|
|
133
133
|
const parent = path.dirname(dirPath);
|
|
134
134
|
return parent === dirPath ? null : parent;
|
|
135
135
|
}
|
|
136
|
+
export function toProjectPath(governanceDir) {
|
|
137
|
+
return path.dirname(governanceDir);
|
|
138
|
+
}
|
|
139
|
+
async function listChildGovernanceDirs(parentPath) {
|
|
140
|
+
const entriesResult = await catchIt(fs.readdir(parentPath, { withFileTypes: true }));
|
|
141
|
+
if (entriesResult.isError()) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
const folders = entriesResult.value
|
|
145
|
+
.filter((entry) => entry.isDirectory())
|
|
146
|
+
.map((entry) => path.join(parentPath, entry.name));
|
|
147
|
+
const markerChecks = await Promise.all(folders.map(async (folderPath) => ({
|
|
148
|
+
folderPath,
|
|
149
|
+
hasMarker: await hasProjectMarker(folderPath),
|
|
150
|
+
})));
|
|
151
|
+
const candidates = markerChecks
|
|
152
|
+
.filter((item) => item.hasMarker)
|
|
153
|
+
.map((item) => item.folderPath)
|
|
154
|
+
.sort((a, b) => a.localeCompare(b));
|
|
155
|
+
return candidates;
|
|
156
|
+
}
|
|
157
|
+
async function resolveChildGovernanceDir(parentPath) {
|
|
158
|
+
const candidates = await listChildGovernanceDirs(parentPath);
|
|
159
|
+
if (candidates.length === 0) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
const defaultCandidate = path.join(parentPath, DEFAULT_GOVERNANCE_DIR);
|
|
163
|
+
if (candidates.includes(defaultCandidate)) {
|
|
164
|
+
return defaultCandidate;
|
|
165
|
+
}
|
|
166
|
+
if (candidates.length === 1) {
|
|
167
|
+
return candidates[0];
|
|
168
|
+
}
|
|
169
|
+
throw new Error(`Multiple governance roots found under path: ${parentPath}. Use projectPath/governanceDir explicitly.`);
|
|
170
|
+
}
|
|
136
171
|
export async function resolveGovernanceDir(inputPath) {
|
|
137
172
|
const absolutePath = path.resolve(inputPath);
|
|
138
173
|
const statResult = await catchIt(fs.stat(absolutePath));
|
|
@@ -145,6 +180,10 @@ export async function resolveGovernanceDir(inputPath) {
|
|
|
145
180
|
if (await hasProjectMarker(cursor)) {
|
|
146
181
|
return cursor;
|
|
147
182
|
}
|
|
183
|
+
const childGovernanceDir = await resolveChildGovernanceDir(cursor);
|
|
184
|
+
if (childGovernanceDir) {
|
|
185
|
+
return childGovernanceDir;
|
|
186
|
+
}
|
|
148
187
|
cursor = parentDir(cursor);
|
|
149
188
|
}
|
|
150
189
|
throw new Error(`No ${PROJECT_MARKER} marker found from path: ${absolutePath}`);
|
|
@@ -158,6 +197,8 @@ export async function discoverProjects(rootPath, maxDepth) {
|
|
|
158
197
|
if (await hasProjectMarker(currentPath)) {
|
|
159
198
|
results.push(currentPath);
|
|
160
199
|
}
|
|
200
|
+
const childGovernanceDirs = await listChildGovernanceDirs(currentPath);
|
|
201
|
+
results.push(...childGovernanceDirs);
|
|
161
202
|
const entriesResult = await catchIt(fs.readdir(currentPath, { withFileTypes: true }));
|
|
162
203
|
if (entriesResult.isError()) {
|
|
163
204
|
return;
|
|
@@ -274,7 +315,7 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
274
315
|
export function registerProjectTools(server) {
|
|
275
316
|
server.registerTool("projectInit", {
|
|
276
317
|
title: "Project Init",
|
|
277
|
-
description: "Bootstrap governance files when a project has no .projitive yet",
|
|
318
|
+
description: "Bootstrap governance files when a project has no .projitive yet (requires projectPath)",
|
|
278
319
|
inputSchema: {
|
|
279
320
|
projectPath: z.string(),
|
|
280
321
|
governanceDir: z.string().optional(),
|
|
@@ -313,7 +354,7 @@ export function registerProjectTools(server) {
|
|
|
313
354
|
"- After init, fill owner/roadmapRefs/links in tasks.md before marking DONE.",
|
|
314
355
|
"- Keep task source-of-truth inside marker block only.",
|
|
315
356
|
]),
|
|
316
|
-
nextCallSection(`projectContext(projectPath=\"${initialized.
|
|
357
|
+
nextCallSection(`projectContext(projectPath=\"${initialized.projectPath}\")`),
|
|
317
358
|
],
|
|
318
359
|
});
|
|
319
360
|
return asText(markdown);
|
|
@@ -408,13 +449,13 @@ export function registerProjectTools(server) {
|
|
|
408
449
|
...ranked.map((item, index) => `${index + 1}. ${item.governanceDir} | actionable=${item.actionable} | in_progress=${item.inProgress} | todo=${item.todo} | blocked=${item.blocked} | done=${item.done} | latest=${item.latestUpdatedAt} | tasksPath=${item.tasksPath}${item.tasksExists ? "" : " (missing)"}`),
|
|
409
450
|
]),
|
|
410
451
|
guidanceSection([
|
|
411
|
-
"- Pick top 1 project and call `projectContext` with its
|
|
452
|
+
"- Pick top 1 project and call `projectContext` with its projectPath.",
|
|
412
453
|
"- Then call `taskList` and `taskContext` to continue execution.",
|
|
413
454
|
"- If `tasksPath` is missing, create tasks.md using project convention before task-level operations.",
|
|
414
455
|
]),
|
|
415
456
|
lintSection(ranked[0]?.lintSuggestions ?? []),
|
|
416
457
|
nextCallSection(ranked[0]
|
|
417
|
-
? `projectContext(projectPath=\"${ranked[0].governanceDir}\")`
|
|
458
|
+
? `projectContext(projectPath=\"${toProjectPath(ranked[0].governanceDir)}\")`
|
|
418
459
|
: undefined),
|
|
419
460
|
],
|
|
420
461
|
});
|
|
@@ -429,18 +470,20 @@ export function registerProjectTools(server) {
|
|
|
429
470
|
}, async ({ inputPath }) => {
|
|
430
471
|
const resolvedFrom = normalizePath(inputPath);
|
|
431
472
|
const governanceDir = await resolveGovernanceDir(resolvedFrom);
|
|
473
|
+
const projectPath = toProjectPath(governanceDir);
|
|
432
474
|
const markerPath = path.join(governanceDir, ".projitive");
|
|
433
475
|
const markdown = renderToolResponseMarkdown({
|
|
434
476
|
toolName: "projectLocate",
|
|
435
477
|
sections: [
|
|
436
478
|
summarySection([
|
|
437
479
|
`- resolvedFrom: ${resolvedFrom}`,
|
|
480
|
+
`- projectPath: ${projectPath}`,
|
|
438
481
|
`- governanceDir: ${governanceDir}`,
|
|
439
482
|
`- markerPath: ${markerPath}`,
|
|
440
483
|
]),
|
|
441
|
-
guidanceSection(["- Call `projectContext` with this
|
|
484
|
+
guidanceSection(["- Call `projectContext` with this projectPath to get task and roadmap summaries."]),
|
|
442
485
|
lintSection(["- Run `projectContext` to get governance/module lint suggestions for this project."]),
|
|
443
|
-
nextCallSection(`projectContext(projectPath=\"${
|
|
486
|
+
nextCallSection(`projectContext(projectPath=\"${projectPath}\")`),
|
|
444
487
|
],
|
|
445
488
|
});
|
|
446
489
|
return asText(markdown);
|
|
@@ -453,6 +496,7 @@ export function registerProjectTools(server) {
|
|
|
453
496
|
},
|
|
454
497
|
}, async ({ projectPath }) => {
|
|
455
498
|
const governanceDir = await resolveGovernanceDir(projectPath);
|
|
499
|
+
const normalizedProjectPath = toProjectPath(governanceDir);
|
|
456
500
|
const artifacts = await discoverGovernanceArtifacts(governanceDir);
|
|
457
501
|
const { tasksPath, tasks, markdown: tasksMarkdown } = await loadTasksDocument(governanceDir);
|
|
458
502
|
const roadmapIds = await readRoadmapIds(governanceDir);
|
|
@@ -468,6 +512,7 @@ export function registerProjectTools(server) {
|
|
|
468
512
|
toolName: "projectContext",
|
|
469
513
|
sections: [
|
|
470
514
|
summarySection([
|
|
515
|
+
`- projectPath: ${normalizedProjectPath}`,
|
|
471
516
|
`- governanceDir: ${governanceDir}`,
|
|
472
517
|
`- tasksFile: ${tasksPath}`,
|
|
473
518
|
`- roadmapIds: ${roadmapIds.length}`,
|
|
@@ -488,7 +533,7 @@ export function registerProjectTools(server) {
|
|
|
488
533
|
"- Then call `taskContext` with a task ID to retrieve evidence locations and reading order.",
|
|
489
534
|
]),
|
|
490
535
|
lintSection(lintSuggestions),
|
|
491
|
-
nextCallSection(`taskList(projectPath=\"${
|
|
536
|
+
nextCallSection(`taskList(projectPath=\"${normalizedProjectPath}\")`),
|
|
492
537
|
],
|
|
493
538
|
});
|
|
494
539
|
return asText(markdown);
|
|
@@ -31,6 +31,24 @@ describe("projitive module", () => {
|
|
|
31
31
|
const resolved = await resolveGovernanceDir(deepDir);
|
|
32
32
|
expect(resolved).toBe(governanceDir);
|
|
33
33
|
});
|
|
34
|
+
it("resolves nested default governance dir when input path is project root", async () => {
|
|
35
|
+
const root = await createTempDir();
|
|
36
|
+
const projectRoot = path.join(root, "repo");
|
|
37
|
+
const governanceDir = path.join(projectRoot, ".projitive");
|
|
38
|
+
await fs.mkdir(governanceDir, { recursive: true });
|
|
39
|
+
await fs.writeFile(path.join(governanceDir, ".projitive"), "", "utf-8");
|
|
40
|
+
const resolved = await resolveGovernanceDir(projectRoot);
|
|
41
|
+
expect(resolved).toBe(governanceDir);
|
|
42
|
+
});
|
|
43
|
+
it("resolves nested custom governance dir when input path is project root", async () => {
|
|
44
|
+
const root = await createTempDir();
|
|
45
|
+
const projectRoot = path.join(root, "repo");
|
|
46
|
+
const governanceDir = path.join(projectRoot, "governance");
|
|
47
|
+
await fs.mkdir(governanceDir, { recursive: true });
|
|
48
|
+
await fs.writeFile(path.join(governanceDir, ".projitive"), "", "utf-8");
|
|
49
|
+
const resolved = await resolveGovernanceDir(projectRoot);
|
|
50
|
+
expect(resolved).toBe(governanceDir);
|
|
51
|
+
});
|
|
34
52
|
it("discovers projects by marker file", async () => {
|
|
35
53
|
const root = await createTempDir();
|
|
36
54
|
const p1 = path.join(root, "a");
|
|
@@ -43,6 +61,24 @@ describe("projitive module", () => {
|
|
|
43
61
|
expect(projects).toContain(p1);
|
|
44
62
|
expect(projects).toContain(p2);
|
|
45
63
|
});
|
|
64
|
+
it("discovers nested default governance directory under project root", async () => {
|
|
65
|
+
const root = await createTempDir();
|
|
66
|
+
const projectRoot = path.join(root, "app");
|
|
67
|
+
const governanceDir = path.join(projectRoot, ".projitive");
|
|
68
|
+
await fs.mkdir(governanceDir, { recursive: true });
|
|
69
|
+
await fs.writeFile(path.join(governanceDir, ".projitive"), "", "utf-8");
|
|
70
|
+
const projects = await discoverProjects(root, 3);
|
|
71
|
+
expect(projects).toContain(governanceDir);
|
|
72
|
+
});
|
|
73
|
+
it("discovers nested custom governance directory under project root", async () => {
|
|
74
|
+
const root = await createTempDir();
|
|
75
|
+
const projectRoot = path.join(root, "app");
|
|
76
|
+
const governanceDir = path.join(projectRoot, "governance");
|
|
77
|
+
await fs.mkdir(governanceDir, { recursive: true });
|
|
78
|
+
await fs.writeFile(path.join(governanceDir, ".projitive"), "", "utf-8");
|
|
79
|
+
const projects = await discoverProjects(root, 3);
|
|
80
|
+
expect(projects).toContain(governanceDir);
|
|
81
|
+
});
|
|
46
82
|
it("initializes governance structure under default .projitive directory", async () => {
|
|
47
83
|
const root = await createTempDir();
|
|
48
84
|
const initialized = await initializeProjectStructure(root);
|
package/output/source/roadmap.js
CHANGED
|
@@ -6,7 +6,7 @@ import { discoverGovernanceArtifacts } from "./helpers/files/index.js";
|
|
|
6
6
|
import { ROADMAP_LINT_CODES, renderLintSuggestions } from "./helpers/linter/index.js";
|
|
7
7
|
import { findTextReferences } from "./helpers/markdown/index.js";
|
|
8
8
|
import { asText, evidenceSection, guidanceSection, lintSection, nextCallSection, renderErrorMarkdown, renderToolResponseMarkdown, summarySection, } from "./helpers/response/index.js";
|
|
9
|
-
import { resolveGovernanceDir } from "./projitive.js";
|
|
9
|
+
import { resolveGovernanceDir, toProjectPath } from "./projitive.js";
|
|
10
10
|
import { loadTasks } from "./tasks.js";
|
|
11
11
|
export const ROADMAP_ID_REGEX = /^ROADMAP-\d{4}$/;
|
|
12
12
|
function collectRoadmapLintSuggestionItems(roadmapIds, tasks) {
|
|
@@ -99,7 +99,7 @@ export function registerRoadmapTools(server) {
|
|
|
99
99
|
guidanceSection(["- Pick one roadmap ID and call `roadmapContext`."]),
|
|
100
100
|
lintSection(lintSuggestions),
|
|
101
101
|
nextCallSection(roadmapIds[0]
|
|
102
|
-
? `roadmapContext(projectPath=\"${governanceDir}\", roadmapId=\"${roadmapIds[0]}\")`
|
|
102
|
+
? `roadmapContext(projectPath=\"${toProjectPath(governanceDir)}\", roadmapId=\"${roadmapIds[0]}\")`
|
|
103
103
|
: undefined),
|
|
104
104
|
],
|
|
105
105
|
});
|
|
@@ -157,7 +157,7 @@ export function registerRoadmapTools(server) {
|
|
|
157
157
|
"- Re-run `roadmapContext` after edits to confirm references remain consistent.",
|
|
158
158
|
]),
|
|
159
159
|
lintSection(lintSuggestions),
|
|
160
|
-
nextCallSection(`roadmapContext(projectPath=\"${governanceDir}\", roadmapId=\"${roadmapId}\")`),
|
|
160
|
+
nextCallSection(`roadmapContext(projectPath=\"${toProjectPath(governanceDir)}\", roadmapId=\"${roadmapId}\")`),
|
|
161
161
|
],
|
|
162
162
|
});
|
|
163
163
|
return asText(markdown);
|
package/output/source/tasks.js
CHANGED
|
@@ -7,7 +7,7 @@ import { findTextReferences } from "./helpers/markdown/index.js";
|
|
|
7
7
|
import { asText, evidenceSection, guidanceSection, lintSection, nextCallSection, renderErrorMarkdown, renderToolResponseMarkdown, summarySection, } from "./helpers/response/index.js";
|
|
8
8
|
import { catchIt } from "./helpers/catch/index.js";
|
|
9
9
|
import { TASK_LINT_CODES, renderLintSuggestions } from "./helpers/linter/index.js";
|
|
10
|
-
import { resolveGovernanceDir, resolveScanDepth, resolveScanRoot, discoverProjects } from "./projitive.js";
|
|
10
|
+
import { resolveGovernanceDir, resolveScanDepth, resolveScanRoot, discoverProjects, toProjectPath } from "./projitive.js";
|
|
11
11
|
import { isValidRoadmapId } from "./roadmap.js";
|
|
12
12
|
export const TASKS_START = "<!-- PROJITIVE:TASKS:START -->";
|
|
13
13
|
export const TASKS_END = "<!-- PROJITIVE:TASKS:END -->";
|
|
@@ -535,7 +535,7 @@ export function registerTaskTools(server) {
|
|
|
535
535
|
guidanceSection(["- Pick one task ID and call `taskContext`."]),
|
|
536
536
|
lintSection(lintSuggestions),
|
|
537
537
|
nextCallSection(nextTaskId
|
|
538
|
-
? `taskContext(projectPath=\"${governanceDir}\", taskId=\"${nextTaskId}\")`
|
|
538
|
+
? `taskContext(projectPath=\"${toProjectPath(governanceDir)}\", taskId=\"${nextTaskId}\")`
|
|
539
539
|
: undefined),
|
|
540
540
|
],
|
|
541
541
|
});
|
|
@@ -609,7 +609,7 @@ export function registerTaskTools(server) {
|
|
|
609
609
|
"- Ensure each new task has stable TASK-xxxx ID and at least one roadmapRefs item.",
|
|
610
610
|
]),
|
|
611
611
|
nextCallSection(preferredProject
|
|
612
|
-
? `projectContext(projectPath=\"${preferredProject.governanceDir}\")`
|
|
612
|
+
? `projectContext(projectPath=\"${toProjectPath(preferredProject.governanceDir)}\")`
|
|
613
613
|
: "projectScan()"),
|
|
614
614
|
],
|
|
615
615
|
});
|
|
@@ -672,7 +672,7 @@ export function registerTaskTools(server) {
|
|
|
672
672
|
"- Re-run `taskContext` for the selectedTaskId after edits to verify evidence consistency.",
|
|
673
673
|
]),
|
|
674
674
|
lintSection(lintSuggestions),
|
|
675
|
-
nextCallSection(`taskContext(projectPath=\"${selected.governanceDir}\", taskId=\"${selected.task.id}\")`),
|
|
675
|
+
nextCallSection(`taskContext(projectPath=\"${toProjectPath(selected.governanceDir)}\", taskId=\"${selected.task.id}\")`),
|
|
676
676
|
],
|
|
677
677
|
});
|
|
678
678
|
return asText(markdown);
|
|
@@ -696,7 +696,7 @@ export function registerTaskTools(server) {
|
|
|
696
696
|
const task = tasks.find((item) => item.id === taskId);
|
|
697
697
|
if (!task) {
|
|
698
698
|
return {
|
|
699
|
-
...asText(renderErrorMarkdown("taskContext", `Task not found: ${taskId}`, ["run `taskList` to discover available IDs", "retry with an existing task ID"], `taskList(projectPath=\"${governanceDir}\")`)),
|
|
699
|
+
...asText(renderErrorMarkdown("taskContext", `Task not found: ${taskId}`, ["run `taskList` to discover available IDs", "retry with an existing task ID"], `taskList(projectPath=\"${toProjectPath(governanceDir)}\")`)),
|
|
700
700
|
isError: true,
|
|
701
701
|
};
|
|
702
702
|
}
|
|
@@ -753,7 +753,7 @@ export function registerTaskTools(server) {
|
|
|
753
753
|
"- After editing, re-run `taskContext` to verify references and context consistency.",
|
|
754
754
|
]),
|
|
755
755
|
lintSection(lintSuggestions),
|
|
756
|
-
nextCallSection(`taskContext(projectPath=\"${governanceDir}\", taskId=\"${task.id}\")`),
|
|
756
|
+
nextCallSection(`taskContext(projectPath=\"${toProjectPath(governanceDir)}\", taskId=\"${task.id}\")`),
|
|
757
757
|
],
|
|
758
758
|
});
|
|
759
759
|
return asText(coreMarkdown);
|