@projitive/mcp 1.0.7 → 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 (56) hide show
  1. package/README.md +3 -3
  2. package/output/package.json +4 -1
  3. package/output/source/{helpers/catch → common}/catch.js +6 -6
  4. package/output/source/{helpers/catch → common}/catch.test.js +6 -6
  5. package/output/source/common/confidence.js +231 -0
  6. package/output/source/common/confidence.test.js +205 -0
  7. package/output/source/common/errors.js +120 -0
  8. package/output/source/{helpers/files → common}/files.js +1 -1
  9. package/output/source/{helpers/files → common}/files.test.js +1 -1
  10. package/output/source/common/index.js +10 -0
  11. package/output/source/{helpers/linter/codes.js → common/linter.js} +13 -0
  12. package/output/source/{helpers/markdown → common}/markdown.test.js +1 -1
  13. package/output/source/{helpers/response → common}/response.test.js +1 -1
  14. package/output/source/common/types.js +7 -0
  15. package/output/source/common/utils.js +39 -0
  16. package/output/source/design-context.js +51 -500
  17. package/output/source/index.js +8 -193
  18. package/output/source/index.test.js +116 -0
  19. package/output/source/prompts/index.js +9 -0
  20. package/output/source/prompts/quickStart.js +94 -0
  21. package/output/source/prompts/taskDiscovery.js +190 -0
  22. package/output/source/prompts/taskExecution.js +161 -0
  23. package/output/source/resources/designs.js +108 -0
  24. package/output/source/resources/designs.test.js +154 -0
  25. package/output/source/resources/governance.js +40 -0
  26. package/output/source/resources/index.js +6 -0
  27. package/output/source/resources/readme.test.js +167 -0
  28. package/output/source/{reports.js → resources/reports.js} +5 -3
  29. package/output/source/resources/reports.test.js +149 -0
  30. package/output/source/tools/index.js +8 -0
  31. package/output/source/{projitive.js → tools/project.js} +6 -9
  32. package/output/source/tools/project.test.js +322 -0
  33. package/output/source/{roadmap.js → tools/roadmap.js} +4 -7
  34. package/output/source/tools/roadmap.test.js +103 -0
  35. package/output/source/{tasks.js → tools/task.js} +581 -27
  36. package/output/source/tools/task.test.js +473 -0
  37. package/output/source/types.js +67 -0
  38. package/package.json +4 -1
  39. package/output/source/designs.js +0 -38
  40. package/output/source/helpers/artifacts/index.js +0 -1
  41. package/output/source/helpers/catch/index.js +0 -1
  42. package/output/source/helpers/files/index.js +0 -1
  43. package/output/source/helpers/index.js +0 -6
  44. package/output/source/helpers/linter/index.js +0 -2
  45. package/output/source/helpers/linter/linter.js +0 -6
  46. package/output/source/helpers/markdown/index.js +0 -1
  47. package/output/source/helpers/response/index.js +0 -1
  48. package/output/source/projitive.test.js +0 -111
  49. package/output/source/roadmap.test.js +0 -11
  50. package/output/source/tasks.test.js +0 -152
  51. /package/output/source/{helpers/artifacts → common}/artifacts.js +0 -0
  52. /package/output/source/{helpers/artifacts → common}/artifacts.test.js +0 -0
  53. /package/output/source/{helpers/linter → common}/linter.test.js +0 -0
  54. /package/output/source/{helpers/markdown → common}/markdown.js +0 -0
  55. /package/output/source/{helpers/response → common}/response.js +0 -0
  56. /package/output/source/{readme.js → resources/readme.js} +0 -0
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.7**
5
+ **Current Spec Version: projitive-spec v1.0.0 | MCP Version: 1.0.8**
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
 
@@ -177,7 +177,7 @@ npm run test
177
177
  #### `projectNext`
178
178
 
179
179
  - **Purpose**: directly list recently actionable projects (ranked by actionable task count and recency).
180
- - **Input**: `maxDepth?`, `limit?`
180
+ - **Input**: `limit?`
181
181
  - **Output Example (Markdown)**:
182
182
 
183
183
  ```markdown
@@ -294,7 +294,7 @@ npm run test
294
294
  #### `taskNext`
295
295
 
296
296
  - **Purpose**: one-step workflow for project discovery + best task selection + evidence/read-order output.
297
- - **Input**: `maxDepth?`, `topCandidates?`
297
+ - **Input**: `limit?`
298
298
  - **Output Example (Markdown)**:
299
299
 
300
300
  ```markdown
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projitive/mcp",
3
- "version": "1.0.7",
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
+ });