@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.
Files changed (66) hide show
  1. package/README.md +44 -20
  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/hooks.js +1 -14
  14. package/output/hooks.test.js +7 -18
  15. package/output/index.js +23 -5
  16. package/output/package.json +36 -0
  17. package/output/projitive.js +158 -124
  18. package/output/projitive.test.js +1 -0
  19. package/output/rendering-input-guard.test.js +20 -0
  20. package/output/roadmap.js +106 -80
  21. package/output/roadmap.test.js +11 -0
  22. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectContext.md +48 -0
  23. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectInit.md +40 -0
  24. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectLocate.md +22 -0
  25. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectNext.md +31 -0
  26. package/output/smoke-reports/2026-02-18T13-18-19-740Z/projectScan.md +28 -0
  27. package/output/smoke-reports/2026-02-18T13-18-19-740Z/roadmapContext.md +33 -0
  28. package/output/smoke-reports/2026-02-18T13-18-19-740Z/roadmapList.md +25 -0
  29. package/output/smoke-reports/2026-02-18T13-18-19-740Z/summary.json +90 -0
  30. package/output/smoke-reports/2026-02-18T13-18-19-740Z/summary.md +17 -0
  31. package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskContext.md +47 -0
  32. package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskList.md +27 -0
  33. package/output/smoke-reports/2026-02-18T13-18-19-740Z/taskNext.md +64 -0
  34. package/output/source/designs.js +38 -0
  35. package/output/source/helpers/artifacts/artifacts.js +10 -0
  36. package/output/source/helpers/artifacts/artifacts.test.js +18 -0
  37. package/output/source/helpers/artifacts/index.js +1 -0
  38. package/output/source/helpers/catch/catch.js +48 -0
  39. package/output/source/helpers/catch/catch.test.js +43 -0
  40. package/output/source/helpers/catch/index.js +1 -0
  41. package/output/source/helpers/files/files.js +62 -0
  42. package/output/source/helpers/files/files.test.js +32 -0
  43. package/output/source/helpers/files/index.js +1 -0
  44. package/output/source/helpers/index.js +6 -0
  45. package/output/source/helpers/linter/codes.js +25 -0
  46. package/output/source/helpers/linter/index.js +2 -0
  47. package/output/source/helpers/linter/linter.js +6 -0
  48. package/output/source/helpers/linter/linter.test.js +16 -0
  49. package/output/source/helpers/markdown/index.js +1 -0
  50. package/output/source/helpers/markdown/markdown.js +33 -0
  51. package/output/source/helpers/markdown/markdown.test.js +36 -0
  52. package/output/source/helpers/response/index.js +1 -0
  53. package/output/source/helpers/response/response.js +73 -0
  54. package/output/source/helpers/response/response.test.js +50 -0
  55. package/output/source/index.js +215 -0
  56. package/output/source/projitive.js +488 -0
  57. package/output/source/projitive.test.js +75 -0
  58. package/output/source/readme.js +26 -0
  59. package/output/source/reports.js +36 -0
  60. package/output/source/roadmap.js +165 -0
  61. package/output/source/roadmap.test.js +11 -0
  62. package/output/source/tasks.js +762 -0
  63. package/output/source/tasks.test.js +152 -0
  64. package/output/tasks.js +403 -204
  65. package/output/tasks.test.js +78 -4
  66. 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.1**
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
- cd packages/mcp
63
- npm ci
64
- npm run build
65
- npm run test
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
- Then configure your MCP client to run:
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
- node /absolute/path/to/packages/mcp/output/index.js
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";
@@ -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/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, task, event) {
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) {
@@ -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("prefers task-level hook over global hook", async () => {
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 fs.writeFile(path.join(governanceDir, "hooks", "custom-assigned.md"), "task assigned", "utf-8");
31
- const task = normalizeTask({
32
- id: "TASK-0001",
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("falls back to global hook when task-level hook is missing", async () => {
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 fs.writeFile(path.join(governanceDir, "hooks", "on_task_completed.md"), "global completed", "utf-8");
46
- const task = normalizeTask({ id: "TASK-0001", title: "Task", status: "IN_PROGRESS" });
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: PROJITIVE_SPEC_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
- "- Hook paths (if any) still resolve.",
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
+ }