@projitive/mcp 1.0.0 → 1.0.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.
- package/README.md +29 -1
- 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/index.js +1 -0
- package/output/projitive.js +252 -97
- package/output/projitive.test.js +29 -1
- 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/tasks.js +341 -162
- package/output/tasks.test.js +51 -1
- 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.2**
|
|
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
|
|
|
@@ -109,6 +109,34 @@ node /absolute/path/to/packages/mcp/output/index.js
|
|
|
109
109
|
|
|
110
110
|
### Discovery Layer
|
|
111
111
|
|
|
112
|
+
#### `projectInit`
|
|
113
|
+
|
|
114
|
+
- **Purpose**: manually initialize governance directory structure for a project (default `.projitive`).
|
|
115
|
+
- **Input**: `rootPath?`, `governanceDir?`, `force?`
|
|
116
|
+
- **Output Example (Markdown)**:
|
|
117
|
+
|
|
118
|
+
```markdown
|
|
119
|
+
# projectInit
|
|
120
|
+
|
|
121
|
+
## Summary
|
|
122
|
+
- rootPath: /workspace/proj-a
|
|
123
|
+
- governanceDir: /workspace/proj-a/.projitive
|
|
124
|
+
- markerPath: /workspace/proj-a/.projitive/.projitive
|
|
125
|
+
- force: false
|
|
126
|
+
|
|
127
|
+
## Evidence
|
|
128
|
+
- createdFiles: 4
|
|
129
|
+
- updatedFiles: 0
|
|
130
|
+
- skippedFiles: 0
|
|
131
|
+
|
|
132
|
+
## Agent Guidance
|
|
133
|
+
- If files were skipped and you want to overwrite templates, rerun with force=true.
|
|
134
|
+
- Continue with projectContext and taskList for execution.
|
|
135
|
+
|
|
136
|
+
## Next Call
|
|
137
|
+
- projectContext(projectPath="/workspace/proj-a/.projitive")
|
|
138
|
+
```
|
|
139
|
+
|
|
112
140
|
#### `projectNext`
|
|
113
141
|
|
|
114
142
|
- **Purpose**: directly list recently actionable projects (ranked by actionable task count and recency).
|
|
@@ -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/index.js
CHANGED
|
@@ -46,6 +46,7 @@ function renderMethodCatalogMarkdown() {
|
|
|
46
46
|
"## Methods",
|
|
47
47
|
"| Group | Method | Role |",
|
|
48
48
|
"|---|---|---|",
|
|
49
|
+
"| Project | projectInit | initialize governance directory structure |",
|
|
49
50
|
"| Project | projectScan | discover governance projects by marker |",
|
|
50
51
|
"| Project | projectNext | rank actionable projects |",
|
|
51
52
|
"| Project | projectLocate | resolve nearest governance root |",
|