@projitive/mcp 2.0.2 → 2.0.4

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 (41) hide show
  1. package/README.md +14 -1
  2. package/output/package.json +8 -2
  3. package/output/source/common/artifacts.js +1 -1
  4. package/output/source/common/artifacts.test.js +11 -11
  5. package/output/source/common/errors.js +19 -19
  6. package/output/source/common/files.js +11 -11
  7. package/output/source/common/files.test.js +14 -14
  8. package/output/source/common/index.js +10 -10
  9. package/output/source/common/linter.js +27 -27
  10. package/output/source/common/linter.test.js +9 -9
  11. package/output/source/common/markdown.js +3 -3
  12. package/output/source/common/markdown.test.js +15 -15
  13. package/output/source/common/response.js +74 -74
  14. package/output/source/common/response.test.js +30 -30
  15. package/output/source/common/store.js +40 -40
  16. package/output/source/common/store.test.js +72 -72
  17. package/output/source/common/types.js +3 -3
  18. package/output/source/common/utils.js +8 -8
  19. package/output/source/index.js +16 -16
  20. package/output/source/index.test.js +64 -64
  21. package/output/source/prompts/index.js +3 -3
  22. package/output/source/prompts/quickStart.js +96 -96
  23. package/output/source/prompts/taskDiscovery.js +184 -180
  24. package/output/source/prompts/taskExecution.js +148 -147
  25. package/output/source/resources/designs.js +26 -26
  26. package/output/source/resources/designs.test.js +88 -88
  27. package/output/source/resources/governance.js +19 -19
  28. package/output/source/resources/index.js +2 -2
  29. package/output/source/resources/readme.js +7 -7
  30. package/output/source/resources/readme.test.js +113 -113
  31. package/output/source/resources/reports.js +10 -10
  32. package/output/source/resources/reports.test.js +83 -83
  33. package/output/source/tools/index.js +3 -3
  34. package/output/source/tools/project.js +196 -191
  35. package/output/source/tools/project.test.js +187 -164
  36. package/output/source/tools/roadmap.js +173 -76
  37. package/output/source/tools/roadmap.test.js +58 -42
  38. package/output/source/tools/task.js +380 -255
  39. package/output/source/tools/task.test.js +117 -110
  40. package/output/source/types.js +22 -22
  41. package/package.json +8 -2
package/README.md CHANGED
@@ -21,6 +21,17 @@ Why teams use it:
21
21
  - Better evidence traceability
22
22
  - More predictable multi-agent delivery
23
23
 
24
+ ## Outcomes You Can Expect
25
+
26
+ After onboarding Projitive MCP, teams typically get these outcomes quickly:
27
+
28
+ - Faster execution bootstrapping: create missing work items with taskCreate/roadmapCreate.
29
+ - Better state integrity: task and roadmap transitions remain traceable and verifiable.
30
+ - Stronger delivery continuity: discover -> execute -> verify -> reprioritize loops stay stable.
31
+ - Easier adoption: new contributors follow a deterministic call sequence.
32
+
33
+ Key point: best results come from autonomous execution agents such as OpenClaw.
34
+
24
35
  ## What It Is Useful For
25
36
 
26
37
  Projitive MCP helps agents move work forward in governed projects without losing traceability.
@@ -82,7 +93,7 @@ Recommended minimal sequence:
82
93
 
83
94
  1. taskNext
84
95
  2. taskContext
85
- 3. taskUpdate and/or roadmapUpdate
96
+ 3. taskCreate/taskUpdate and/or roadmapCreate/roadmapUpdate
86
97
  4. taskContext
87
98
  5. taskNext
88
99
 
@@ -124,9 +135,11 @@ sequenceDiagram
124
135
  | Task | taskList | List tasks |
125
136
  | Task | taskNext | Select best actionable task |
126
137
  | Task | taskContext | Get task evidence and reading order |
138
+ | Task | taskCreate | Create task |
127
139
  | Task | taskUpdate | Update task state and metadata |
128
140
  | Roadmap | roadmapList | List roadmaps and linked tasks |
129
141
  | Roadmap | roadmapContext | Get roadmap context |
142
+ | Roadmap | roadmapCreate | Create roadmap milestone |
130
143
  | Roadmap | roadmapUpdate | Update roadmap milestone fields |
131
144
 
