@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.
Files changed (33) hide show
  1. package/README.md +29 -1
  2. package/output/helpers/artifacts/artifacts.js +10 -0
  3. package/output/helpers/artifacts/artifacts.test.js +18 -0
  4. package/output/helpers/artifacts/index.js +1 -0
  5. package/output/helpers/index.js +3 -0
  6. package/output/helpers/linter/codes.js +25 -0
  7. package/output/helpers/linter/index.js +2 -0
  8. package/output/helpers/linter/linter.js +6 -0
  9. package/output/helpers/linter/linter.test.js +16 -0
  10. package/output/helpers/response/index.js +1 -0
  11. package/output/helpers/response/response.js +73 -0
  12. package/output/helpers/response/response.test.js +50 -0
  13. package/output/index.js +1 -0
  14. package/output/projitive.js +252 -97
  15. package/output/projitive.test.js +29 -1
  16. package/output/rendering-input-guard.test.js +20 -0
  17. package/output/roadmap.js +106 -80
  18. package/output/roadmap.test.js +11 -0
  19. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectContext.md +48 -0
  20. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectInit.md +40 -0
  21. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectLocate.md +22 -0
  22. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectNext.md +31 -0
  23. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectScan.md +28 -0
  24. package/output/smoke-reports/2026-02-18T13-18-19-740Z/roadmapContext.md +33 -0
  25. package/output/smoke-reports/2026-02-18T13-18-19-740Z/roadmapList.md +25 -0
  26. package/output/smoke-reports/2026-02-18T13-18-19-740Z/summary.json +90 -0
  27. package/output/smoke-reports/2026-02-18T13-18-19-740Z/summary.md +17 -0
  28. package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskContext.md +47 -0
  29. package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskList.md +27 -0
  30. package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskNext.md +64 -0
  31. package/output/tasks.js +341 -162
  32. package/output/tasks.test.js +51 -1
  33. 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.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";
@@ -1,3 +1,6 @@
1
+ export * from './artifacts/index.js';
1
2
  export * from './catch/index.js';
2
3
  export * from './files/index.js';
4
+ export * from './linter/index.js';
3
5
  export * from './markdown/index.js';
6
+ export * from './response/index.js';
@@ -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,2 @@
1
+ export * from './linter.js';
2
+ export * from './codes.js';
@@ -0,0 +1,6 @@
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
+ }
@@ -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 |",