@projitive/mcp 1.0.1 → 1.0.3
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 +44 -20
- package/output/helpers/artifacts/artifacts.js +10 -0
- package/output/helpers/artifacts/artifacts.test.js +18 -0
- package/output/helpers/artifacts/index.js +1 -0
- package/output/helpers/index.js +3 -0
- package/output/helpers/linter/codes.js +25 -0
- package/output/helpers/linter/index.js +2 -0
- package/output/helpers/linter/linter.js +6 -0
- package/output/helpers/linter/linter.test.js +16 -0
- package/output/helpers/response/index.js +1 -0
- package/output/helpers/response/response.js +73 -0
- package/output/helpers/response/response.test.js +50 -0
- package/output/hooks.js +1 -14
- package/output/hooks.test.js +7 -18
- package/output/index.js +23 -5
- package/output/package.json +36 -0
- package/output/projitive.js +158 -124
- package/output/projitive.test.js +1 -0
- package/output/rendering-input-guard.test.js +20 -0
- package/output/roadmap.js +106 -80
- package/output/roadmap.test.js +11 -0
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectContext.md +48 -0
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectInit.md +40 -0
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectLocate.md +22 -0
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectNext.md +31 -0
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectScan.md +28 -0
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/roadmapContext.md +33 -0
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/roadmapList.md +25 -0
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/summary.json +90 -0
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/summary.md +17 -0
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskContext.md +47 -0
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskList.md +27 -0
- package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskNext.md +64 -0
- package/output/source/designs.js +38 -0
- package/output/source/helpers/artifacts/artifacts.js +10 -0
- package/output/source/helpers/artifacts/artifacts.test.js +18 -0
- package/output/source/helpers/artifacts/index.js +1 -0
- package/output/source/helpers/catch/catch.js +48 -0
- package/output/source/helpers/catch/catch.test.js +43 -0
- package/output/source/helpers/catch/index.js +1 -0
- package/output/source/helpers/files/files.js +62 -0
- package/output/source/helpers/files/files.test.js +32 -0
- package/output/source/helpers/files/index.js +1 -0
- package/output/source/helpers/index.js +6 -0
- package/output/source/helpers/linter/codes.js +25 -0
- package/output/source/helpers/linter/index.js +2 -0
- package/output/source/helpers/linter/linter.js +6 -0
- package/output/source/helpers/linter/linter.test.js +16 -0
- package/output/source/helpers/markdown/index.js +1 -0
- package/output/source/helpers/markdown/markdown.js +33 -0
- package/output/source/helpers/markdown/markdown.test.js +36 -0
- package/output/source/helpers/response/index.js +1 -0
- package/output/source/helpers/response/response.js +73 -0
- package/output/source/helpers/response/response.test.js +50 -0
- package/output/source/index.js +215 -0
- package/output/source/projitive.js +488 -0
- package/output/source/projitive.test.js +75 -0
- package/output/source/readme.js +26 -0
- package/output/source/reports.js +36 -0
- package/output/source/roadmap.js +165 -0
- package/output/source/roadmap.test.js +11 -0
- package/output/source/tasks.js +762 -0
- package/output/source/tasks.test.js +152 -0
- package/output/tasks.js +403 -204
- package/output/tasks.test.js +78 -4
- 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.3**
|
|
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
|
|
|
@@ -50,6 +50,17 @@ taskNext
|
|
|
50
50
|
-> taskNext (next cycle)
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
When no actionable task exists (`actionableTasks: 0`), use bootstrap path:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
taskNext
|
|
57
|
+
-> projectContext
|
|
58
|
+
-> create 1-3 TODO tasks in tasks.md marker block (from roadmap/readme/report gaps)
|
|
59
|
+
-> taskNext
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Optional customization: add `hooks/task_no_actionable.md` in governance root to override the default no-task discovery checklist.
|
|
63
|
+
|
|
53
64
|
When the agent starts inside a project:
|
|
54
65
|
|
|
55
66
|
```text
|
|
@@ -58,17 +69,43 @@ projectLocate -> projectContext -> taskList -> taskContext
|
|
|
58
69
|
|
|
59
70
|
## Quick Start
|
|
60
71
|
|
|
72
|
+
Use npm package directly in MCP client configuration:
|
|
73
|
+
|
|
61
74
|
```bash
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
npx -y @projitive/mcp
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
MCP client config example (`mcp.json`):
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"mcpServers": {
|
|
83
|
+
"projitive": {
|
|
84
|
+
"command": "npx",
|
|
85
|
+
"args": ["-y", "@projitive/mcp"],
|
|
86
|
+
"env": {
|
|
87
|
+
"PROJITIVE_SCAN_ROOT_PATH": "/absolute/path/to/your/workspace",
|
|
88
|
+
"PROJITIVE_SCAN_MAX_DEPTH": "3"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
66
93
|
```
|
|
67
94
|
|
|
68
|
-
|
|
95
|
+
Environment variables:
|
|
96
|
+
|
|
97
|
+
- `PROJITIVE_SCAN_ROOT_PATH`: fallback scan root for discovery methods when `rootPath` is omitted.
|
|
98
|
+
- `PROJITIVE_SCAN_MAX_DEPTH`: fallback scan depth when `maxDepth` is omitted (`0-8`, default `3`).
|
|
99
|
+
|
|
100
|
+
Local path startup is not the recommended usage mode in this README.
|
|
101
|
+
|
|
102
|
+
For maintainers/contributors only:
|
|
69
103
|
|
|
70
104
|
```bash
|
|
71
|
-
|
|
105
|
+
cd packages/mcp
|
|
106
|
+
npm ci
|
|
107
|
+
npm run build
|
|
108
|
+
npm run test
|
|
72
109
|
```
|
|
73
110
|
|
|
74
111
|
## Spec Version
|
|
@@ -332,17 +369,9 @@ node /absolute/path/to/packages/mcp/output/index.js
|
|
|
332
369
|
|
|
333
370
|
- **Purpose**: return task detail + related evidence locations in one call (replacing `trace.references`).
|
|
334
371
|
- **Input**: `projectPath`, `taskId`
|
|
335
|
-
- **HOOK Injection**:
|
|
336
|
-
- If `hooks/task_get_head.md` exists, its content is prepended to result.
|
|
337
|
-
- If `hooks/task_get_footer.md` exists, its content is appended to result.
|
|
338
|
-
- Used for project-level custom guidance without changing core `taskContext` shape.
|
|
339
372
|
- **Output Example (Markdown)**:
|
|
340
373
|
|
|
341
374
|
```markdown
|
|
342
|
-
[hooks/task_get_head.md content (if present)]
|
|
343
|
-
|
|
344
|
-
---
|
|
345
|
-
|
|
346
375
|
# taskContext
|
|
347
376
|
|
|
348
377
|
## Summary
|
|
@@ -354,7 +383,6 @@ node /absolute/path/to/packages/mcp/output/index.js
|
|
|
354
383
|
- updatedAt: 2026-02-17T12:00:00.000Z
|
|
355
384
|
- roadmapRefs: ROADMAP-0001
|
|
356
385
|
- taskLocation: /workspace/proj-a/tasks.md#L42
|
|
357
|
-
- hookStatus: head=loaded, footer=missing
|
|
358
386
|
|
|
359
387
|
## Evidence
|
|
360
388
|
### Related Artifacts
|
|
@@ -379,10 +407,6 @@ node /absolute/path/to/packages/mcp/output/index.js
|
|
|
379
407
|
|
|
380
408
|
## Next Call
|
|
381
409
|
- taskContext(projectPath="/workspace/proj-a", taskId="TASK-0003")
|
|
382
|
-
|
|
383
|
-
---
|
|
384
|
-
|
|
385
|
-
[hooks/task_get_footer.md content (if present)]
|
|
386
410
|
```
|
|
387
411
|
|
|
388
412
|
### Roadmap Layer
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./artifacts.js";
|
package/output/helpers/index.js
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./response.js";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export function asText(markdown) {
|
|
2
|
+
return {
|
|
3
|
+
content: [{ type: "text", text: markdown }],
|
|
4
|
+
};
|
|
5
|
+
}
|
|
6
|
+
function withFallback(lines) {
|
|
7
|
+
return lines.length > 0 ? lines : ["- (none)"];
|
|
8
|
+
}
|
|
9
|
+
function shouldKeepRawLine(trimmed) {
|
|
10
|
+
if (trimmed.length === 0) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
if (trimmed.startsWith("#") || trimmed.startsWith(">") || trimmed.startsWith("```")) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (/^[-*+]\s/.test(trimmed)) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (/^\d+\.\s/.test(trimmed)) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
function normalizeLine(line) {
|
|
25
|
+
const trimmed = line.trim();
|
|
26
|
+
if (shouldKeepRawLine(trimmed)) {
|
|
27
|
+
return line;
|
|
28
|
+
}
|
|
29
|
+
return `- ${trimmed}`;
|
|
30
|
+
}
|
|
31
|
+
function normalizeLines(lines) {
|
|
32
|
+
return lines.map((line) => normalizeLine(line));
|
|
33
|
+
}
|
|
34
|
+
export function section(title, lines) {
|
|
35
|
+
return { title, lines: normalizeLines(lines) };
|
|
36
|
+
}
|
|
37
|
+
export function summarySection(lines) {
|
|
38
|
+
return section("Summary", lines);
|
|
39
|
+
}
|
|
40
|
+
export function evidenceSection(lines) {
|
|
41
|
+
return section("Evidence", lines);
|
|
42
|
+
}
|
|
43
|
+
export function guidanceSection(lines) {
|
|
44
|
+
return section("Agent Guidance", lines);
|
|
45
|
+
}
|
|
46
|
+
export function lintSection(lines) {
|
|
47
|
+
return section("Lint Suggestions", lines);
|
|
48
|
+
}
|
|
49
|
+
export function nextCallSection(nextCall) {
|
|
50
|
+
return section("Next Call", nextCall ? [nextCall] : []);
|
|
51
|
+
}
|
|
52
|
+
export function renderToolResponseMarkdown(payload) {
|
|
53
|
+
const body = payload.sections.flatMap((section) => [
|
|
54
|
+
`## ${section.title}`,
|
|
55
|
+
...withFallback(section.lines),
|
|
56
|
+
"",
|
|
57
|
+
]);
|
|
58
|
+
return [
|
|
59
|
+
`# ${payload.toolName}`,
|
|
60
|
+
"",
|
|
61
|
+
...body,
|
|
62
|
+
].join("\n").trimEnd();
|
|
63
|
+
}
|
|
64
|
+
export function renderErrorMarkdown(toolName, cause, nextSteps, retryExample) {
|
|
65
|
+
return renderToolResponseMarkdown({
|
|
66
|
+
toolName,
|
|
67
|
+
sections: [
|
|
68
|
+
section("Error", [`cause: ${cause}`]),
|
|
69
|
+
section("Next Step", nextSteps),
|
|
70
|
+
section("Retry Example", [retryExample ?? "(none)"]),
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { asText, nextCallSection, renderErrorMarkdown, renderToolResponseMarkdown, summarySection, } from "./response.js";
|
|
3
|
+
describe("response helpers", () => {
|
|
4
|
+
it("wraps markdown text as MCP text content", () => {
|
|
5
|
+
const result = asText("# hello");
|
|
6
|
+
expect(result.content).toEqual([{ type: "text", text: "# hello" }]);
|
|
7
|
+
});
|
|
8
|
+
it("renders error markdown sections", () => {
|
|
9
|
+
const markdown = renderErrorMarkdown("taskContext", "bad id", ["retry"], "taskContext(...)");
|
|
10
|
+
expect(markdown).toContain("# taskContext");
|
|
11
|
+
expect(markdown).toContain("## Error");
|
|
12
|
+
expect(markdown).toContain("- cause: bad id");
|
|
13
|
+
expect(markdown).toContain("- retry");
|
|
14
|
+
expect(markdown).toContain("## Retry Example");
|
|
15
|
+
});
|
|
16
|
+
it("renders standard tool response sections with fallback", () => {
|
|
17
|
+
const markdown = renderToolResponseMarkdown({
|
|
18
|
+
toolName: "taskList",
|
|
19
|
+
sections: [
|
|
20
|
+
{ title: "Summary", lines: ["- governanceDir: /tmp/.projitive"] },
|
|
21
|
+
{ title: "Evidence", lines: [] },
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
expect(markdown).toContain("# taskList");
|
|
25
|
+
expect(markdown).toContain("## Summary");
|
|
26
|
+
expect(markdown).toContain("## Evidence");
|
|
27
|
+
expect(markdown).toContain("- (none)");
|
|
28
|
+
});
|
|
29
|
+
it("auto-prefixes plain lines in section helpers", () => {
|
|
30
|
+
const markdown = renderToolResponseMarkdown({
|
|
31
|
+
toolName: "taskList",
|
|
32
|
+
sections: [
|
|
33
|
+
summarySection(["governanceDir: /tmp/.projitive"]),
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
expect(markdown).toContain("- governanceDir: /tmp/.projitive");
|
|
37
|
+
});
|
|
38
|
+
it("nextCallSection accepts optional call and falls back when missing", () => {
|
|
39
|
+
const withCall = renderToolResponseMarkdown({
|
|
40
|
+
toolName: "taskList",
|
|
41
|
+
sections: [nextCallSection("taskContext(projectPath=\"/tmp\", taskId=\"TASK-0001\")")],
|
|
42
|
+
});
|
|
43
|
+
expect(withCall).toContain("- taskContext(projectPath=\"/tmp\", taskId=\"TASK-0001\")");
|
|
44
|
+
const withoutCall = renderToolResponseMarkdown({
|
|
45
|
+
toolName: "taskList",
|
|
46
|
+
sections: [nextCallSection(undefined)],
|
|
47
|
+
});
|
|
48
|
+
expect(withoutCall).toContain("- (none)");
|
|
49
|
+
});
|
|
50
|
+
});
|
package/output/hooks.js
CHANGED
|
@@ -30,20 +30,7 @@ async function readHookFile(filePath) {
|
|
|
30
30
|
}
|
|
31
31
|
return { ok: true, content };
|
|
32
32
|
}
|
|
33
|
-
export async function resolveHookForEvent(governanceDir,
|
|
34
|
-
const taskHookPath = task.hooks[event];
|
|
35
|
-
if (taskHookPath) {
|
|
36
|
-
const resolvedTaskPath = path.resolve(governanceDir, taskHookPath);
|
|
37
|
-
const taskFile = await readHookFile(resolvedTaskPath);
|
|
38
|
-
if (taskFile.ok) {
|
|
39
|
-
return {
|
|
40
|
-
event,
|
|
41
|
-
source: "task",
|
|
42
|
-
path: resolvedTaskPath,
|
|
43
|
-
content: taskFile.content,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
}
|
|
33
|
+
export async function resolveHookForEvent(governanceDir, event) {
|
|
47
34
|
const globalPath = path.join(governanceDir, "hooks", GLOBAL_EVENT_FILES[event]);
|
|
48
35
|
const globalFile = await readHookFile(globalPath);
|
|
49
36
|
if (globalFile.ok) {
|
package/output/hooks.test.js
CHANGED
|
@@ -3,7 +3,6 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest";
|
|
5
5
|
import { detectHookEvent, resolveHookForEvent } from "./hooks.js";
|
|
6
|
-
import { normalizeTask } from "./tasks.js";
|
|
7
6
|
const tempPaths = [];
|
|
8
7
|
async function createTempDir() {
|
|
9
8
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "projitive-mcp-test-"));
|
|
@@ -22,30 +21,20 @@ describe("hooks module", () => {
|
|
|
22
21
|
expect(detectHookEvent("IN_PROGRESS", "BLOCKED")).toBe("onBlocked");
|
|
23
22
|
expect(detectHookEvent("BLOCKED", "TODO")).toBe(null);
|
|
24
23
|
});
|
|
25
|
-
it("
|
|
24
|
+
it("loads global hook when global file exists", async () => {
|
|
26
25
|
const root = await createTempDir();
|
|
27
26
|
const governanceDir = path.join(root, "gov");
|
|
28
27
|
await fs.mkdir(path.join(governanceDir, "hooks"), { recursive: true });
|
|
29
28
|
await fs.writeFile(path.join(governanceDir, "hooks", "on_task_assigned.md"), "global assigned", "utf-8");
|
|
30
|
-
await
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
title: "Task",
|
|
34
|
-
status: "TODO",
|
|
35
|
-
hooks: { onAssigned: "./hooks/custom-assigned.md" },
|
|
36
|
-
});
|
|
37
|
-
const hook = await resolveHookForEvent(governanceDir, task, "onAssigned");
|
|
38
|
-
expect(hook.source).toBe("task");
|
|
39
|
-
expect(hook.content).toContain("task assigned");
|
|
29
|
+
const hook = await resolveHookForEvent(governanceDir, "onAssigned");
|
|
30
|
+
expect(hook.source).toBe("global");
|
|
31
|
+
expect(hook.content).toContain("global assigned");
|
|
40
32
|
});
|
|
41
|
-
it("
|
|
33
|
+
it("returns none when global hook is missing", async () => {
|
|
42
34
|
const root = await createTempDir();
|
|
43
35
|
const governanceDir = path.join(root, "gov");
|
|
44
36
|
await fs.mkdir(path.join(governanceDir, "hooks"), { recursive: true });
|
|
45
|
-
await
|
|
46
|
-
|
|
47
|
-
const hook = await resolveHookForEvent(governanceDir, task, "onCompleted");
|
|
48
|
-
expect(hook.source).toBe("global");
|
|
49
|
-
expect(hook.content).toContain("global completed");
|
|
37
|
+
const hook = await resolveHookForEvent(governanceDir, "onCompleted");
|
|
38
|
+
expect(hook.source).toBe("none");
|
|
50
39
|
});
|
|
51
40
|
});
|
package/output/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import fs from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import process from "node:process";
|
|
@@ -10,14 +11,29 @@ import { registerProjectTools } from "./projitive.js";
|
|
|
10
11
|
import { registerTaskTools } from "./tasks.js";
|
|
11
12
|
import { registerRoadmapTools } from "./roadmap.js";
|
|
12
13
|
const PROJITIVE_SPEC_VERSION = "1.0.0";
|
|
14
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
15
|
+
const sourceDir = path.dirname(currentFilePath);
|
|
16
|
+
const repoRoot = path.resolve(sourceDir, "..", "..", "..");
|
|
17
|
+
function resolveRuntimeVersion() {
|
|
18
|
+
const packageJsonPath = path.resolve(sourceDir, "..", "package.json");
|
|
19
|
+
try {
|
|
20
|
+
const raw = readFileSync(packageJsonPath, "utf-8");
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
if (typeof parsed.version === "string" && parsed.version.trim().length > 0) {
|
|
23
|
+
return parsed.version.trim();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// fallback handled below
|
|
28
|
+
}
|
|
29
|
+
return PROJITIVE_SPEC_VERSION;
|
|
30
|
+
}
|
|
31
|
+
const MCP_RUNTIME_VERSION = resolveRuntimeVersion();
|
|
13
32
|
const server = new McpServer({
|
|
14
33
|
name: "projitive",
|
|
15
|
-
version:
|
|
34
|
+
version: MCP_RUNTIME_VERSION,
|
|
16
35
|
description: "Semantic Projitive MCP for project/task discovery and agent guidance with markdown-first outputs",
|
|
17
36
|
});
|
|
18
|
-
const currentFilePath = fileURLToPath(import.meta.url);
|
|
19
|
-
const sourceDir = path.dirname(currentFilePath);
|
|
20
|
-
const repoRoot = path.resolve(sourceDir, "..", "..", "..");
|
|
21
37
|
function resolveRepoFile(relativePath) {
|
|
22
38
|
return path.join(repoRoot, relativePath);
|
|
23
39
|
}
|
|
@@ -170,7 +186,7 @@ function registerGovernancePrompts() {
|
|
|
170
186
|
"Checklist:",
|
|
171
187
|
"- Transition is valid per status machine.",
|
|
172
188
|
"- links/roadmapRefs remain parseable and consistent.",
|
|
173
|
-
"-
|
|
189
|
+
"- Only `hooks/task_no_actionable.md` is used as global background hook for no-task discovery.",
|
|
174
190
|
].join("\n");
|
|
175
191
|
return asUserPrompt(text);
|
|
176
192
|
});
|
|
@@ -200,6 +216,8 @@ registerRoadmapTools(server);
|
|
|
200
216
|
registerGovernanceResources();
|
|
201
217
|
registerGovernancePrompts();
|
|
202
218
|
async function main() {
|
|
219
|
+
console.error(`[projitive-mcp] starting server`);
|
|
220
|
+
console.error(`[projitive-mcp] version=${MCP_RUNTIME_VERSION} spec=${PROJITIVE_SPEC_VERSION} transport=stdio pid=${process.pid}`);
|
|
203
221
|
const transport = new StdioServerTransport();
|
|
204
222
|
await server.connect(transport);
|
|
205
223
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@projitive/mcp",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "Projitive MCP Server for project and task discovery/update",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mcp": "output/index.js"
|
|
10
|
+
},
|
|
11
|
+
"main": "./output/index.js",
|
|
12
|
+
"types": "./output/index.d.ts",
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"lint": "tsc -p tsconfig.json --noEmit",
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"dev": "tsc -p tsconfig.json --watch"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"output"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.17.5",
|
|
28
|
+
"zod": "^3.23.8"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^24.3.0",
|
|
32
|
+
"tsx": "^4.20.5",
|
|
33
|
+
"typescript": "^5.9.2",
|
|
34
|
+
"vitest": "^3.2.4"
|
|
35
|
+
}
|
|
36
|
+
}
|