132
145
  ### Resources
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projitive/mcp",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Projitive MCP Server for project and task discovery/update",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -16,7 +16,9 @@
16
16
  "test": "vitest run",
17
17
  "test:coverage": "vitest run --coverage",
18
18
  "benchmark": "vitest bench --run",
19
- "lint": "tsc -p tsconfig.json --noEmit",
19
+ "lint": "npm run lint:types && npm run lint:eslint",
20
+ "lint:types": "tsc -p tsconfig.json --noEmit",
21
+ "lint:eslint": "eslint .",
20
22
  "build": "rm -rf output && tsc -p tsconfig.json",
21
23
  "prepublishOnly": "npm run build",
22
24
  "dev": "tsc -p tsconfig.json --watch"
@@ -31,8 +33,12 @@
31
33
  "devDependencies": {
32
34
  "@types/node": "^24.3.0",
33
35
  "@vitest/coverage-v8": "^3.2.4",
36
+ "@eslint/js": "^9.37.0",
37
+ "eslint": "^9.37.0",
38
+ "globals": "^16.4.0",
34
39
  "tsx": "^4.20.5",
35
40
  "typescript": "^5.9.2",
41
+ "typescript-eslint": "^8.45.0",
36
42
  "vitest": "^3.2.4"
37
43
  }
38
44
  }
@@ -2,7 +2,7 @@ export function candidateFilesFromArtifacts(artifacts) {
2
2
  return artifacts
3
3
  .filter((item) => item.exists)
4
4
  .flatMap((item) => {
5
- if (item.kind === "file") {
5
+ if (item.kind === 'file') {
6
6
  return [item.path];
7
7
  }
8
8
  return (item.markdownFiles ?? []).map((entry) => entry.path);
@@ -1,18 +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", () => {
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
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 },
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
8
  {
9
- name: "designs",
10
- kind: "directory",
11
- path: "/a/designs",
9
+ name: 'designs',
10
+ kind: 'directory',
11
+ path: '/a/designs',
12
12
  exists: true,
13
- markdownFiles: [{ path: "/a/designs/d1.md", lineCount: 10 }],
13
+ markdownFiles: [{ path: '/a/designs/d1.md', lineCount: 10 }],
14
14
  },
15
15
  ]);
16
- expect(candidates).toEqual(["/a/README.md", "/a/designs/d1.md"]);
16
+ expect(candidates).toEqual(['/a/README.md', '/a/designs/d1.md']);
17
17
  });
18
18
  });
@@ -6,49 +6,49 @@ export class ProjitiveError extends Error {
6
6
  super(message);
7
7
  this.code = code;
8
8
  this.details = details;
9
- this.name = "ProjitiveError";
9
+ this.name = 'ProjitiveError';
10
10
  }
11
11
  }
12
12
  // Project related errors
13
13
  export class ProjectError extends ProjitiveError {
14
14
  constructor(message, code, details) {
15
15
  super(message, code, details);
16
- this.name = "ProjectError";
16
+ this.name = 'ProjectError';
17
17
  }
18
18
  }
