@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.
- package/README.md +3 -3
- package/output/package.json +4 -1
- package/output/source/{helpers/catch → common}/catch.js +6 -6
- package/output/source/{helpers/catch → common}/catch.test.js +6 -6
- package/output/source/common/confidence.js +231 -0
- package/output/source/common/confidence.test.js +205 -0
- package/output/source/common/errors.js +120 -0
- package/output/source/{helpers/files → common}/files.js +1 -1
- package/output/source/{helpers/files → common}/files.test.js +1 -1
- package/output/source/common/index.js +10 -0
- package/output/source/{helpers/linter/codes.js → common/linter.js} +13 -0
- package/output/source/{helpers/markdown → common}/markdown.test.js +1 -1
- package/output/source/{helpers/response → common}/response.test.js +1 -1
- package/output/source/common/types.js +7 -0
- package/output/source/common/utils.js +39 -0
- package/output/source/design-context.js +51 -500
- package/output/source/index.js +8 -193
- package/output/source/index.test.js +116 -0
- package/output/source/prompts/index.js +9 -0
- package/output/source/prompts/quickStart.js +94 -0
- package/output/source/prompts/taskDiscovery.js +190 -0
- package/output/source/prompts/taskExecution.js +161 -0
- package/output/source/resources/designs.js +108 -0
- package/output/source/resources/designs.test.js +154 -0
- package/output/source/resources/governance.js +40 -0
- package/output/source/resources/index.js +6 -0
- package/output/source/resources/readme.test.js +167 -0
- package/output/source/{reports.js → resources/reports.js} +5 -3
- package/output/source/resources/reports.test.js +149 -0
- package/output/source/tools/index.js +8 -0
- package/output/source/{projitive.js → tools/project.js} +6 -9
- package/output/source/tools/project.test.js +322 -0
- package/output/source/{roadmap.js → tools/roadmap.js} +4 -7
- package/output/source/tools/roadmap.test.js +103 -0
- package/output/source/{tasks.js → tools/task.js} +581 -27
- package/output/source/tools/task.test.js +473 -0
- package/output/source/types.js +67 -0
- package/package.json +4 -1
- package/output/source/designs.js +0 -38
- package/output/source/helpers/artifacts/index.js +0 -1
- package/output/source/helpers/catch/index.js +0 -1
- package/output/source/helpers/files/index.js +0 -1
- package/output/source/helpers/index.js +0 -6
- package/output/source/helpers/linter/index.js +0 -2
- package/output/source/helpers/linter/linter.js +0 -6
- package/output/source/helpers/markdown/index.js +0 -1
- package/output/source/helpers/response/index.js +0 -1
- package/output/source/projitive.test.js +0 -111
- package/output/source/roadmap.test.js +0 -11
- package/output/source/tasks.test.js +0 -152
- /package/output/source/{helpers/artifacts → common}/artifacts.js +0 -0
- /package/output/source/{helpers/artifacts → common}/artifacts.test.js +0 -0
- /package/output/source/{helpers/linter → common}/linter.test.js +0 -0
- /package/output/source/{helpers/markdown → common}/markdown.js +0 -0
- /package/output/source/{helpers/response → common}/response.js +0 -0
- /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.
|
|
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**: `
|
|
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**: `
|
|
297
|
+
- **Input**: `limit?`
|
|
298
298
|
- **Output Example (Markdown)**:
|
|
299
299
|
|
|
300
300
|
```markdown
|
package/output/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@projitive/mcp",
|
|
3
|
-
"version": "1.
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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
|
|
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
|
|
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
|
+
});
|