@projitive/mcp 1.0.8 → 1.1.1

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 (55) hide show
  1. package/output/package.json +4 -1
  2. package/output/source/{helpers/catch → common}/catch.js +6 -6
  3. package/output/source/{helpers/catch → common}/catch.test.js +6 -6
  4. package/output/source/common/confidence.js +231 -0
  5. package/output/source/common/confidence.test.js +205 -0
  6. package/output/source/common/errors.js +120 -0
  7. package/output/source/{helpers/files → common}/files.js +1 -1
  8. package/output/source/{helpers/files → common}/files.test.js +1 -1
  9. package/output/source/common/index.js +10 -0
  10. package/output/source/{helpers/linter/codes.js → common/linter.js} +13 -0
  11. package/output/source/{helpers/markdown → common}/markdown.test.js +1 -1
  12. package/output/source/{helpers/response → common}/response.test.js +1 -1
  13. package/output/source/common/types.js +7 -0
  14. package/output/source/common/utils.js +39 -0
  15. package/output/source/design-context.js +51 -500
  16. package/output/source/index.js +8 -193
  17. package/output/source/index.test.js +116 -0
  18. package/output/source/prompts/index.js +9 -0
  19. package/output/source/prompts/quickStart.js +94 -0
  20. package/output/source/prompts/taskDiscovery.js +190 -0
  21. package/output/source/prompts/taskExecution.js +161 -0
  22. package/output/source/resources/designs.js +108 -0
  23. package/output/source/resources/designs.test.js +154 -0
  24. package/output/source/resources/governance.js +40 -0
  25. package/output/source/resources/index.js +6 -0
  26. package/output/source/resources/readme.test.js +167 -0
  27. package/output/source/{reports.js → resources/reports.js} +5 -3
  28. package/output/source/resources/reports.test.js +149 -0
  29. package/output/source/tools/index.js +8 -0
  30. package/output/source/{projitive.js → tools/project.js} +4 -6
  31. package/output/source/tools/project.test.js +322 -0
  32. package/output/source/{roadmap.js → tools/roadmap.js} +4 -7
  33. package/output/source/tools/roadmap.test.js +103 -0
  34. package/output/source/{tasks.js → tools/task.js} +577 -22
  35. package/output/source/tools/task.test.js +473 -0
  36. package/output/source/types.js +67 -0
  37. package/package.json +4 -1
  38. package/output/source/designs.js +0 -38
  39. package/output/source/helpers/artifacts/index.js +0 -1
  40. package/output/source/helpers/catch/index.js +0 -1
  41. package/output/source/helpers/files/index.js +0 -1
  42. package/output/source/helpers/index.js +0 -6
  43. package/output/source/helpers/linter/index.js +0 -2
  44. package/output/source/helpers/linter/linter.js +0 -6
  45. package/output/source/helpers/markdown/index.js +0 -1
  46. package/output/source/helpers/response/index.js +0 -1
  47. package/output/source/projitive.test.js +0 -111
  48. package/output/source/roadmap.test.js +0 -11
  49. package/output/source/tasks.test.js +0 -152
  50. /package/output/source/{helpers/artifacts → common}/artifacts.js +0 -0
  51. /package/output/source/{helpers/artifacts → common}/artifacts.test.js +0 -0
  52. /package/output/source/{helpers/linter → common}/linter.test.js +0 -0
  53. /package/output/source/{helpers/markdown → common}/markdown.js +0 -0
  54. /package/output/source/{helpers/response → common}/response.js +0 -0
  55. /package/output/source/{readme.js → resources/readme.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projitive/mcp",
3
- "version": "1.0.8",
3
+ "version": "1.1.1",
4
4
  "description": "Projitive MCP Server for project and task discovery/update",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -14,6 +14,8 @@
14
14
  },
15
15
  "scripts": {
16
16
  "test": "vitest run",
17
+ "test:coverage": "vitest run --coverage",
18
+ "benchmark": "vitest bench --run",
17
19
  "lint": "tsc -p tsconfig.json --noEmit",
18
20
  "build": "rm -rf output && tsc -p tsconfig.json",
19
21
  "prepublishOnly": "npm run build",
@@ -28,6 +30,7 @@
28
30
  },
