@projitive/mcp 1.1.1 → 1.2.0
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 +4 -2
- package/output/package.json +1 -1
- package/output/source/common/index.js +0 -1
- package/output/source/index.js +3 -6
- package/output/source/index.test.js +0 -6
- package/output/source/tools/project.js +48 -8
- package/output/source/tools/project.test.js +27 -8
- package/output/source/tools/task.js +8 -114
- package/output/source/types.js +0 -11
- package/package.json +1 -1
- package/output/source/common/confidence.js +0 -231
- package/output/source/common/confidence.test.js +0 -205
- package/output/source/design-context.js +0 -66
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ MCP client config example (`mcp.json`):
|
|
|
84
84
|
"command": "npx",
|
|
85
85
|
"args": ["-y", "@projitive/mcp"],
|
|
86
86
|
"env": {
|
|
87
|
-
"
|
|
87
|
+
"PROJITIVE_SCAN_ROOT_PATHS": "/workspace/a:/workspace/b",
|
|
88
88
|
"PROJITIVE_SCAN_MAX_DEPTH": "3"
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -94,7 +94,9 @@ MCP client config example (`mcp.json`):
|
|
|
94
94
|
|
|
95
95
|
Environment variables (required):
|
|
96
96
|
|
|
97
|
-
- `
|
|
97
|
+
- `PROJITIVE_SCAN_ROOT_PATHS`: required scan roots for discovery methods.
|
|
98
|
+
- Use platform-delimiter string (`:` on Linux/macOS, `;` on Windows), e.g. `/workspace/a:/workspace/b`.
|
|
99
|
+
- Fallback: if not set, legacy `PROJITIVE_SCAN_ROOT_PATH` is used.
|
|
98
100
|
- `PROJITIVE_SCAN_MAX_DEPTH`: required scan depth for discovery methods (integer `0-8`).
|
|
99
101
|
|
|
100
102
|
Local path startup is not the recommended usage mode in this README.
|
package/output/package.json
CHANGED
package/output/source/index.js
CHANGED
|
@@ -6,9 +6,8 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
6
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
7
|
import packageJson from "../package.json" with { type: "json" };
|
|
8
8
|
import { registerTools } from "./tools/index.js";
|
|
9
|
-
import { registerResources } from "./resources/index.js";
|
|
10
9
|
import { registerPrompts } from "./prompts/index.js";
|
|
11
|
-
import {
|
|
10
|
+
import { registerResources } from "./resources/index.js";
|
|
12
11
|
const PROJITIVE_SPEC_VERSION = "1.1.0";
|
|
13
12
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
14
13
|
const sourceDir = path.dirname(currentFilePath);
|
|
@@ -21,12 +20,10 @@ const server = new McpServer({
|
|
|
21
20
|
version: MCP_RUNTIME_VERSION,
|
|
22
21
|
description: "Semantic Projitive MCP for project/task discovery and agent guidance with markdown-first outputs",
|
|
23
22
|
});
|
|
24
|
-
//
|
|
23
|
+
// 注册所有模块
|
|
25
24
|
registerTools(server);
|
|
26
|
-
registerResources(server, repoRoot);
|
|
27
25
|
registerPrompts(server);
|
|
28
|
-
|
|
29
|
-
registerDesignContextPrompts(server);
|
|
26
|
+
registerResources(server, repoRoot);
|
|
30
27
|
async function main() {
|
|
31
28
|
console.error(`[projitive-mcp] starting server`);
|
|
32
29
|
console.error(`[projitive-mcp] version=${MCP_RUNTIME_VERSION} spec=${PROJITIVE_SPEC_VERSION} transport=stdio pid=${process.pid}`);
|
|
@@ -26,10 +26,6 @@ vi.mock("./tools/task.js", () => ({
|
|
|
26
26
|
vi.mock("./tools/roadmap.js", () => ({
|
|
27
27
|
registerRoadmapTools: vi.fn(),
|
|
28
28
|
}));
|
|
29
|
-
vi.mock("./design-context.js", () => ({
|
|
30
|
-
registerDesignContextResources: vi.fn(),
|
|
31
|
-
registerDesignContextPrompts: vi.fn(),
|
|
32
|
-
}));
|
|
33
29
|
vi.mock("./resources/governance.js", () => ({
|
|
34
30
|
registerGovernanceResources: vi.fn(),
|
|
35
31
|
}));
|
|
@@ -87,8 +83,6 @@ describe("index module", () => {
|
|
|
87
83
|
expect(content).toContain("registerTools(server)");
|
|
88
84
|
expect(content).toContain("registerResources(server, repoRoot)");
|
|
89
85
|
expect(content).toContain("registerPrompts(server)");
|
|
90
|
-
expect(content).toContain("registerDesignContextResources(server)");
|
|
91
|
-
expect(content).toContain("registerDesignContextPrompts(server)");
|
|
92
86
|
});
|
|
93
87
|
it("should have proper error handling in main", async () => {
|
|
94
88
|
const indexPath = path.join(sourceDir, "index.ts");
|
|
@@ -46,9 +46,43 @@ function requireEnvVar(name) {
|
|
|
46
46
|
}
|
|
47
47
|
return value.trim();
|
|
48
48
|
}
|
|
49
|
+
function normalizeScanRoots(rootPaths) {
|
|
50
|
+
const normalized = rootPaths
|
|
51
|
+
.map((entry) => entry.trim())
|
|
52
|
+
.filter((entry) => entry.length > 0)
|
|
53
|
+
.map((entry) => normalizePath(entry));
|
|
54
|
+
return Array.from(new Set(normalized));
|
|
55
|
+
}
|
|
56
|
+
function parseScanRoots(rawValue) {
|
|
57
|
+
const trimmed = rawValue.trim();
|
|
58
|
+
if (trimmed.length === 0) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
return trimmed.split(path.delimiter);
|
|
62
|
+
}
|
|
63
|
+
export function resolveScanRoots(inputPaths) {
|
|
64
|
+
const normalizedInputPaths = normalizeScanRoots(inputPaths ?? []);
|
|
65
|
+
if (normalizedInputPaths.length > 0) {
|
|
66
|
+
return normalizedInputPaths;
|
|
67
|
+
}
|
|
68
|
+
const configuredRoots = process.env.PROJITIVE_SCAN_ROOT_PATHS;
|
|
69
|
+
const rootsFromMultiEnv = typeof configuredRoots === "string"
|
|
70
|
+
? normalizeScanRoots(parseScanRoots(configuredRoots))
|
|
71
|
+
: [];
|
|
72
|
+
if (rootsFromMultiEnv.length > 0) {
|
|
73
|
+
return rootsFromMultiEnv;
|
|
74
|
+
}
|
|
75
|
+
const legacyRoot = process.env.PROJITIVE_SCAN_ROOT_PATH;
|
|
76
|
+
const rootsFromLegacyEnv = typeof legacyRoot === "string"
|
|
77
|
+
? normalizeScanRoots([legacyRoot])
|
|
78
|
+
: [];
|
|
79
|
+
if (rootsFromLegacyEnv.length > 0) {
|
|
80
|
+
return rootsFromLegacyEnv;
|
|
81
|
+
}
|
|
82
|
+
throw new Error("Missing required environment variable: PROJITIVE_SCAN_ROOT_PATHS (or legacy PROJITIVE_SCAN_ROOT_PATH)");
|
|
83
|
+
}
|
|
49
84
|
export function resolveScanRoot(inputPath) {
|
|
50
|
-
|
|
51
|
-
return normalizePath(inputPath ?? configuredRoot);
|
|
85
|
+
return resolveScanRoots(inputPath ? [inputPath] : undefined)[0];
|
|
52
86
|
}
|
|
53
87
|
export function resolveScanDepth(inputDepth) {
|
|
54
88
|
const configuredDepthRaw = requireEnvVar("PROJITIVE_SCAN_MAX_DEPTH");
|
|
@@ -210,6 +244,10 @@ export async function discoverProjects(rootPath, maxDepth) {
|
|
|
210
244
|
await walk(rootPath, 0);
|
|
211
245
|
return Array.from(new Set(results)).sort();
|
|
212
246
|
}
|
|
247
|
+
export async function discoverProjectsAcrossRoots(rootPaths, maxDepth) {
|
|
248
|
+
const perRootResults = await Promise.all(rootPaths.map((rootPath) => discoverProjects(rootPath, maxDepth)));
|
|
249
|
+
return Array.from(new Set(perRootResults.flat())).sort();
|
|
250
|
+
}
|
|
213
251
|
async function pathExists(targetPath) {
|
|
214
252
|
const accessResult = await catchIt(fs.access(targetPath));
|
|
215
253
|
return !accessResult.isError();
|
|
@@ -362,14 +400,15 @@ export function registerProjectTools(server) {
|
|
|
362
400
|
description: "Start here when project path is unknown; discover all governance roots",
|
|
363
401
|
inputSchema: {},
|
|
364
402
|
}, async () => {
|
|
365
|
-
const
|
|
403
|
+
const roots = resolveScanRoots();
|
|
366
404
|
const depth = resolveScanDepth();
|
|
367
|
-
const projects = await
|
|
405
|
+
const projects = await discoverProjectsAcrossRoots(roots, depth);
|
|
368
406
|
const markdown = renderToolResponseMarkdown({
|
|
369
407
|
toolName: "projectScan",
|
|
370
408
|
sections: [
|
|
371
409
|
summarySection([
|
|
372
|
-
`-
|
|
410
|
+
`- rootPaths: ${roots.join(", ")}`,
|
|
411
|
+
`- rootCount: ${roots.length}`,
|
|
373
412
|
`- maxDepth: ${depth}`,
|
|
374
413
|
`- discoveredCount: ${projects.length}`,
|
|
375
414
|
]),
|
|
@@ -398,9 +437,9 @@ export function registerProjectTools(server) {
|
|
|
398
437
|
limit: z.number().int().min(1).max(50).optional(),
|
|
399
438
|
},
|
|
400
439
|
}, async ({ limit }) => {
|
|
401
|
-
const
|
|
440
|
+
const roots = resolveScanRoots();
|
|
402
441
|
const depth = resolveScanDepth();
|
|
403
|
-
const projects = await
|
|
442
|
+
const projects = await discoverProjectsAcrossRoots(roots, depth);
|
|
404
443
|
const snapshots = await Promise.all(projects.map(async (governanceDir) => {
|
|
405
444
|
const snapshot = await readTasksSnapshot(governanceDir);
|
|
406
445
|
const inProgress = snapshot.tasks.filter((task) => task.status === "IN_PROGRESS").length;
|
|
@@ -435,7 +474,8 @@ export function registerProjectTools(server) {
|
|
|
435
474
|
toolName: "projectNext",
|
|
436
475
|
sections: [
|
|
437
476
|
summarySection([
|
|
438
|
-
`-
|
|
477
|
+
`- rootPaths: ${roots.join(", ")}`,
|
|
478
|
+
`- rootCount: ${roots.length}`,
|
|
439
479
|
`- maxDepth: ${depth}`,
|
|
440
480
|
`- matchedProjects: ${projects.length}`,
|
|
441
481
|
`- actionableProjects: ${ranked.length}`,
|
|
@@ -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, vi } from "vitest";
|
|
5
|
-
import { discoverProjects, hasProjectMarker, initializeProjectStructure, resolveGovernanceDir,
|
|
5
|
+
import { discoverProjects, discoverProjectsAcrossRoots, hasProjectMarker, initializeProjectStructure, resolveGovernanceDir, resolveScanRoots, resolveScanDepth, toProjectPath, registerProjectTools } from "./project.js";
|
|
6
6
|
const tempPaths = [];
|
|
7
7
|
async function createTempDir() {
|
|
8
8
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "projitive-mcp-test-"));
|
|
@@ -186,6 +186,15 @@ describe("projitive module", () => {
|
|
|
186
186
|
const projects = await discoverProjects(root, 3);
|
|
187
187
|
expect(projects).toEqual([]);
|
|
188
188
|
});
|
|
189
|
+
it("ignores non-existent roots when scanning across multiple roots", async () => {
|
|
190
|
+
const validRoot = await createTempDir();
|
|
191
|
+
const validProject = path.join(validRoot, "project-a");
|
|
192
|
+
const missingRoot = path.join(validRoot, "__missing_root__");
|
|
193
|
+
await fs.mkdir(validProject, { recursive: true });
|
|
194
|
+
await fs.writeFile(path.join(validProject, ".projitive"), "", "utf-8");
|
|
195
|
+
const projects = await discoverProjectsAcrossRoots([missingRoot, validRoot], 3);
|
|
196
|
+
expect(projects).toContain(validProject);
|
|
197
|
+
});
|
|
189
198
|
});
|
|
190
199
|
describe("initializeProjectStructure", () => {
|
|
191
200
|
it("initializes governance structure under default .projitive directory", async () => {
|
|
@@ -267,20 +276,30 @@ describe("projitive module", () => {
|
|
|
267
276
|
expect(toProjectPath("/a/b/c")).toBe("/a/b");
|
|
268
277
|
});
|
|
269
278
|
});
|
|
270
|
-
describe("
|
|
271
|
-
it("uses environment variable when no
|
|
279
|
+
describe("resolveScanRoots", () => {
|
|
280
|
+
it("uses legacy environment variable when no multi-root env is provided", () => {
|
|
272
281
|
vi.stubEnv("PROJITIVE_SCAN_ROOT_PATH", "/test/root");
|
|
273
|
-
expect(
|
|
282
|
+
expect(resolveScanRoots()).toEqual(["/test/root"]);
|
|
274
283
|
vi.unstubAllEnvs();
|
|
275
284
|
});
|
|
276
|
-
it("uses input
|
|
285
|
+
it("uses input paths when provided", () => {
|
|
277
286
|
vi.stubEnv("PROJITIVE_SCAN_ROOT_PATH", "/test/root");
|
|
278
|
-
expect(
|
|
287
|
+
expect(resolveScanRoots(["/custom/path", " /custom/path ", "/second/path"])).toEqual(["/custom/path", "/second/path"]);
|
|
288
|
+
vi.unstubAllEnvs();
|
|
289
|
+
});
|
|
290
|
+
it("uses PROJITIVE_SCAN_ROOT_PATHS with platform delimiter", () => {
|
|
291
|
+
vi.stubEnv("PROJITIVE_SCAN_ROOT_PATHS", ["/root/a", "/root/b", "", " /root/c "].join(path.delimiter));
|
|
292
|
+
expect(resolveScanRoots()).toEqual(["/root/a", "/root/b", "/root/c"]);
|
|
293
|
+
vi.unstubAllEnvs();
|
|
294
|
+
});
|
|
295
|
+
it("treats JSON-like string as plain delimiter input", () => {
|
|
296
|
+
vi.stubEnv("PROJITIVE_SCAN_ROOT_PATHS", JSON.stringify(["/json/a", "/json/b"]));
|
|
297
|
+
expect(resolveScanRoots()).toHaveLength(1);
|
|
279
298
|
vi.unstubAllEnvs();
|
|
280
299
|
});
|
|
281
|
-
it("throws error when
|
|
300
|
+
it("throws error when no root environment variables are configured", () => {
|
|
282
301
|
vi.unstubAllEnvs();
|
|
283
|
-
expect(() =>
|
|
302
|
+
expect(() => resolveScanRoots()).toThrow("Missing required environment variable: PROJITIVE_SCAN_ROOT_PATHS");
|
|
284
303
|
});
|
|
285
304
|
});
|
|
286
305
|
describe("resolveScanDepth", () => {
|
|
@@ -4,10 +4,9 @@ import { z } from "zod";
|
|
|
4
4
|
import { candidateFilesFromArtifacts, discoverGovernanceArtifacts, findTextReferences } from "../common/index.js";
|
|
5
5
|
import { asText, evidenceSection, guidanceSection, lintSection, nextCallSection, renderErrorMarkdown, renderToolResponseMarkdown, summarySection, } from "../common/index.js";
|
|
6
6
|
import { catchIt, TASK_LINT_CODES, renderLintSuggestions } from "../common/index.js";
|
|
7
|
-
import { resolveGovernanceDir, resolveScanDepth,
|
|
7
|
+
import { resolveGovernanceDir, resolveScanDepth, resolveScanRoots, discoverProjectsAcrossRoots, toProjectPath } from "./project.js";
|
|
8
8
|
import { isValidRoadmapId } from "./roadmap.js";
|
|
9
9
|
import { SUB_STATE_PHASES, BLOCKER_TYPES } from "../types.js";
|
|
10
|
-
import { calculateConfidenceScore, calculateContextCompleteness, calculateSimilarTaskHistory, calculateSpecificationClarity, getOrCreateTaskAutoCreateValidationHook, runPreCreationValidation, generateConfidenceReport } from "../common/index.js";
|
|
11
10
|
export const TASKS_START = "<!-- PROJITIVE:TASKS:START -->";
|
|
12
11
|
export const TASKS_END = "<!-- PROJITIVE:TASKS:END -->";
|
|
13
12
|
export const ALLOWED_STATUS = ["TODO", "IN_PROGRESS", "BLOCKED", "DONE"];
|
|
@@ -51,7 +50,7 @@ const NO_TASK_DISCOVERY_HOOK_FILE = "task_no_actionable.md";
|
|
|
51
50
|
const DEFAULT_NO_TASK_DISCOVERY_GUIDANCE = [
|
|
52
51
|
"- Check whether current code violates project guide/spec conventions; create TODO tasks for each actionable gap.",
|
|
53
52
|
"- Check unit/integration test coverage and identify high-value missing tests; create TODO tasks for meaningful coverage improvements.",
|
|
54
|
-
"- Check development/testing workflow for bottlenecks (slow feedback, fragile scripts, unclear runbooks); create
|
|
53
|
+
"- Check development/testing workflow for bottlenecks (slow feedback, fragile scripts, unclear runbooks); create tasks to improve reliability.",
|
|
55
54
|
"- Scan for TODO/FIXME/HACK comments and convert feasible items into governed TODO tasks with evidence links.",
|
|
56
55
|
"- Check dependency freshness and security advisories; create tasks for safe upgrades when needed.",
|
|
57
56
|
"- Check repeated manual operations that can be automated (lint/test/release checks); create tasks to reduce operational toil.",
|
|
@@ -821,9 +820,9 @@ export function registerTaskTools(server) {
|
|
|
821
820
|
limit: z.number().int().min(1).max(20).optional(),
|
|
822
821
|
},
|
|
823
822
|
}, async ({ limit }) => {
|
|
824
|
-
const
|
|
823
|
+
const roots = resolveScanRoots();
|
|
825
824
|
const depth = resolveScanDepth();
|
|
826
|
-
const projects = await
|
|
825
|
+
const projects = await discoverProjectsAcrossRoots(roots, depth);
|
|
827
826
|
const rankedCandidates = rankActionableTaskCandidates(await readActionableTaskCandidates(projects));
|
|
828
827
|
if (rankedCandidates.length === 0) {
|
|
829
828
|
const projectSnapshots = await Promise.all(projects.map(async (governanceDir) => {
|
|
@@ -851,7 +850,8 @@ export function registerTaskTools(server) {
|
|
|
851
850
|
toolName: "taskNext",
|
|
852
851
|
sections: [
|
|
853
852
|
summarySection([
|
|
854
|
-
`-
|
|
853
|
+
`- rootPaths: ${roots.join(", ")}`,
|
|
854
|
+
`- rootCount: ${roots.length}`,
|
|
855
855
|
`- maxDepth: ${depth}`,
|
|
856
856
|
`- matchedProjects: ${projects.length}`,
|
|
857
857
|
"- actionableTasks: 0",
|
|
@@ -901,7 +901,8 @@ export function registerTaskTools(server) {
|
|
|
901
901
|
toolName: "taskNext",
|
|
902
902
|
sections: [
|
|
903
903
|
summarySection([
|
|
904
|
-
`-
|
|
904
|
+
`- rootPaths: ${roots.join(", ")}`,
|
|
905
|
+
`- rootCount: ${roots.length}`,
|
|
905
906
|
`- maxDepth: ${depth}`,
|
|
906
907
|
`- matchedProjects: ${projects.length}`,
|
|
907
908
|
`- actionableTasks: ${rankedCandidates.length}`,
|
|
@@ -1205,111 +1206,4 @@ export function registerTaskTools(server) {
|
|
|
1205
1206
|
});
|
|
1206
1207
|
return asText(markdown);
|
|
1207
1208
|
});
|
|
1208
|
-
// ============================================================================
|
|
1209
|
-
// Spec v1.1.0 - Confidence Scoring Tools
|
|
1210
|
-
// ============================================================================
|
|
1211
|
-
server.registerTool("taskCalculateConfidence", {
|
|
1212
|
-
title: "Calculate Task Confidence",
|
|
1213
|
-
description: "Calculate confidence score for auto-creating a new task (Spec v1.1.0)",
|
|
1214
|
-
inputSchema: {
|
|
1215
|
-
projectPath: z.string(),
|
|
1216
|
-
candidateTaskSummary: z.string(),
|
|
1217
|
-
contextCompleteness: z.number().min(0).max(1).optional(),
|
|
1218
|
-
similarTaskHistory: z.number().min(0).max(1).optional(),
|
|
1219
|
-
specificationClarity: z.number().min(0).max(1).optional(),
|
|
1220
|
-
},
|
|
1221
|
-
}, async ({ projectPath, candidateTaskSummary, contextCompleteness, similarTaskHistory, specificationClarity }) => {
|
|
1222
|
-
const governanceDir = await resolveGovernanceDir(projectPath);
|
|
1223
|
-
const { tasks } = await loadTasks(governanceDir);
|
|
1224
|
-
// Calculate factors if not provided
|
|
1225
|
-
const calculatedContextCompleteness = contextCompleteness ?? await calculateContextCompleteness(governanceDir);
|
|
1226
|
-
const calculatedSimilarTaskHistory = similarTaskHistory ?? calculateSimilarTaskHistory(tasks, candidateTaskSummary);
|
|
1227
|
-
const calculatedSpecificationClarity = specificationClarity ?? calculateSpecificationClarity({
|
|
1228
|
-
hasRoadmap: true, // Assume roadmap exists for simplicity
|
|
1229
|
-
hasDesignDocs: true,
|
|
1230
|
-
hasClearAcceptanceCriteria: candidateTaskSummary.length > 50,
|
|
1231
|
-
});
|
|
1232
|
-
const factors = {
|
|
1233
|
-
contextCompleteness: calculatedContextCompleteness,
|
|
1234
|
-
similarTaskHistory: calculatedSimilarTaskHistory,
|
|
1235
|
-
specificationClarity: calculatedSpecificationClarity,
|
|
1236
|
-
};
|
|
1237
|
-
const confidenceScore = calculateConfidenceScore(factors);
|
|
1238
|
-
const validationResult = await runPreCreationValidation(governanceDir, confidenceScore);
|
|
1239
|
-
const hookContent = await getOrCreateTaskAutoCreateValidationHook(governanceDir);
|
|
1240
|
-
const markdown = renderToolResponseMarkdown({
|
|
1241
|
-
toolName: "taskCalculateConfidence",
|
|
1242
|
-
sections: [
|
|
1243
|
-
summarySection([
|
|
1244
|
-
`- governanceDir: ${governanceDir}`,
|
|
1245
|
-
`- confidenceScore: ${(confidenceScore.score * 100).toFixed(0)}%`,
|
|
1246
|
-
`- recommendation: ${confidenceScore.recommendation}`,
|
|
1247
|
-
`- validationPassed: ${validationResult.passed}`,
|
|
1248
|
-
]),
|
|
1249
|
-
evidenceSection([
|
|
1250
|
-
"### Confidence Report",
|
|
1251
|
-
...generateConfidenceReport(confidenceScore).split("\n"),
|
|
1252
|
-
"",
|
|
1253
|
-
"### Validation Issues",
|
|
1254
|
-
...(validationResult.issues.length > 0
|
|
1255
|
-
? validationResult.issues.map(issue => `- ${issue}`)
|
|
1256
|
-
: ["- (none)"]),
|
|
1257
|
-
"",
|
|
1258
|
-
"### Validation Hook",
|
|
1259
|
-
"- hook created/verified at: hooks/task_auto_create_validation.md",
|
|
1260
|
-
]),
|
|
1261
|
-
guidanceSection([
|
|
1262
|
-
confidenceScore.recommendation === "auto_create"
|
|
1263
|
-
? "✅ Confidence is high - you can auto-create this task."
|
|
1264
|
-
: confidenceScore.recommendation === "review_required"
|
|
1265
|
-
? "⚠️ Confidence is medium - review recommended before creating."
|
|
1266
|
-
: "❌ Confidence is low - do not auto-create this task.",
|
|
1267
|
-
"",
|
|
1268
|
-
"### Next Steps",
|
|
1269
|
-
"- If recommendation is auto_create: use taskUpdate or manually add the task",
|
|
1270
|
-
"- If review_required: review the factors and improve context before creating",
|
|
1271
|
-
"- If do_not_create: gather more requirements or context before attempting",
|
|
1272
|
-
]),
|
|
1273
|
-
lintSection([]),
|
|
1274
|
-
nextCallSection(confidenceScore.recommendation !== "do_not_create"
|
|
1275
|
-
? `projectContext(projectPath=\"${toProjectPath(governanceDir)}\")`
|
|
1276
|
-
: undefined),
|
|
1277
|
-
],
|
|
1278
|
-
});
|
|
1279
|
-
return asText(markdown);
|
|
1280
|
-
});
|
|
1281
|
-
server.registerTool("taskCreateValidationHook", {
|
|
1282
|
-
title: "Create Validation Hook",
|
|
1283
|
-
description: "Create or update the task auto-create validation hook (Spec v1.1.0)",
|
|
1284
|
-
inputSchema: {
|
|
1285
|
-
projectPath: z.string(),
|
|
1286
|
-
},
|
|
1287
|
-
}, async ({ projectPath }) => {
|
|
1288
|
-
const governanceDir = await resolveGovernanceDir(projectPath);
|
|
1289
|
-
const hookContent = await getOrCreateTaskAutoCreateValidationHook(governanceDir);
|
|
1290
|
-
const markdown = renderToolResponseMarkdown({
|
|
1291
|
-
toolName: "taskCreateValidationHook",
|
|
1292
|
-
sections: [
|
|
1293
|
-
summarySection([
|
|
1294
|
-
`- governanceDir: ${governanceDir}`,
|
|
1295
|
-
`- hookPath: ${governanceDir}/hooks/task_auto_create_validation.md`,
|
|
1296
|
-
`- status: created/verified`,
|
|
1297
|
-
]),
|
|
1298
|
-
evidenceSection([
|
|
1299
|
-
"### Hook Content",
|
|
1300
|
-
"```markdown",
|
|
1301
|
-
...hookContent.split("\n"),
|
|
1302
|
-
"```",
|
|
1303
|
-
]),
|
|
1304
|
-
guidanceSection([
|
|
1305
|
-
"Validation hook created successfully.",
|
|
1306
|
-
"Edit the hook file to customize pre-creation and post-creation actions.",
|
|
1307
|
-
"The hook will be used by taskCalculateConfidence for validation.",
|
|
1308
|
-
]),
|
|
1309
|
-
lintSection([]),
|
|
1310
|
-
nextCallSection(`taskCalculateConfidence(projectPath=\"${toProjectPath(governanceDir)}\", candidateTaskSummary=\"Your task summary here\")`),
|
|
1311
|
-
],
|
|
1312
|
-
});
|
|
1313
|
-
return asText(markdown);
|
|
1314
|
-
});
|
|
1315
1209
|
}
|
package/output/source/types.js
CHANGED
|
@@ -26,17 +26,6 @@ export const BLOCKER_TYPES = [
|
|
|
26
26
|
"resource",
|
|
27
27
|
"approval"
|
|
28
28
|
];
|
|
29
|
-
// Weight factors for confidence calculation
|
|
30
|
-
export const CONFIDENCE_WEIGHTS = {
|
|
31
|
-
contextCompleteness: 0.4,
|
|
32
|
-
similarTaskHistory: 0.3,
|
|
33
|
-
specificationClarity: 0.3,
|
|
34
|
-
};
|
|
35
|
-
// Confidence thresholds
|
|
36
|
-
export const CONFIDENCE_THRESHOLDS = {
|
|
37
|
-
autoCreate: 0.85,
|
|
38
|
-
reviewRequired: 0.6,
|
|
39
|
-
};
|
|
40
29
|
// ============================================================================
|
|
41
30
|
// Parser Constants
|
|
42
31
|
// ============================================================================
|
package/package.json
CHANGED
|
@@ -1,231 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,205 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
function asUserPrompt(text) {
|
|
3
|
-
return {
|
|
4
|
-
messages: [
|
|
5
|
-
{
|
|
6
|
-
role: "user",
|
|
7
|
-
content: {
|
|
8
|
-
type: "text",
|
|
9
|
-
text,
|
|
10
|
-
},
|
|
11
|
-
},
|
|
12
|
-
],
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
export function registerDesignContextResources(server) {
|
|
16
|
-
// Register design context resources
|
|
17
|
-
server.registerResource("designContext", "projitive://design-context/designs", {
|
|
18
|
-
title: "Design Context",
|
|
19
|
-
description: "Design context and resources for the project",
|
|
20
|
-
mimeType: "text/markdown",
|
|
21
|
-
}, async () => ({
|
|
22
|
-
contents: [
|
|
23
|
-
{
|
|
24
|
-
uri: "projitive://design-context/designs",
|
|
25
|
-
text: `# Design Context\n\nThis is the design context for the project.`,
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
}));
|
|
29
|
-
}
|
|
30
|
-
export function registerDesignContextPrompts(server) {
|
|
31
|
-
// Register design context prompts
|
|
32
|
-
server.registerPrompt("designContext", {
|
|
33
|
-
title: "Design Context",
|
|
34
|
-
description: "Get design context for the project",
|
|
35
|
-
argsSchema: {
|
|
36
|
-
projectPath: z.string().optional(),
|
|
37
|
-
},
|
|
38
|
-
}, async ({ projectPath }) => {
|
|
39
|
-
const text = [
|
|
40
|
-
"You are exploring the design context for the project.",
|
|
41
|
-
"",
|
|
42
|
-
projectPath
|
|
43
|
-
? [
|
|
44
|
-
`1) Project path is known: "${projectPath}"`,
|
|
45
|
-
"2) Review the design context resources.",
|
|
46
|
-
"3) Identify design patterns and guidelines.",
|
|
47
|
-
].join("\n")
|
|
48
|
-
: [
|
|
49
|
-
"1) Project path is unknown: Run projectScan() to discover all governance roots.",
|
|
50
|
-
"2) Select a project to work on.",
|
|
51
|
-
"3) Run projectContext() to load project summary.",
|
|
52
|
-
"4) Review the design context resources.",
|
|
53
|
-
].join("\n"),
|
|
54
|
-
"",
|
|
55
|
-
"Key resources to read:",
|
|
56
|
-
"- projitive://design-context/designs - Design context and resources",
|
|
57
|
-
"- projitive://governance/workspace - Project overview",
|
|
58
|
-
"- projitive://governance/roadmap - Project roadmap",
|
|
59
|
-
"",
|
|
60
|
-
"Hard rules:",
|
|
61
|
-
"- Follow the design patterns and guidelines.",
|
|
62
|
-
"- Keep design documents in the .projitive/designs/ directory.",
|
|
63
|
-
].join("\n");
|
|
64
|
-
return asUserPrompt(text);
|
|
65
|
-
});
|
|
66
|
-
}
|