19
19
  export class ProjectNotFoundError extends ProjectError {
20
20
  constructor(inputPath) {
21
- super(`Project not found at path: ${inputPath}`, "PROJECT_NOT_FOUND", {
21
+ super(`Project not found at path: ${inputPath}`, 'PROJECT_NOT_FOUND', {
22
22
  inputPath,
23
23
  });
24
24
  }
25
25
  }
26
26
  export class GovernanceRootNotFoundError extends ProjectError {
27
27
  constructor(projectPath) {
28
- super(`Governance root not found for project: ${projectPath}`, "GOVERNANCE_ROOT_NOT_FOUND", { projectPath });
28
+ super(`Governance root not found for project: ${projectPath}`, 'GOVERNANCE_ROOT_NOT_FOUND', { projectPath });
29
29
  }
30
30
  }
31
31
  // Task related errors
32
32
  export class TaskError extends ProjitiveError {
33
33
  constructor(message, code, details) {
34
34
  super(message, code, details);
35
- this.name = "TaskError";
35
+ this.name = 'TaskError';
36
36
  }
37
37
  }
38
38
  export class TaskNotFoundError extends TaskError {
39
39
  constructor(taskId) {
40
- super(`Task not found: ${taskId}`, "TASK_NOT_FOUND", { taskId });
40
+ super(`Task not found: ${taskId}`, 'TASK_NOT_FOUND', { taskId });
41
41
  }
42
42
  }
43
43
  export class InvalidTaskIdError extends TaskError {
44
44
  constructor(taskId) {
45
- super(`Invalid task ID: ${taskId}`, "INVALID_TASK_ID", { taskId });
45
+ super(`Invalid task ID: ${taskId}`, 'INVALID_TASK_ID', { taskId });
46
46
  }
47
47
  }
48
48
  export class TaskValidationError extends TaskError {
49
49
  errors;
50
50
  constructor(taskId, errors) {
51
- super(`Task validation failed for ${taskId}: ${errors.join(", ")}`, "TASK_VALIDATION_FAILED", { taskId, errors });
51
+ super(`Task validation failed for ${taskId}: ${errors.join(', ')}`, 'TASK_VALIDATION_FAILED', { taskId, errors });
52
52
  this.errors = errors;
53
53
  }
54
54
  }
@@ -56,26 +56,26 @@ export class TaskValidationError extends TaskError {
56
56
  export class FileError extends ProjitiveError {
57
57
  filePath;
58
58
  constructor(message, filePath, code, details) {
59
- super(message, code || "FILE_ERROR", { filePath, ...details });
59
+ super(message, code || 'FILE_ERROR', { filePath, ...details });
60
60
  this.filePath = filePath;
61
- this.name = "FileError";
61
+ this.name = 'FileError';
62
62
  }
63
63
  }
64
64
  export class FileNotFoundError extends FileError {
65
65
  constructor(filePath) {
66
- super(`File not found: ${filePath}`, filePath, "FILE_NOT_FOUND");
66
+ super(`File not found: ${filePath}`, filePath, 'FILE_NOT_FOUND');
67
67
  }
68
68
  }
69
69
  export class FileReadError extends FileError {
70
70
  constructor(filePath, cause) {
71
- super(`Failed to read file: ${filePath}`, filePath, "FILE_READ_ERROR", {
71
+ super(`Failed to read file: ${filePath}`, filePath, 'FILE_READ_ERROR', {
72
72
  cause: cause?.message,
73
73
  });
74
74
  }
75
75
  }
76
76
  export class FileWriteError extends FileError {
77
77
  constructor(filePath, cause) {
78
- super(`Failed to write file: ${filePath}`, filePath, "FILE_WRITE_ERROR", {
78
+ super(`Failed to write file: ${filePath}`, filePath, 'FILE_WRITE_ERROR', {
79
79
  cause: cause?.message,
80
80
  });
81
81
  }
@@ -84,15 +84,15 @@ export class FileWriteError extends FileError {
84
84
  export class ValidationError extends ProjitiveError {
85
85
  errors;
86
86
  constructor(message, errors = [], code) {
87
- super(message, code || "VALIDATION_FAILED", { errors });
87
+ super(message, code || 'VALIDATION_FAILED', { errors });
88
88
  this.errors = errors;
89
- this.name = "ValidationError";
89
+ this.name = 'ValidationError';
90
90
  }
91
91
  }
92
92
  export class ConfidenceScoreError extends ValidationError {
93
93
  score;
94
94
  constructor(message, score, errors = []) {
95
- super(message, errors, "CONFIDENCE_SCORE_ERROR");
95
+ super(message, errors, 'CONFIDENCE_SCORE_ERROR');
96
96
  this.score = score;
97
97
  this.score = score;
98
98
  }
@@ -101,19 +101,19 @@ export class ConfidenceScoreError extends ValidationError {
101
101
  export class MCPError extends ProjitiveError {
102
102
  constructor(message, code, details) {
103
103
  super(message, code, details);
104
- this.name = "MCPError";
104
+ this.name = 'MCPError';
105
105
  }
106
106
  }
107
107
  export class ResourceNotFoundError extends MCPError {
108
108
  constructor(resourceUri) {
109
- super(`Resource not found: ${resourceUri}`, "RESOURCE_NOT_FOUND", {
109
+ super(`Resource not found: ${resourceUri}`, 'RESOURCE_NOT_FOUND', {
110
110
  resourceUri,
111
111
  });
112
112
  }
113
113
  }
114
114
  export class PromptNotFoundError extends MCPError {
115
115
  constructor(promptName) {
116
- super(`Prompt not found: ${promptName}`, "PROMPT_NOT_FOUND", {
116
+ super(`Prompt not found: ${promptName}`, 'PROMPT_NOT_FOUND', {
117
117
  promptName,
118
118
  });
119
119
  }
@@ -1,10 +1,10 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { catchIt } from "./catch.js";
4
- const FILE_ARTIFACTS = ["README.md", "roadmap.md", "tasks.md"];
5
- const DIRECTORY_ARTIFACTS = ["designs", "reports", "templates"];
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { catchIt } from './catch.js';
4
+ const FILE_ARTIFACTS = ['README.md', 'roadmap.md', 'tasks.md'];
5
+ const DIRECTORY_ARTIFACTS = ['designs', 'reports', 'templates'];
6
6
  async function fileLineCount(filePath) {
7
- const content = await fs.readFile(filePath, "utf-8");
7
+ const content = await fs.readFile(filePath, 'utf-8');
8
8
  if (!content) {
9
9
  return 0;
10
10
  }
@@ -16,7 +16,7 @@ async function listMarkdownFiles(dirPath) {
16
16
  return [];
17
17
  }
18
18
  const entries = entriesResult.value;
19
- const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md"));
19
+ const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.md'));
20
20
  const result = [];
21
21
  for (const file of files) {
22
22
  const fullPath = path.join(dirPath, file.name);
@@ -32,14 +32,14 @@ export async function discoverGovernanceArtifacts(governanceDir) {
32
32
  if (!accessResult.isError()) {
33
33
  result.push({
34
34
  name: artifact,
35
- kind: "file",
35
+ kind: 'file',
36
36
  path: artifactPath,
37
37
  exists: true,
38
38
  lineCount: await fileLineCount(artifactPath),
39
39
  });
40
40
  }
41
41
  else {
42
- result.push({ name: artifact, kind: "file", path: artifactPath, exists: false });
42
+ result.push({ name: artifact, kind: 'file', path: artifactPath, exists: false });
43
43
  }
44
44
  }
45
45
  for (const artifact of DIRECTORY_ARTIFACTS) {
@@ -48,14 +48,14 @@ export async function discoverGovernanceArtifacts(governanceDir) {
48
48
  if (!accessResult.isError()) {
49
49
  result.push({
50
50
  name: artifact,
51
- kind: "directory",
51
+ kind: 'directory',
52
52
  path: artifactPath,
53
53
  exists: true,
54
54
  markdownFiles: await listMarkdownFiles(artifactPath),
55
55
  });
56
56
  }
57
57
  else {
58
- result.push({ name: artifact, kind: "directory", path: artifactPath, exists: false, markdownFiles: [] });
58
+ result.push({ name: artifact, kind: 'directory', path: artifactPath, exists: false, markdownFiles: [] });
59
59
  }
60
60
  }
61
61
  return result;
@@ -1,11 +1,11 @@
1
- import fs from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { afterEach, describe, expect, it } from "vitest";
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterEach, describe, expect, it } from 'vitest';
5
5
  import { discoverGovernanceArtifacts } from './files.js';
6
6
  const tempPaths = [];
7
7
  async function createTempDir() {
8
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), "projitive-mcp-test-"));
8
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'projitive-mcp-test-'));
9
9
  tempPaths.push(dir);
10
10
  return dir;
11
11
  }
@@ -14,19 +14,19 @@ afterEach(async () => {
14
14
  await fs.rm(dir, { recursive: true, force: true });
15
15
  }));
16
16
  });
17
- describe("files module", () => {
18
- it("discovers governance artifacts with paths and line counts", async () => {
17
+ describe('files module', () => {
18
+ it('discovers governance artifacts with paths and line counts', async () => {
19
19
  const root = await createTempDir();
20
- await fs.writeFile(path.join(root, "README.md"), "# Readme\n", "utf-8");
21
- await fs.writeFile(path.join(root, "tasks.md"), "# Tasks\n## TODO\n", "utf-8");
22
- await fs.mkdir(path.join(root, "designs"), { recursive: true });
23
- await fs.writeFile(path.join(root, "designs", "feature-design.md"), "# Design\n", "utf-8");
20
+ await fs.writeFile(path.join(root, 'README.md'), '# Readme\n', 'utf-8');
21
+ await fs.writeFile(path.join(root, 'tasks.md'), '# Tasks\n## TODO\n', 'utf-8');
22
+ await fs.mkdir(path.join(root, 'designs'), { recursive: true });
23
+ await fs.writeFile(path.join(root, 'designs', 'feature-design.md'), '# Design\n', 'utf-8');
24
24
  const artifacts = await discoverGovernanceArtifacts(root);
25
- const readme = artifacts.find((item) => item.name === "README.md");
26
- const designs = artifacts.find((item) => item.name === "designs");
25
+ const readme = artifacts.find((item) => item.name === 'README.md');
26
+ const designs = artifacts.find((item) => item.name === 'designs');
27
27
  expect(readme?.exists).toBe(true);
28
28
  expect(readme?.lineCount).toBe(2);
29
29
  expect(designs?.exists).toBe(true);
30
- expect(designs?.markdownFiles?.[0].path.endsWith("feature-design.md")).toBe(true);
30
+ expect(designs?.markdownFiles?.[0].path.endsWith('feature-design.md')).toBe(true);
31
31
  });
32
32
  });
@@ -1,10 +1,10 @@
1
- export * from "./errors.js";
2
- export * from "./types.js";
3
- export * from "./utils.js";
4
- export * from "./markdown.js";
5
- export * from "./files.js";
6
- export * from "./response.js";
7
- export * from "./catch.js";
8
- export * from "./artifacts.js";
9
- export * from "./linter.js";
10
- export * from "./store.js";
1
+ export * from './errors.js';
2
+ export * from './types.js';
3
+ export * from './utils.js';
4
+ export * from './markdown.js';
5
+ export * from './files.js';
6
+ export * from './response.js';
7
+ export * from './catch.js';
8
+ export * from './artifacts.js';
9
+ export * from './linter.js';
10
+ export * from './store.js';
@@ -1,39 +1,39 @@
1
1
  export function renderLintSuggestions(suggestions) {
2
2
  return suggestions.map((item) => {
3
- const suffix = item.fixHint ? ` ${item.fixHint}` : "";
3
+ const suffix = item.fixHint ? ` ${item.fixHint}` : '';
4
4
  return `- [${item.code}] ${item.message}${suffix}`;
5
5
  });
6
6
  }
7
7
  export const TASK_LINT_CODES = {
8
- DUPLICATE_ID: "TASK_DUPLICATE_ID",
9
- IN_PROGRESS_OWNER_EMPTY: "TASK_IN_PROGRESS_OWNER_EMPTY",
10
- DONE_LINKS_MISSING: "TASK_DONE_LINKS_MISSING",
11
- BLOCKED_SUMMARY_EMPTY: "TASK_BLOCKED_SUMMARY_EMPTY",
12
- UPDATED_AT_INVALID: "TASK_UPDATED_AT_INVALID",
13
- ROADMAP_REFS_EMPTY: "TASK_ROADMAP_REFS_EMPTY",
14
- OUTSIDE_MARKER: "TASK_OUTSIDE_MARKER",
15
- LINK_TARGET_MISSING: "TASK_LINK_TARGET_MISSING",
16
- LINK_PATH_FORMAT_INVALID: "TASK_LINK_PATH_FORMAT_INVALID",
17
- HOOK_FILE_MISSING: "TASK_HOOK_FILE_MISSING",
18
- FILTER_EMPTY: "TASK_FILTER_EMPTY",
19
- CONTEXT_HOOK_HEAD_MISSING: "TASK_CONTEXT_HOOK_HEAD_MISSING",
20
- CONTEXT_HOOK_FOOTER_MISSING: "TASK_CONTEXT_HOOK_FOOTER_MISSING",
8
+ DUPLICATE_ID: 'TASK_DUPLICATE_ID',
9
+ IN_PROGRESS_OWNER_EMPTY: 'TASK_IN_PROGRESS_OWNER_EMPTY',
10
+ DONE_LINKS_MISSING: 'TASK_DONE_LINKS_MISSING',
11
+ BLOCKED_SUMMARY_EMPTY: 'TASK_BLOCKED_SUMMARY_EMPTY',
12
+ UPDATED_AT_INVALID: 'TASK_UPDATED_AT_INVALID',
13
+ ROADMAP_REFS_EMPTY: 'TASK_ROADMAP_REFS_EMPTY',
14
+ OUTSIDE_MARKER: 'TASK_OUTSIDE_MARKER',
15
+ LINK_TARGET_MISSING: 'TASK_LINK_TARGET_MISSING',
16
+ LINK_PATH_FORMAT_INVALID: 'TASK_LINK_PATH_FORMAT_INVALID',
17
+ HOOK_FILE_MISSING: 'TASK_HOOK_FILE_MISSING',
18
+ FILTER_EMPTY: 'TASK_FILTER_EMPTY',
19
+ CONTEXT_HOOK_HEAD_MISSING: 'TASK_CONTEXT_HOOK_HEAD_MISSING',
20
+ CONTEXT_HOOK_FOOTER_MISSING: 'TASK_CONTEXT_HOOK_FOOTER_MISSING',
21
21
  // Spec v1.1.0 - Blocker Categorization
22
- BLOCKED_WITHOUT_BLOCKER: "TASK_BLOCKED_WITHOUT_BLOCKER",
23
- BLOCKER_TYPE_INVALID: "TASK_BLOCKER_TYPE_INVALID",
24
- BLOCKER_DESCRIPTION_EMPTY: "TASK_BLOCKER_DESCRIPTION_EMPTY",
25
- IN_PROGRESS_WITHOUT_SUBSTATE: "TASK_IN_PROGRESS_WITHOUT_SUBSTATE",
26
- SUBSTATE_PHASE_INVALID: "TASK_SUBSTATE_PHASE_INVALID",
27
- SUBSTATE_CONFIDENCE_INVALID: "TASK_SUBSTATE_CONFIDENCE_INVALID",
22
+ BLOCKED_WITHOUT_BLOCKER: 'TASK_BLOCKED_WITHOUT_BLOCKER',
23
+ BLOCKER_TYPE_INVALID: 'TASK_BLOCKER_TYPE_INVALID',
24
+ BLOCKER_DESCRIPTION_EMPTY: 'TASK_BLOCKER_DESCRIPTION_EMPTY',
25
+ IN_PROGRESS_WITHOUT_SUBSTATE: 'TASK_IN_PROGRESS_WITHOUT_SUBSTATE',
26
+ SUBSTATE_PHASE_INVALID: 'TASK_SUBSTATE_PHASE_INVALID',
27
+ SUBSTATE_CONFIDENCE_INVALID: 'TASK_SUBSTATE_CONFIDENCE_INVALID',
28
28
  };
29
29
  export const ROADMAP_LINT_CODES = {
30
- IDS_EMPTY: "ROADMAP_IDS_EMPTY",
31
- TASKS_EMPTY: "ROADMAP_TASKS_EMPTY",
32
- TASK_REFS_EMPTY: "ROADMAP_TASK_REFS_EMPTY",
33
- UNKNOWN_REFS: "ROADMAP_UNKNOWN_REFS",
34
- ZERO_LINKED_TASKS: "ROADMAP_ZERO_LINKED_TASKS",
35
- CONTEXT_RELATED_TASKS_EMPTY: "ROADMAP_CONTEXT_RELATED_TASKS_EMPTY",
30
+ IDS_EMPTY: 'ROADMAP_IDS_EMPTY',
31
+ TASKS_EMPTY: 'ROADMAP_TASKS_EMPTY',
32
+ TASK_REFS_EMPTY: 'ROADMAP_TASK_REFS_EMPTY',
33
+ UNKNOWN_REFS: 'ROADMAP_UNKNOWN_REFS',
34
+ ZERO_LINKED_TASKS: 'ROADMAP_ZERO_LINKED_TASKS',
35
+ CONTEXT_RELATED_TASKS_EMPTY: 'ROADMAP_CONTEXT_RELATED_TASKS_EMPTY',
36
36
  };
37
37
  export const PROJECT_LINT_CODES = {
38
- TASKS_FILE_MISSING: "PROJECT_TASKS_FILE_MISSING",
38
+ TASKS_FILE_MISSING: 'PROJECT_TASKS_FILE_MISSING',
39
39
  };
@@ -1,16 +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", () => {
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
5
  const lines = renderLintSuggestions([
6
- { code: "TASK_001", message: "Example lint" },
6
+ { code: 'TASK_001', message: 'Example lint' },
7
7
  ]);
8
- expect(lines).toEqual(["- [TASK_001] Example lint"]);
8
+ expect(lines).toEqual(['- [TASK_001] Example lint']);
9
9
  });
10
- it("appends fixHint when provided", () => {
10
+ it('appends fixHint when provided', () => {
11
11
  const lines = renderLintSuggestions([
12
- { code: "TASK_002", message: "Missing field.", fixHint: "Set owner." },
12
+ { code: 'TASK_002', message: 'Missing field.', fixHint: 'Set owner.' },
13
13
  ]);
14
- expect(lines).toEqual(["- [TASK_002] Missing field. Set owner."]);
14
+ expect(lines).toEqual(['- [TASK_002] Missing field. Set owner.']);
15
15
  });
16
16
  });
@@ -1,6 +1,6 @@
1
- import fs from "node:fs/promises";
1
+ import fs from 'node:fs/promises';
2
2
  export async function readMarkdownSections(filePath) {
3
- const content = await fs.readFile(filePath, "utf-8");
3
+ const content = await fs.readFile(filePath, 'utf-8');
4
4
  const lines = content.split(/\r?\n/);
5
5
  const headers = [];
6
6
  lines.forEach((line, index) => {
@@ -21,7 +21,7 @@ export async function readMarkdownSections(filePath) {
21
21
  return { filePath, lineCount: lines.length, sections };
22
22
  }
23
23
  export async function findTextReferences(filePath, needle) {
24
- const content = await fs.readFile(filePath, "utf-8");
24
+ const content = await fs.readFile(filePath, 'utf-8');
25
25
  const lines = content.split(/\r?\n/);
26
26
  const result = [];
27
27
  lines.forEach((line, index) => {
@@ -1,11 +1,11 @@
1
- import fs from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { afterEach, describe, expect, it } from "vitest";
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterEach, describe, expect, it } from 'vitest';
5
5
  import { findTextReferences, readMarkdownSections } from './markdown.js';
6
6
  const tempPaths = [];
7
7
  async function createTempDir() {
8
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), "projitive-mcp-test-"));
8
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'projitive-mcp-test-'));
9
9
  tempPaths.push(dir);
10
10
  return dir;
11
11
  }
@@ -14,22 +14,22 @@ afterEach(async () => {
14
14
  await fs.rm(dir, { recursive: true, force: true });
15
15
  }));
16
16
  });
17
- describe("markdown module", () => {
18
- it("locates markdown sections with line ranges", async () => {
17
+ describe('markdown module', () => {
18
+ it('locates markdown sections with line ranges', async () => {
19
19
  const root = await createTempDir();
20
- const file = path.join(root, "tasks.md");
21
- await fs.writeFile(file, ["# Tasks", "", "## TODO", "- TASK-0001", "## DONE", "- TASK-0002"].join("\n"), "utf-8");
20
+ const file = path.join(root, 'tasks.md');
21
+ await fs.writeFile(file, ['# Tasks', '', '## TODO', '- TASK-0001', '## DONE', '- TASK-0002'].join('\n'), 'utf-8');
22
22
  const located = await readMarkdownSections(file);
23
23
  expect(located.lineCount).toBe(6);
24
- expect(located.sections[0].heading).toBe("Tasks");
25
- expect(located.sections[1].heading).toBe("TODO");
24
+ expect(located.sections[0].heading).toBe('Tasks');
25
+ expect(located.sections[1].heading).toBe('TODO');
26
26
  expect(located.sections[1].startLine).toBe(3);
27
27
  });
28
- it("finds ID references with exact line number", async () => {
28
+ it('finds ID references with exact line number', async () => {
29
29
  const root = await createTempDir();
30
- const file = path.join(root, "reports.md");
31
- await fs.writeFile(file, ["Task: TASK-0001", "Roadmap: ROADMAP-0001"].join("\n"), "utf-8");
32
- const refs = await findTextReferences(file, "TASK-0001");
30
+ const file = path.join(root, 'reports.md');
31
+ await fs.writeFile(file, ['Task: TASK-0001', 'Roadmap: ROADMAP-0001'].join('\n'), 'utf-8');
32
+ const refs = await findTextReferences(file, 'TASK-0001');
33
33
  expect(refs).toHaveLength(1);
34
34
  expect(refs[0].line).toBe(1);
35
35
  });