29
31
  "devDependencies": {
30
32
  "@types/node": "^24.3.0",
33
+ "@vitest/coverage-v8": "^3.2.4",
31
34
  "tsx": "^4.20.5",
32
35
  "typescript": "^5.9.2",
33
36
  "vitest": "^3.2.4"
@@ -1,10 +1,10 @@
1
- // 辅助函数:检查是否为 PromiseLike
1
+ // Helper function: check if value is PromiseLike
2
2
  function isPromiseLike(value) {
3
3
  return value != null && typeof value === 'object' && 'then' in value && typeof value.then === 'function';
4
4
  }
5
5
  /**
6
- * 构造成功结果对象
7
- * isError 始终返回 false
6
+ * Construct success result object
7
+ * isError always returns false
8
8
  */
9
9
  function createSuccess(value) {
10
10
  return {
@@ -14,8 +14,8 @@ function createSuccess(value) {
14
14
  };
15
15
  }
16
16
  /**
17
- * 构造失败结果对象
18
- * isError 始终返回 true
17
+ * Construct failure result object
18
+ * isError always returns true
19
19
  */
20
20
  function createFailure(error) {
21
21
  return {
@@ -43,6 +43,6 @@ export async function catchIt(input) {
43
43
  catch (error) {
44
44
  return createFailure(error);
45
45
  }
46
- // 理论上不会到达这里,兜底类型安全
46
+ // Theoretically shouldn't reach here, fallback for type safety
47
47
  return createFailure(new Error('Unexpected input type'));
48
48
  }
@@ -1,39 +1,39 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { catchIt } from './catch.js';
3
3
  describe('catchIt', () => {
4
- it('同步函数返回值应为 valueerror undefined,isError false', async () => {
4
+ it('sync function should return value, error undefined, isError false', async () => {
5
5
  const result = await catchIt(() => 123);
6
6
  expect(result.value).toBe(123);
7
7
  expect(result.error).toBeUndefined();
8
8
  expect(result.isError()).toBe(false);
9
9
  });
10
- it('异步函数返回值应为 valueerror undefined,isError false', async () => {
10
+ it('async function should return value, error undefined, isError false', async () => {
11
11
  const result = await catchIt(async () => 456);
12
12
  expect(result.value).toBe(456);
13
13
  expect(result.error).toBeUndefined();
14
14
  expect(result.isError()).toBe(false);
15
15
  });
16
- it('同步抛出异常时应返回 errorvalue undefined,isError true', async () => {
16
+ it('sync throw should return error, value undefined, isError true', async () => {
17
17
  const error = new Error('fail');
18
18
  const result = await catchIt(() => { throw error; });
19
19
  expect(result.value).toBeUndefined();
20
20
  expect(result.error).toBe(error);
21
21
  expect(result.isError()).toBe(true);
22
22
  });
23
- it('异步抛出异常时应返回 errorvalue undefined,isError true', async () => {
23
+ it('async throw should return error, value undefined, isError true', async () => {
24
24
  const error = new Error('fail-async');
25
25
  const result = await catchIt(() => Promise.reject(error));
26
26
  expect(result.value).toBeUndefined();
27
27
  expect(result.error).toBe(error);
28
28
  expect(result.isError()).toBe(true);
29
29
  });
30
- it('PromiseLike resolve 时应返回 valueerror undefined,isError false', async () => {
30
+ it('PromiseLike resolve should return value, error undefined, isError false', async () => {
31
31
  const result = await catchIt(Promise.resolve('ok'));
32
32
  expect(result.value).toBe('ok');
33
33
  expect(result.error).toBeUndefined();
34
34
  expect(result.isError()).toBe(false);
35
35
  });
36
- it('PromiseLike reject 时应返回 errorvalue undefined,isError true', async () => {
36
+ it('PromiseLike reject should return error, value undefined, isError true', async () => {
37
37
  const error = new Error('promise-fail');
38
38
  const result = await catchIt(Promise.reject(error));
39
39
  expect(result.value).toBeUndefined();
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Projitive MCP - Confidence Scoring for Spec v1.1.0
3
+ *
4
+ * This module implements the confidence scoring algorithm for auto-discovery
5
+ * and task creation, along with validation hooks integration.
6
+ */
7
+ import fs from "node:fs/promises";
8
+ import path from "node:path";
9
+ import { CONFIDENCE_WEIGHTS, CONFIDENCE_THRESHOLDS } from "../types.js";
10
+ // ============================================================================
11
+ // Confidence Scoring Algorithm
12
+ // ============================================================================
13
+ /**
14
+ * Calculate confidence score based on the three factors
15
+ * Formula: context_completeness * 0.4 + similar_task_history * 0.3 + specification_clarity * 0.3
16
+ */
17
+ export function calculateConfidenceScore(factors) {
18
+ // Validate input ranges
19
+ const validateFactor = (value, name) => {
20
+ if (value < 0 || value > 1) {
21
+ console.warn(`[Confidence] ${name} value ${value} is outside [0, 1] range, clamping`);
22
+ return Math.max(0, Math.min(1, value));
23
+ }
24
+ return value;
25
+ };
26
+ const contextCompleteness = validateFactor(factors.contextCompleteness, "contextCompleteness");
27
+ const similarTaskHistory = validateFactor(factors.similarTaskHistory, "similarTaskHistory");
28
+ const specificationClarity = validateFactor(factors.specificationClarity, "specificationClarity");
29
+ // Calculate weighted score
30
+ const score = contextCompleteness * CONFIDENCE_WEIGHTS.contextCompleteness +
31
+ similarTaskHistory * CONFIDENCE_WEIGHTS.similarTaskHistory +
32
+ specificationClarity * CONFIDENCE_WEIGHTS.specificationClarity;
33
+ // Determine recommendation
34
+ let recommendation;
35
+ if (score >= CONFIDENCE_THRESHOLDS.autoCreate) {
36
+ recommendation = "auto_create";
37
+ }
38
+ else if (score >= CONFIDENCE_THRESHOLDS.reviewRequired) {
39
+ recommendation = "review_required";
40
+ }
41
+ else {
42
+ recommendation = "do_not_create";
43
+ }
44
+ return {
45
+ score,
46
+ factors: {
47
+ contextCompleteness,
48
+ similarTaskHistory,
49
+ specificationClarity,
50
+ },
51
+ recommendation,
52
+ };
53
+ }
54
+ // ============================================================================
55
+ // Factor Calculation Helpers
56
+ // ============================================================================
57
+ /**
58
+ * Calculate context completeness factor by checking available governance artifacts
59
+ */
60
+ export async function calculateContextCompleteness(governanceDir) {
61
+ const requiredFiles = [
62
+ "tasks.md",
63
+ "roadmap.md",
64
+ "README.md"
65
+ ];
66
+ const optionalFiles = [
67
+ "hooks/task_no_actionable.md",
68
+ "hooks/task_auto_create_validation.md"
69
+ ];
70
+ let availableCount = 0;
71
+ const totalFiles = requiredFiles.length + optionalFiles.length;
72
+ // Check required files
73
+ for (const file of requiredFiles) {
74
+ const filePath = path.join(governanceDir, file);
75
+ try {
76
+ await fs.access(filePath);
77
+ availableCount++;
78
+ }
79
+ catch {
80
+ // File doesn't exist
81
+ }
82
+ }
83
+ // Check optional files (weighted 0.5 each)
84
+ for (const file of optionalFiles) {
85
+ const filePath = path.join(governanceDir, file);
86
+ try {
87
+ await fs.access(filePath);
88
+ availableCount += 0.5;
89
+ }
90
+ catch {
91
+ // File doesn't exist
92
+ }
93
+ }
94
+ return Math.min(1, availableCount / totalFiles);
95
+ }
96
+ /**
97
+ * Calculate similar task history success rate
98
+ */
99
+ export function calculateSimilarTaskHistory(tasks, candidateTaskSummary) {
100
+ // Filter similar tasks based on keyword matching
101
+ const keywords = candidateTaskSummary.toLowerCase().split(/\s+/).filter(w => w.length > 3);
102
+ const similarTasks = tasks.filter(task => {
103
+ const taskText = `${task.title} ${task.summary}`.toLowerCase();
104
+ return keywords.some(keyword => taskText.includes(keyword));
105
+ });
106
+ if (similarTasks.length === 0) {
107
+ return 0.5; // Neutral score when no history
108
+ }
109
+ // Calculate success rate
110
+ const completedTasks = similarTasks.filter(task => task.status === "DONE");
111
+ return completedTasks.length / similarTasks.length;
112
+ }
113
+ /**
114
+ * Calculate specification clarity factor
115
+ */
116
+ export function calculateSpecificationClarity(projectContext) {
117
+ let clarity = 0.3; // Base clarity
118
+ if (projectContext.hasRoadmap)
119
+ clarity += 0.2;
120
+ if (projectContext.hasDesignDocs)
121
+ clarity += 0.2;
122
+ if (projectContext.hasClearAcceptanceCriteria)
123
+ clarity += 0.3;
124
+ return Math.min(1, clarity);
125
+ }
126
+ // ============================================================================
127
+ // Validation Hooks Integration
128
+ // ============================================================================
129
+ const TASK_AUTO_CREATE_VALIDATION_HOOK = "task_auto_create_validation.md";
130
+ const DEFAULT_TASK_AUTO_CREATE_VALIDATION_HOOK = `# Task Auto-Create Validation Hook
131
+
132
+ ## Pre-Creation Checklist
133
+ - [ ] Context files exist and are readable
134
+ - [ ] Similar tasks have been completed successfully
135
+ - [ ] Acceptance criteria are clear and testable
136
+ - [ ] Dependencies are identified and available
137
+
138
+ ## Post-Creation Actions
139
+ - [ ] Add evidence link to analysis document
140
+ - [ ] Notify relevant stakeholders (if configured)
141
+ - [ ] Schedule validation review (24h for high-confidence)
142
+ `;
143
+ /**
144
+ * Check if task auto-create validation hook exists
145
+ */
146
+ export async function hasTaskAutoCreateValidationHook(governanceDir) {
147
+ const hookPath = path.join(governanceDir, "hooks", TASK_AUTO_CREATE_VALIDATION_HOOK);
148
+ try {
149
+ await fs.access(hookPath);
150
+ return true;
151
+ }
152
+ catch {
153
+ return false;
154
+ }
155
+ }
156
+ /**
157
+ * Get or create task auto-create validation hook
158
+ */
159
+ export async function getOrCreateTaskAutoCreateValidationHook(governanceDir) {
160
+ const hooksDir = path.join(governanceDir, "hooks");
161
+ const hookPath = path.join(hooksDir, TASK_AUTO_CREATE_VALIDATION_HOOK);
162
+ try {
163
+ // Try to read existing hook
164
+ const content = await fs.readFile(hookPath, "utf-8");
165
+ return content;
166
+ }
167
+ catch {
168
+ // Create hooks directory if it doesn't exist
169
+ try {
170
+ await fs.mkdir(hooksDir, { recursive: true });
171
+ }
172
+ catch {
173
+ // Directory already exists or creation failed silently
174
+ }
175
+ // Create default hook
176
+ await fs.writeFile(hookPath, DEFAULT_TASK_AUTO_CREATE_VALIDATION_HOOK, "utf-8");
177
+ return DEFAULT_TASK_AUTO_CREATE_VALIDATION_HOOK;
178
+ }
179
+ }
180
+ /**
181
+ * Run pre-creation validation checklist
182
+ */
183
+ export async function runPreCreationValidation(governanceDir, confidenceScore) {
184
+ const issues = [];
185
+ // Check confidence score first
186
+ if (confidenceScore.recommendation === "do_not_create") {
187
+ issues.push(`Confidence score ${confidenceScore.score.toFixed(2)} is below threshold (${CONFIDENCE_THRESHOLDS.reviewRequired})`);
188
+ }
189
+ // Check context completeness
190
+ if (confidenceScore.factors.contextCompleteness < 0.6) {
191
+ issues.push(`Context completeness (${confidenceScore.factors.contextCompleteness.toFixed(2)}) is low - more governance artifacts recommended`);
192
+ }
193
+ // Check for validation hook
194
+ const hasHook = await hasTaskAutoCreateValidationHook(governanceDir);
195
+ if (!hasHook) {
196
+ issues.push("Task auto-create validation hook not found - will create default hook");
197
+ }
198
+ return {
199
+ passed: issues.length === 0 || confidenceScore.recommendation === "auto_create",
200
+ issues,
201
+ };
202
+ }
203
+ // ============================================================================
204
+ // Confidence Report Generation
205
+ // ============================================================================
206
+ /**
207
+ * Generate a human-readable confidence report
208
+ */
209
+ export function generateConfidenceReport(confidenceScore) {
210
+ const lines = [
211
+ "# Confidence Score Report",
212
+ "",
213
+ `## Final Score: ${(confidenceScore.score * 100).toFixed(0)}%`,
214
+ `**Recommendation**: ${confidenceScore.recommendation.replace(/_/g, " ")}`,
215
+ "",
216
+ "## Factor Breakdown",
217
+ "",
218
+ `| Factor | Score | Weight | Contribution |`,
219
+ `|--------|-------|--------|--------------|`,
220
+ `| Context Completeness | ${(confidenceScore.factors.contextCompleteness * 100).toFixed(0)}% | ${(CONFIDENCE_WEIGHTS.contextCompleteness * 100).toFixed(0)}% | ${(confidenceScore.factors.contextCompleteness * CONFIDENCE_WEIGHTS.contextCompleteness * 100).toFixed(0)}% |`,
221
+ `| Similar Task History | ${(confidenceScore.factors.similarTaskHistory * 100).toFixed(0)}% | ${(CONFIDENCE_WEIGHTS.similarTaskHistory * 100).toFixed(0)}% | ${(confidenceScore.factors.similarTaskHistory * CONFIDENCE_WEIGHTS.similarTaskHistory * 100).toFixed(0)}% |`,
222
+ `| Specification Clarity | ${(confidenceScore.factors.specificationClarity * 100).toFixed(0)}% | ${(CONFIDENCE_WEIGHTS.specificationClarity * 100).toFixed(0)}% | ${(confidenceScore.factors.specificationClarity * CONFIDENCE_WEIGHTS.specificationClarity * 100).toFixed(0)}% |`,
223
+ "",
224
+ "## Thresholds",
225
+ "",
226
+ `- Auto-create: >= ${(CONFIDENCE_THRESHOLDS.autoCreate * 100).toFixed(0)}%`,
227
+ `- Review required: ${(CONFIDENCE_THRESHOLDS.reviewRequired * 100).toFixed(0)}% - ${(CONFIDENCE_THRESHOLDS.autoCreate * 100).toFixed(0)}%`,
228
+ `- Do not create: < ${(CONFIDENCE_THRESHOLDS.reviewRequired * 100).toFixed(0)}%`,
229
+ ];
230
+ return lines.join("\n");
231
+ }
@@ -0,0 +1,205 @@
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
+ import { calculateConfidenceScore, calculateContextCompleteness, calculateSimilarTaskHistory, calculateSpecificationClarity, hasTaskAutoCreateValidationHook, getOrCreateTaskAutoCreateValidationHook, runPreCreationValidation, generateConfidenceReport, } from "./confidence.js";
6
+ const tempPaths = [];
7
+ async function createTempDir() {
8
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "projitive-confidence-test-"));
9
+ tempPaths.push(dir);
10
+ return dir;
11
+ }
12
+ afterEach(async () => {
13
+ await Promise.all(tempPaths.splice(0).map(async (dir) => {
14
+ await fs.rm(dir, { recursive: true, force: true });
15
+ }));
16
+ });
17
+ describe("confidence module", () => {
18
+ describe("calculateConfidenceScore", () => {
19
+ it("calculates confidence score with valid inputs", () => {
20
+ const result = calculateConfidenceScore({
21
+ contextCompleteness: 0.8,
22
+ similarTaskHistory: 0.7,
23
+ specificationClarity: 0.9,
24
+ });
25
+ expect(result.score).toBeGreaterThan(0);
26
+ expect(result.score).toBeLessThanOrEqual(1);
27
+ expect(result.recommendation).toBeDefined();
28
+ });
29
+ it("recommends auto_create for high confidence scores", () => {
30
+ const result = calculateConfidenceScore({
31
+ contextCompleteness: 1.0,
32
+ similarTaskHistory: 1.0,
33
+ specificationClarity: 1.0,
34
+ });
35
+ expect(result.recommendation).toBe("auto_create");
36
+ });
37
+ it("recommends review_required for medium confidence scores", () => {
38
+ const result = calculateConfidenceScore({
39
+ contextCompleteness: 0.7,
40
+ similarTaskHistory: 0.7,
41
+ specificationClarity: 0.7,
42
+ });
43
+ expect(result.recommendation).toBe("review_required");
44
+ });
45
+ it("recommends do_not_create for low confidence scores", () => {
46
+ const result = calculateConfidenceScore({
47
+ contextCompleteness: 0.0,
48
+ similarTaskHistory: 0.0,
49
+ specificationClarity: 0.0,
50
+ });
51
+ expect(result.recommendation).toBe("do_not_create");
52
+ });
53
+ it("clamps values outside [0, 1] range", () => {
54
+ const result = calculateConfidenceScore({
55
+ contextCompleteness: 1.5,
56
+ similarTaskHistory: -0.5,
57
+ specificationClarity: 2.0,
58
+ });
59
+ expect(result.factors.contextCompleteness).toBe(1);
60
+ expect(result.factors.similarTaskHistory).toBe(0);
61
+ expect(result.factors.specificationClarity).toBe(1);
62
+ });
63
+ });
64
+ describe("calculateContextCompleteness", () => {
65
+ it("returns 0 for empty directory", async () => {
66
+ const root = await createTempDir();
67
+ const completeness = await calculateContextCompleteness(root);
68
+ expect(completeness).toBe(0);
69
+ });
70
+ it("returns higher score for more complete context", async () => {
71
+ const root = await createTempDir();
72
+ await fs.writeFile(path.join(root, "tasks.md"), "", "utf-8");
73
+ await fs.writeFile(path.join(root, "roadmap.md"), "", "utf-8");
74
+ await fs.writeFile(path.join(root, "README.md"), "", "utf-8");
75
+ const completeness = await calculateContextCompleteness(root);
76
+ expect(completeness).toBeGreaterThan(0.5);
77
+ });
78
+ });
79
+ describe("calculateSimilarTaskHistory", () => {
80
+ const sampleTasks = [
81
+ {
82
+ id: "TASK-0001",
83
+ title: "Implement feature X",
84
+ status: "DONE",
85
+ summary: "Build core functionality for feature X",
86
+ owner: "ai-copilot",
87
+ updatedAt: "2026-02-22T00:00:00.000Z",
88
+ links: [],
89
+ roadmapRefs: [],
90
+ },
91
+ {
92
+ id: "TASK-0002",
93
+ title: "Test feature Y",
94
+ status: "TODO",
95
+ summary: "Write tests for feature Y",
96
+ owner: "ai-copilot",
97
+ updatedAt: "2026-02-22T00:00:00.000Z",
98
+ links: [],
99
+ roadmapRefs: [],
100
+ },
101
+ {
102
+ id: "TASK-0003",
103
+ title: "Implement feature Z",
104
+ status: "DONE",
105
+ summary: "Build core functionality for feature Z",
106
+ owner: "ai-copilot",
107
+ updatedAt: "2026-02-22T00:00:00.000Z",
108
+ links: [],
109
+ roadmapRefs: [],
110
+ },
111
+ ];
112
+ it("returns 0.5 when no similar tasks", () => {
113
+ const result = calculateSimilarTaskHistory([], "Build something new");
114
+ expect(result).toBe(0.5);
115
+ });
116
+ it("calculates success rate for similar tasks", () => {
117
+ const result = calculateSimilarTaskHistory(sampleTasks, "Implement feature A with core functionality");
118
+ expect(result).toBeGreaterThan(0);
119
+ expect(result).toBeLessThanOrEqual(1);
120
+ });
121
+ it("handles tasks with no matching keywords", () => {
122
+ const result = calculateSimilarTaskHistory(sampleTasks, "Something completely different unrelated");
123
+ expect(result).toBe(0.5);
124
+ });
125
+ });
126
+ describe("calculateSpecificationClarity", () => {
127
+ it("returns base clarity with no context", () => {
128
+ const result = calculateSpecificationClarity({});
129
+ expect(result).toBe(0.3);
130
+ });
131
+ it("increases clarity with roadmap", () => {
132
+ const result = calculateSpecificationClarity({ hasRoadmap: true });
133
+ expect(result).toBeGreaterThan(0.3);
134
+ });
135
+ it("increases clarity with design docs", () => {
136
+ const result = calculateSpecificationClarity({ hasDesignDocs: true });
137
+ expect(result).toBeGreaterThan(0.3);
138
+ });
139
+ it("reaches maximum clarity with all factors", () => {
140
+ const result = calculateSpecificationClarity({
141
+ hasRoadmap: true,
142
+ hasDesignDocs: true,
143
+ hasClearAcceptanceCriteria: true,
144
+ });
145
+ expect(result).toBe(1);
146
+ });
147
+ });
148
+ describe("validation hooks", () => {
149
+ it("detects missing validation hook", async () => {
150
+ const root = await createTempDir();
151
+ const hasHook = await hasTaskAutoCreateValidationHook(root);
152
+ expect(hasHook).toBe(false);
153
+ });
154
+ it("creates default validation hook", async () => {
155
+ const root = await createTempDir();
156
+ const hookContent = await getOrCreateTaskAutoCreateValidationHook(root);
157
+ expect(hookContent).toContain("Task Auto-Create Validation Hook");
158
+ });
159
+ it("reads existing validation hook", async () => {
160
+ const root = await createTempDir();
161
+ const hooksDir = path.join(root, "hooks");
162
+ await fs.mkdir(hooksDir, { recursive: true });
163
+ const hookPath = path.join(hooksDir, "task_auto_create_validation.md");
164
+ await fs.writeFile(hookPath, "custom hook content", "utf-8");
165
+ const hookContent = await getOrCreateTaskAutoCreateValidationHook(root);
166
+ expect(hookContent).toBe("custom hook content");
167
+ });
168
+ });
169
+ describe("runPreCreationValidation", () => {
170
+ it("validates confidence score", async () => {
171
+ const root = await createTempDir();
172
+ const confidenceScore = calculateConfidenceScore({
173
+ contextCompleteness: 0.9,
174
+ similarTaskHistory: 0.9,
175
+ specificationClarity: 0.9,
176
+ });
177
+ const result = await runPreCreationValidation(root, confidenceScore);
178
+ expect(result.passed).toBeDefined();
179
+ expect(result.issues).toBeInstanceOf(Array);
180
+ });
181
+ });
182
+ describe("generateConfidenceReport", () => {
183
+ it("generates human-readable report", () => {
184
+ const confidenceScore = calculateConfidenceScore({
185
+ contextCompleteness: 0.8,
186
+ similarTaskHistory: 0.7,
187
+ specificationClarity: 0.9,
188
+ });
189
+ const report = generateConfidenceReport(confidenceScore);
190
+ expect(report).toContain("Confidence Score Report");
191
+ expect(report).toContain("Final Score");
192
+ expect(report).toContain("Factor Breakdown");
193
+ expect(report).toContain("Thresholds");
194
+ });
195
+ it("includes recommendation in report", () => {
196
+ const confidenceScore = calculateConfidenceScore({
197
+ contextCompleteness: 1.0,
198
+ similarTaskHistory: 1.0,
199
+ specificationClarity: 1.0,
200
+ });
201
+ const report = generateConfidenceReport(confidenceScore);
202
+ expect(report).toContain("auto create");
203
+ });
204
+ });
205
+ });
@@ -0,0 +1,120 @@
1
+ // Projitive unified error type definitions
2
+ export class ProjitiveError extends Error {
3
+ code;
4
+ details;
5
+ constructor(message, code, details) {
6
+ super(message);
7
+ this.code = code;
8
+ this.details = details;
9
+ this.name = "ProjitiveError";
10
+ }
11
+ }
12
+ // Project related errors
13
+ export class ProjectError extends ProjitiveError {
14
+ constructor(message, code, details) {
15
+ super(message, code, details);
16
+ this.name = "ProjectError";
17
+ }
18
+ }
19
+ export class ProjectNotFoundError extends ProjectError {
20
+ constructor(inputPath) {
21
+ super(`Project not found at path: ${inputPath}`, "PROJECT_NOT_FOUND", {
22
+ inputPath,
23
+ });
24
+ }
25
+ }
26
+ export class GovernanceRootNotFoundError extends ProjectError {
27
+ constructor(projectPath) {
28
+ super(`Governance root not found for project: ${projectPath}`, "GOVERNANCE_ROOT_NOT_FOUND", { projectPath });
29
+ }
30
+ }
31
+ // Task related errors
32
+ export class TaskError extends ProjitiveError {
33
+ constructor(message, code, details) {
34
+ super(message, code, details);
35
+ this.name = "TaskError";
36
+ }
37
+ }
38
+ export class TaskNotFoundError extends TaskError {
39
+ constructor(taskId) {
40
+ super(`Task not found: ${taskId}`, "TASK_NOT_FOUND", { taskId });
41
+ }
42
+ }
43
+ export class InvalidTaskIdError extends TaskError {
44
+ constructor(taskId) {
45
+ super(`Invalid task ID: ${taskId}`, "INVALID_TASK_ID", { taskId });
46
+ }
47
+ }
48
+ export class TaskValidationError extends TaskError {
49
+ errors;
50
+ constructor(taskId, errors) {
51
+ super(`Task validation failed for ${taskId}: ${errors.join(", ")}`, "TASK_VALIDATION_FAILED", { taskId, errors });
52
+ this.errors = errors;
53
+ }
54
+ }
55
+ // File operation errors
56
+ export class FileError extends ProjitiveError {
57
+ filePath;
58
+ constructor(message, filePath, code, details) {
59
+ super(message, code || "FILE_ERROR", { filePath, ...details });
60
+ this.filePath = filePath;
61
+ this.name = "FileError";
62
+ }
63
+ }
64
+ export class FileNotFoundError extends FileError {
65
+ constructor(filePath) {
66
+ super(`File not found: ${filePath}`, filePath, "FILE_NOT_FOUND");
67
+ }
68
+ }
69
+ export class FileReadError extends FileError {
70
+ constructor(filePath, cause) {
71
+ super(`Failed to read file: ${filePath}`, filePath, "FILE_READ_ERROR", {
72
+ cause: cause?.message,
73
+ });
74
+ }
75
+ }
76
+ export class FileWriteError extends FileError {
77
+ constructor(filePath, cause) {
78
+ super(`Failed to write file: ${filePath}`, filePath, "FILE_WRITE_ERROR", {
79
+ cause: cause?.message,
80
+ });
81
+ }
82
+ }
83
+ // Validation errors
84
+ export class ValidationError extends ProjitiveError {
85
+ errors;
86
+ constructor(message, errors = [], code) {
87
+ super(message, code || "VALIDATION_FAILED", { errors });
88
+ this.errors = errors;
89
+ this.name = "ValidationError";
90
+ }
91
+ }
92
+ export class ConfidenceScoreError extends ValidationError {
93
+ score;
94
+ constructor(message, score, errors = []) {
95
+ super(message, errors, "CONFIDENCE_SCORE_ERROR");
96
+ this.score = score;
97
+ this.score = score;
98
+ }
99
+ }
100
+ // MCP related errors
101
+ export class MCPError extends ProjitiveError {
102
+ constructor(message, code, details) {
103
+ super(message, code, details);
104
+ this.name = "MCPError";
105
+ }
106
+ }
107
+ export class ResourceNotFoundError extends MCPError {
108
+ constructor(resourceUri) {
109
+ super(`Resource not found: ${resourceUri}`, "RESOURCE_NOT_FOUND", {
110
+ resourceUri,
111
+ });
112
+ }
113
+ }
114
+ export class PromptNotFoundError extends MCPError {
115
+ constructor(promptName) {
116
+ super(`Prompt not found: ${promptName}`, "PROMPT_NOT_FOUND", {
117
+ promptName,
118
+ });
119
+ }
120
+ }
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { catchIt } from "../catch/index.js";
3
+ import { catchIt } from "./catch.js";
4
4
  const FILE_ARTIFACTS = ["README.md", "roadmap.md", "tasks.md"];
5
5
  const DIRECTORY_ARTIFACTS = ["designs", "reports", "hooks"];
6
6
  async function fileLineCount(filePath) {
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { afterEach, describe, expect, it } from "vitest";
5
- import { discoverGovernanceArtifacts } from "./files.js";
5
+ import { discoverGovernanceArtifacts } from './files.js';
6
6
  const tempPaths = [];
7
7
  async function createTempDir() {
8
8
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), "projitive-mcp-test-"));