@popoverai/dotrequirements 0.26.1 → 0.27.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 +13 -69
- package/dist/cli.js +19 -7
- package/dist/codebase-to-spec/present.js +4 -5
- package/dist/codebase-to-spec/validate.js +3 -2
- package/dist/commands/acceptance-test.js +4 -2
- package/dist/commands/ai-setup.d.ts +8 -2
- package/dist/commands/ai-setup.js +154 -310
- package/dist/commands/get.js +6 -2
- package/dist/commands/init.js +8 -6
- package/dist/commands/link-resolution.d.ts +3 -1
- package/dist/commands/link-resolution.js +4 -2
- package/dist/commands/mcp.d.ts +8 -2
- package/dist/commands/mcp.js +17 -6
- package/dist/commands/pull.js +36 -3
- package/dist/commands/push.js +54 -16
- package/dist/commands/report.js +18 -3
- package/dist/commands/review-test.d.ts +5 -1
- package/dist/commands/review-test.js +117 -15
- package/dist/commands/style-check.d.ts +1 -0
- package/dist/commands/style-check.js +137 -13
- package/dist/commands/tests-for.js +13 -13
- package/dist/commands/validate.js +14 -14
- package/dist/convex.d.ts +1 -3
- package/dist/convex.js +3 -3
- package/dist/harness/cache.d.ts +19 -3
- package/dist/harness/cache.js +38 -12
- package/dist/harness/finalize.js +33 -1
- package/dist/harness/index.js +16 -9
- package/dist/harness/requirementsLoader.js +12 -0
- package/dist/harness/tracking.d.ts +17 -2
- package/dist/harness/tracking.js +83 -9
- package/dist/push/core.d.ts +50 -0
- package/dist/push/core.js +149 -11
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +1 -1
- package/dist/requirements/cloud-ai.d.ts +21 -8
- package/dist/requirements/cloud-ai.js +10 -8
- package/dist/requirements/cloud-coverage.d.ts +12 -2
- package/dist/requirements/cloud-coverage.js +30 -3
- package/dist/requirements/grep.d.ts +7 -2
- package/dist/requirements/grep.js +75 -47
- package/dist/schema/builder.d.ts +1 -1
- package/dist/schema/builder.js +13 -0
- package/dist/schema/conversions.d.ts +7 -2
- package/dist/schema/conversions.js +13 -4
- package/dist/schema/parser-core.d.ts +41 -0
- package/dist/schema/parser-core.js +113 -18
- package/dist/schema/parser.d.ts +8 -26
- package/dist/schema/parser.js +23 -251
- package/dist/schema/resolver.js +18 -8
- package/dist/templates/context-file-section.md +25 -22
- package/dist/utils/context-file.d.ts +7 -3
- package/dist/utils/context-file.js +10 -7
- package/dist/utils/env.js +17 -1
- package/dist/utils/oauth-flow.js +8 -0
- package/dist/utils/project-settings.d.ts +5 -0
- package/dist/utils/project-settings.js +36 -1
- package/package.json +3 -5
- package/dist/mcp/convexClient.d.ts +0 -19
- package/dist/mcp/convexClient.js +0 -24
- package/dist/mcp/handlers/authoring.d.ts +0 -41
- package/dist/mcp/handlers/authoring.js +0 -104
- package/dist/mcp/handlers/debug.d.ts +0 -16
- package/dist/mcp/handlers/debug.js +0 -37
- package/dist/mcp/handlers/get.d.ts +0 -24
- package/dist/mcp/handlers/get.js +0 -65
- package/dist/mcp/handlers/index.d.ts +0 -28
- package/dist/mcp/handlers/index.js +0 -19
- package/dist/mcp/handlers/list.d.ts +0 -7
- package/dist/mcp/handlers/list.js +0 -43
- package/dist/mcp/handlers/push.d.ts +0 -26
- package/dist/mcp/handlers/push.js +0 -186
- package/dist/mcp/handlers/report.d.ts +0 -16
- package/dist/mcp/handlers/report.js +0 -134
- package/dist/mcp/handlers/review.d.ts +0 -51
- package/dist/mcp/handlers/review.js +0 -200
- package/dist/mcp/handlers/search.d.ts +0 -30
- package/dist/mcp/handlers/search.js +0 -58
- package/dist/mcp/handlers/test-mapping.d.ts +0 -39
- package/dist/mcp/handlers/test-mapping.js +0 -133
- package/dist/mcp/handlers/types.d.ts +0 -75
- package/dist/mcp/handlers/types.js +0 -25
- package/dist/mcp/index.d.ts +0 -45
- package/dist/mcp/index.js +0 -634
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified coverage handler for the MCP `report_coverage` tool. Replaces
|
|
3
|
-
* `get_requirement_coverage` and `get_project_coverage_summary` with a
|
|
4
|
-
* single tool that takes a `source: "local" | "cloud"` argument and
|
|
5
|
-
* mirrors the CLI `dotreq report` semantics.
|
|
6
|
-
*/
|
|
7
|
-
import { getProjectCoverage as queryProjectCoverage, getRequirementCoverage as queryRequirementCoverage, } from "../../requirements/cloud-coverage.js";
|
|
8
|
-
import { buildLocalReport, LocalReportCacheMissingError, } from "../../requirements/coverage.js";
|
|
9
|
-
import { CONVEX_URL } from "../convexClient.js";
|
|
10
|
-
import { errorResponse, textResponse } from "./types.js";
|
|
11
|
-
export async function handleReport(args, context) {
|
|
12
|
-
const source = args.source ?? "local";
|
|
13
|
-
if (source === "local") {
|
|
14
|
-
if (args.branch || args.sinceTimestamp !== undefined) {
|
|
15
|
-
return errorResponse("branch and sinceTimestamp require source=cloud. Local reports cover only the most recent test run on this machine.");
|
|
16
|
-
}
|
|
17
|
-
try {
|
|
18
|
-
const report = buildLocalReport({
|
|
19
|
-
projectRoot: context.workspaceRoot,
|
|
20
|
-
requirementFilter: args.requirementKey,
|
|
21
|
-
});
|
|
22
|
-
return textResponse(formatLocalReport(report));
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
if (error instanceof LocalReportCacheMissingError) {
|
|
26
|
-
return errorResponse(error.message);
|
|
27
|
-
}
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
// source === "cloud"
|
|
32
|
-
let project;
|
|
33
|
-
try {
|
|
34
|
-
project = await context.getProjectFromDiscovery(args.projectId);
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
return errorResponse(`Cloud report requires cloud credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
|
|
38
|
-
}
|
|
39
|
-
if (args.requirementKey) {
|
|
40
|
-
const record = await queryRequirementCoverage(args.requirementKey, project.projectId, project.projectSecret, CONVEX_URL);
|
|
41
|
-
return textResponse(formatCloudRequirement(record));
|
|
42
|
-
}
|
|
43
|
-
const record = await queryProjectCoverage(project.projectId, project.projectSecret, CONVEX_URL, {
|
|
44
|
-
branch: args.branch,
|
|
45
|
-
sinceTimestamp: args.sinceTimestamp,
|
|
46
|
-
});
|
|
47
|
-
return textResponse(formatCloudProject(record, {
|
|
48
|
-
branch: args.branch,
|
|
49
|
-
sinceTimestamp: args.sinceTimestamp,
|
|
50
|
-
}));
|
|
51
|
-
}
|
|
52
|
-
function formatLocalReport(report) {
|
|
53
|
-
const { total, tested, untested, coveragePercent } = report.summary;
|
|
54
|
-
let out = `# Coverage Report (local)\n\n`;
|
|
55
|
-
if (report.warnings.length > 0) {
|
|
56
|
-
for (const w of report.warnings) {
|
|
57
|
-
out += `> ⚠️ ${w}\n`;
|
|
58
|
-
}
|
|
59
|
-
out += "\n";
|
|
60
|
-
}
|
|
61
|
-
out += `**Total:** ${total} | **Tested:** ${tested} | **Untested:** ${untested} | **Coverage:** ${coveragePercent}%\n\n`;
|
|
62
|
-
const testedEntries = report.entries.filter((c) => c.tested);
|
|
63
|
-
if (testedEntries.length > 0) {
|
|
64
|
-
out += `## Tested (${testedEntries.length})\n\n`;
|
|
65
|
-
for (const c of testedEntries) {
|
|
66
|
-
out += `- **${c.key}**: ${c.content}\n`;
|
|
67
|
-
for (const loc of c.locations) {
|
|
68
|
-
out += ` - \`${loc}\`\n`;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
out += "\n";
|
|
72
|
-
}
|
|
73
|
-
const untestedEntries = report.entries.filter((c) => !c.tested);
|
|
74
|
-
if (untestedEntries.length > 0) {
|
|
75
|
-
out += `## Untested (${untestedEntries.length})\n\n`;
|
|
76
|
-
for (const c of untestedEntries) {
|
|
77
|
-
out += `- **${c.key}**: ${c.content}\n`;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return out;
|
|
81
|
-
}
|
|
82
|
-
function formatCloudRequirement(record) {
|
|
83
|
-
if (!record.lastTestedAt) {
|
|
84
|
-
return `**${record.requirementKey}** has no cloud coverage records.`;
|
|
85
|
-
}
|
|
86
|
-
const lastTested = new Date(record.lastTestedAt).toISOString();
|
|
87
|
-
let out = `# Coverage for ${record.requirementKey} (cloud)\n\n`;
|
|
88
|
-
out += `**Last tested:** ${lastTested}\n`;
|
|
89
|
-
if (record.branch)
|
|
90
|
-
out += `**Branch:** ${record.branch}\n`;
|
|
91
|
-
if (record.testFile) {
|
|
92
|
-
out += `**Test location:** \`${record.testFile}${record.testLine ? `:${record.testLine}` : ""}\`\n`;
|
|
93
|
-
}
|
|
94
|
-
const otherBranches = record.allBranches.filter((b) => b.branch !== record.branch);
|
|
95
|
-
if (otherBranches.length > 0) {
|
|
96
|
-
out += `\n**Also tested on:**\n`;
|
|
97
|
-
for (const b of otherBranches) {
|
|
98
|
-
out += `- ${b.branch}: ${new Date(b.lastTestedAt).toISOString()}\n`;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return out;
|
|
102
|
-
}
|
|
103
|
-
function formatCloudProject(record, filters) {
|
|
104
|
-
const tested = record.tested.length;
|
|
105
|
-
const untested = record.untested.length;
|
|
106
|
-
const total = tested + untested;
|
|
107
|
-
const coveragePercent = total > 0 ? ((tested / total) * 100).toFixed(1) : "0.0";
|
|
108
|
-
let filterNote = "";
|
|
109
|
-
if (filters.branch)
|
|
110
|
-
filterNote += `\n**Branch:** ${filters.branch}`;
|
|
111
|
-
if (filters.sinceTimestamp) {
|
|
112
|
-
filterNote += `\n**Since:** ${new Date(filters.sinceTimestamp).toISOString()}`;
|
|
113
|
-
}
|
|
114
|
-
let out = `# Project Coverage Report (cloud)\n\n`;
|
|
115
|
-
out += `**Total Requirements:** ${total}\n**Coverage:** ${coveragePercent}%${filterNote}\n`;
|
|
116
|
-
if (record.tested.length > 0) {
|
|
117
|
-
out += `\n## Tested Requirements (${record.tested.length})\n\n`;
|
|
118
|
-
for (const t of record.tested) {
|
|
119
|
-
const date = new Date(t.lastTestedAt).toISOString();
|
|
120
|
-
const location = t.testFile
|
|
121
|
-
? ` (\`${t.testFile}${t.testLine ? `:${t.testLine}` : ""}\`)`
|
|
122
|
-
: "";
|
|
123
|
-
out += `- **${t.requirementKey}** [${t.branch}]: ${date}${location}\n`;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (record.untested.length > 0) {
|
|
127
|
-
out += `\n## Untested Requirements (${record.untested.length})\n\n`;
|
|
128
|
-
for (const key of record.untested) {
|
|
129
|
-
out += `- ${key}\n`;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return out;
|
|
133
|
-
}
|
|
134
|
-
//# sourceMappingURL=report.js.map
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Review handlers for MCP tools
|
|
3
|
-
*
|
|
4
|
-
* Provides AI-powered review functionality:
|
|
5
|
-
* - style_check: Check requirements or test files for style issues
|
|
6
|
-
* - review_test: Comprehensively review tests for semantic correctness
|
|
7
|
-
*/
|
|
8
|
-
import type { HandlerContext, ToolResponse } from "./types.js";
|
|
9
|
-
/**
|
|
10
|
-
* Arguments for style_check tool
|
|
11
|
-
*/
|
|
12
|
-
export interface StyleCheckArgs {
|
|
13
|
-
filePath: string;
|
|
14
|
-
requirementKeys?: string[];
|
|
15
|
-
model?: string;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Arguments for review_test tool
|
|
19
|
-
*/
|
|
20
|
-
export interface ReviewTestArgs {
|
|
21
|
-
testFilePath: string;
|
|
22
|
-
projectId?: string;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Handler for style_check tool
|
|
26
|
-
*
|
|
27
|
-
* Requirements covered:
|
|
28
|
-
* - MCP-REVIEW-1.0: For requirements files, feedback identifies clarity issues such as vague language and missing preconditions
|
|
29
|
-
* - MCP-REVIEW-1.1: For test files, feedback identifies incorrect requirement() usage and missing test coverage
|
|
30
|
-
* - MCP-REVIEW-1.2: Feedback is categorized by severity: must fix, should fix, could improve
|
|
31
|
-
* - MCP-REVIEW-1.3: When cloud credentials are unavailable, an error explains how to authenticate
|
|
32
|
-
*/
|
|
33
|
-
export declare function handleStyleCheck(args: StyleCheckArgs, context: HandlerContext, options?: {
|
|
34
|
-
useEnvAuth?: boolean;
|
|
35
|
-
apiBaseUrl?: string;
|
|
36
|
-
}): Promise<ToolResponse>;
|
|
37
|
-
/**
|
|
38
|
-
* Handler for review_test tool
|
|
39
|
-
*
|
|
40
|
-
* Requirements covered:
|
|
41
|
-
* - MCP-REVIEW-2.0: Feedback identifies test setup that does not match requirement preconditions
|
|
42
|
-
* - MCP-REVIEW-2.1: Feedback identifies test actions that do not match requirement triggers
|
|
43
|
-
* - MCP-REVIEW-2.2: Feedback identifies test assertions that do not match requirement outcomes
|
|
44
|
-
* - MCP-REVIEW-2.3: Feedback identifies requirements without test coverage
|
|
45
|
-
* - MCP-REVIEW-2.4: Feedback identifies tests that reference non-existent requirements
|
|
46
|
-
* - MCP-REVIEW-2.5: When cloud credentials are unavailable, an error explains how to authenticate
|
|
47
|
-
*/
|
|
48
|
-
export declare function handleReviewTest(args: ReviewTestArgs, context: HandlerContext, options?: {
|
|
49
|
-
apiBaseUrl?: string;
|
|
50
|
-
}): Promise<ToolResponse>;
|
|
51
|
-
//# sourceMappingURL=review.d.ts.map
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Review handlers for MCP tools
|
|
3
|
-
*
|
|
4
|
-
* Provides AI-powered review functionality:
|
|
5
|
-
* - style_check: Check requirements or test files for style issues
|
|
6
|
-
* - review_test: Comprehensively review tests for semantic correctness
|
|
7
|
-
*/
|
|
8
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
-
import { dirname, resolve } from "node:path";
|
|
10
|
-
import { fetchReviewTestFeedback, fetchStyleCheckFeedback, } from "../../requirements/cloud-ai.js";
|
|
11
|
-
import { filterRequirementsByKeys, formatRequirementTree, getRequirementTree, } from "../../requirements/index.js";
|
|
12
|
-
import { discoverProjects, } from "../../utils/project-discovery.js";
|
|
13
|
-
import { errorResponse, textResponse } from "./types.js";
|
|
14
|
-
const STYLE_CHECK_GUIDANCE = `
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## How to use this feedback
|
|
19
|
-
|
|
20
|
-
1. **This check is stateless.** It is a "new set of eyes" from any previous feedback rounds — it has no memory of prior suggestions or changes you've already made.
|
|
21
|
-
|
|
22
|
-
2. **Act on feedback from the posture of "what will make the requirements better."** The severity categories (MUST FIX / SHOULD FIX / COULD IMPROVE) are prioritized for convenience, but unless the user has explicitly told you to ignore lower-priority feedback, go ahead and make any improvements — minor or otherwise. If you believe acting on a specific piece of feedback would make the requirements *worse*, surface that feedback to the user rather than silently ignoring it. Otherwise, just act.
|
|
23
|
-
|
|
24
|
-
3. **Next steps:** Always run \`validate_requirements\` again after making updates. If you make significant changes based on this feedback, you can run \`style_check\` again — but be aware that will be a fresh set of eyes, not a continued conversation.`;
|
|
25
|
-
/**
|
|
26
|
-
* Handler for style_check tool
|
|
27
|
-
*
|
|
28
|
-
* Requirements covered:
|
|
29
|
-
* - MCP-REVIEW-1.0: For requirements files, feedback identifies clarity issues such as vague language and missing preconditions
|
|
30
|
-
* - MCP-REVIEW-1.1: For test files, feedback identifies incorrect requirement() usage and missing test coverage
|
|
31
|
-
* - MCP-REVIEW-1.2: Feedback is categorized by severity: must fix, should fix, could improve
|
|
32
|
-
* - MCP-REVIEW-1.3: When cloud credentials are unavailable, an error explains how to authenticate
|
|
33
|
-
*/
|
|
34
|
-
export async function handleStyleCheck(args, context, options) {
|
|
35
|
-
const { filePath, requirementKeys, model } = args;
|
|
36
|
-
const { useEnvAuth = false, apiBaseUrl = "https://app.dotrequirements.io" } = options ?? {};
|
|
37
|
-
const fullPath = resolve(context.workspaceRoot, filePath);
|
|
38
|
-
if (!existsSync(fullPath)) {
|
|
39
|
-
return errorResponse(`File not found: ${filePath}`);
|
|
40
|
-
}
|
|
41
|
-
// Read file contents
|
|
42
|
-
const fileContents = readFileSync(fullPath, "utf-8");
|
|
43
|
-
// Detect file type
|
|
44
|
-
// MCP-REVIEW-1.0 & MCP-REVIEW-1.1: Different feedback for different file types
|
|
45
|
-
const isRequirementsFile = filePath.endsWith(".requirements.md");
|
|
46
|
-
const isTestFile = /\.(test|spec)\.(js|jsx|ts|tsx)$/.test(filePath);
|
|
47
|
-
if (!isRequirementsFile && !isTestFile) {
|
|
48
|
-
return errorResponse(`Unsupported file type. File must be:\n- Requirements file: *.requirements.md\n- Test file: *.test.{js,jsx,ts,tsx} or *.spec.{js,jsx,ts,tsx}`);
|
|
49
|
-
}
|
|
50
|
-
const fileType = isRequirementsFile ? "requirements" : "test";
|
|
51
|
-
// If requirementKeys provided, filter the file to only those requirements
|
|
52
|
-
let fileContentsToCheck = fileContents;
|
|
53
|
-
let scopeNote = "";
|
|
54
|
-
if (requirementKeys && requirementKeys.length > 0) {
|
|
55
|
-
if (!isRequirementsFile) {
|
|
56
|
-
return errorResponse(`requirementKeys can only be used with requirements files (*.requirements.md), not test files.`);
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
const { filteredContent, foundKeys, missingKeys } = filterRequirementsByKeys(fileContents, requirementKeys);
|
|
60
|
-
if (foundKeys.length === 0) {
|
|
61
|
-
return errorResponse(`None of the specified requirement keys were found in ${filePath}: ${requirementKeys.join(", ")}`);
|
|
62
|
-
}
|
|
63
|
-
fileContentsToCheck = filteredContent;
|
|
64
|
-
if (missingKeys.length > 0) {
|
|
65
|
-
scopeNote = `\n\n> **Note:** Some specified keys were not found in the file: ${missingKeys.join(", ")}`;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
return errorResponse(`Failed to filter requirements: ${error instanceof Error ? error.message : String(error)}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
// MCP-REVIEW-1.3: Get credentials - walk up from file's directory to find project
|
|
73
|
-
const fileDir = dirname(fullPath);
|
|
74
|
-
let project;
|
|
75
|
-
try {
|
|
76
|
-
if (useEnvAuth) {
|
|
77
|
-
// Using env auth - go straight to context
|
|
78
|
-
project = await context.getProjectFromDiscovery();
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
// Try to find a project starting from the file's directory
|
|
82
|
-
const result = await discoverProjects(fileDir);
|
|
83
|
-
if (result.type === "none") {
|
|
84
|
-
// If no project found from file dir, try from context
|
|
85
|
-
project = await context.getProjectFromDiscovery();
|
|
86
|
-
}
|
|
87
|
-
else if (result.type === "single") {
|
|
88
|
-
project = result.project;
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
// Multiple projects - can't auto-detect which one to use
|
|
92
|
-
throw new Error("Multiple projects found - cannot auto-detect for this file");
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
return errorResponse(`Style check requires project credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
|
|
98
|
-
}
|
|
99
|
-
// Call hosted endpoint via the shared library helper
|
|
100
|
-
try {
|
|
101
|
-
const feedback = await fetchStyleCheckFeedback({
|
|
102
|
-
apiBaseUrl,
|
|
103
|
-
projectId: project.projectId,
|
|
104
|
-
projectSecret: project.projectSecret,
|
|
105
|
-
fileContents: fileContentsToCheck,
|
|
106
|
-
fileType,
|
|
107
|
-
model,
|
|
108
|
-
});
|
|
109
|
-
const scopeLabel = requirementKeys && requirementKeys.length > 0
|
|
110
|
-
? ` (${requirementKeys.join(", ")})`
|
|
111
|
-
: "";
|
|
112
|
-
// MCP-REVIEW-1.2: Feedback includes severity categories (in the API response)
|
|
113
|
-
return textResponse(`# Style Check Results for \`${filePath}\`${scopeLabel}\n\n${feedback}${scopeNote}${STYLE_CHECK_GUIDANCE}`);
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
return errorResponse(`Style check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Handler for review_test tool
|
|
121
|
-
*
|
|
122
|
-
* Requirements covered:
|
|
123
|
-
* - MCP-REVIEW-2.0: Feedback identifies test setup that does not match requirement preconditions
|
|
124
|
-
* - MCP-REVIEW-2.1: Feedback identifies test actions that do not match requirement triggers
|
|
125
|
-
* - MCP-REVIEW-2.2: Feedback identifies test assertions that do not match requirement outcomes
|
|
126
|
-
* - MCP-REVIEW-2.3: Feedback identifies requirements without test coverage
|
|
127
|
-
* - MCP-REVIEW-2.4: Feedback identifies tests that reference non-existent requirements
|
|
128
|
-
* - MCP-REVIEW-2.5: When cloud credentials are unavailable, an error explains how to authenticate
|
|
129
|
-
*/
|
|
130
|
-
export async function handleReviewTest(args, context, options) {
|
|
131
|
-
const { testFilePath, projectId } = args;
|
|
132
|
-
const { apiBaseUrl = "https://app.dotrequirements.io" } = options ?? {};
|
|
133
|
-
const fullPath = resolve(context.workspaceRoot, testFilePath);
|
|
134
|
-
if (!existsSync(fullPath)) {
|
|
135
|
-
return errorResponse(`File not found: ${testFilePath}`);
|
|
136
|
-
}
|
|
137
|
-
// Verify it's a test file
|
|
138
|
-
const isTestFile = /\.(test|spec)\.(js|jsx|ts|tsx)$/.test(testFilePath);
|
|
139
|
-
if (!isTestFile) {
|
|
140
|
-
return errorResponse(`File must be a test file: *.test.{js,jsx,ts,tsx} or *.spec.{js,jsx,ts,tsx}`);
|
|
141
|
-
}
|
|
142
|
-
// Read test file contents
|
|
143
|
-
const testFileContents = readFileSync(fullPath, "utf-8");
|
|
144
|
-
// Extract requirement IDs from the test file using regex
|
|
145
|
-
const requirementIdMatches = testFileContents.matchAll(/requirement\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g);
|
|
146
|
-
const requirementIds = new Set();
|
|
147
|
-
for (const match of requirementIdMatches) {
|
|
148
|
-
requirementIds.add(match[1]);
|
|
149
|
-
}
|
|
150
|
-
// MCP-REVIEW-2.5: Get project credentials
|
|
151
|
-
let project;
|
|
152
|
-
try {
|
|
153
|
-
project = await context.getProjectFromDiscovery(projectId);
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
return errorResponse(`Test review requires a configured dotrequirements project.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
|
|
157
|
-
}
|
|
158
|
-
// Load all requirements for this project
|
|
159
|
-
const allRequirements = await context.getRequirements(projectId);
|
|
160
|
-
// Group requirement IDs by their root to avoid sending duplicate trees
|
|
161
|
-
// e.g., NAV-3.0, NAV-3.1, NAV-3.2 all belong to root NAV-3
|
|
162
|
-
const rootToTestedIds = new Map();
|
|
163
|
-
for (const reqId of requirementIds) {
|
|
164
|
-
const req = allRequirements.find((r) => r.id === reqId || r.rootId === reqId);
|
|
165
|
-
if (req) {
|
|
166
|
-
if (!rootToTestedIds.has(req.rootId)) {
|
|
167
|
-
rootToTestedIds.set(req.rootId, new Set());
|
|
168
|
-
}
|
|
169
|
-
rootToTestedIds.get(req.rootId).add(reqId);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// Build requirements array with one entry per unique root tree
|
|
173
|
-
// MCP-REVIEW-2.0, 2.1, 2.2: These validations happen in the API based on the tree structure
|
|
174
|
-
const requirements = [];
|
|
175
|
-
for (const [rootId, testedIds] of rootToTestedIds) {
|
|
176
|
-
const tree = getRequirementTree(allRequirements, rootId);
|
|
177
|
-
const formattedTree = formatRequirementTree(tree);
|
|
178
|
-
requirements.push({
|
|
179
|
-
id: rootId,
|
|
180
|
-
content: formattedTree,
|
|
181
|
-
testedIds: Array.from(testedIds).sort(),
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
// Call hosted endpoint via the shared library helper
|
|
185
|
-
// MCP-REVIEW-2.3 & 2.4: API identifies coverage gaps and non-existent references
|
|
186
|
-
try {
|
|
187
|
-
const feedback = await fetchReviewTestFeedback({
|
|
188
|
-
apiBaseUrl,
|
|
189
|
-
projectId: project.projectId,
|
|
190
|
-
projectSecret: project.projectSecret,
|
|
191
|
-
testFileContents,
|
|
192
|
-
requirements,
|
|
193
|
-
});
|
|
194
|
-
return textResponse(`# Test Review Results for \`${testFilePath}\`\n\n${feedback}`);
|
|
195
|
-
}
|
|
196
|
-
catch (error) {
|
|
197
|
-
return errorResponse(`Test review failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
//# sourceMappingURL=review.js.map
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* search_requirements handler
|
|
3
|
-
*
|
|
4
|
-
* Searches requirements by text or regex query across IDs, content, and labels.
|
|
5
|
-
* Returns matching requirements with their full tree context.
|
|
6
|
-
*/
|
|
7
|
-
import type { HandlerContext, ToolResponse } from "./types.js";
|
|
8
|
-
/**
|
|
9
|
-
* Arguments for search_requirements tool
|
|
10
|
-
*/
|
|
11
|
-
export interface SearchRequirementsArgs {
|
|
12
|
-
query: string;
|
|
13
|
-
useRegex?: boolean;
|
|
14
|
-
projectId?: string;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Handler for search_requirements tool
|
|
18
|
-
*
|
|
19
|
-
* Requirements covered:
|
|
20
|
-
* - MCP-SEARCH-1.0: Query matches requirement ID
|
|
21
|
-
* - MCP-SEARCH-1.1: Query matches requirement content
|
|
22
|
-
* - MCP-SEARCH-1.2: Query matches requirement label
|
|
23
|
-
* - MCP-SEARCH-1.3: useRegex interprets query as case-insensitive regex
|
|
24
|
-
* - MCP-SEARCH-1.4: Invalid regex returns error
|
|
25
|
-
* - MCP-SEARCH-1.5: No matches returns informative message
|
|
26
|
-
* - MCP-SEARCH-1.6: Nested match returns the full root requirement tree
|
|
27
|
-
* - MCP-SEARCH-1.7: Results include full tree as code block
|
|
28
|
-
*/
|
|
29
|
-
export declare function handleSearchRequirements(args: SearchRequirementsArgs, context: HandlerContext): Promise<ToolResponse>;
|
|
30
|
-
//# sourceMappingURL=search.d.ts.map
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* search_requirements handler
|
|
3
|
-
*
|
|
4
|
-
* Searches requirements by text or regex query across IDs, content, and labels.
|
|
5
|
-
* Returns matching requirements with their full tree context.
|
|
6
|
-
*/
|
|
7
|
-
import { formatRequirementTree, getRequirementTree, searchRequirements, } from "../../requirements/index.js";
|
|
8
|
-
import { errorResponse, textResponse } from "./types.js";
|
|
9
|
-
/**
|
|
10
|
-
* Handler for search_requirements tool
|
|
11
|
-
*
|
|
12
|
-
* Requirements covered:
|
|
13
|
-
* - MCP-SEARCH-1.0: Query matches requirement ID
|
|
14
|
-
* - MCP-SEARCH-1.1: Query matches requirement content
|
|
15
|
-
* - MCP-SEARCH-1.2: Query matches requirement label
|
|
16
|
-
* - MCP-SEARCH-1.3: useRegex interprets query as case-insensitive regex
|
|
17
|
-
* - MCP-SEARCH-1.4: Invalid regex returns error
|
|
18
|
-
* - MCP-SEARCH-1.5: No matches returns informative message
|
|
19
|
-
* - MCP-SEARCH-1.6: Nested match returns the full root requirement tree
|
|
20
|
-
* - MCP-SEARCH-1.7: Results include full tree as code block
|
|
21
|
-
*/
|
|
22
|
-
export async function handleSearchRequirements(args, context) {
|
|
23
|
-
const { query, useRegex = false, projectId } = args;
|
|
24
|
-
const requirements = await context.getRequirements(projectId);
|
|
25
|
-
let results;
|
|
26
|
-
if (useRegex) {
|
|
27
|
-
// MCP-SEARCH-1.3: Interpret query as case-insensitive regex
|
|
28
|
-
// MCP-SEARCH-1.4: Return error for invalid regex
|
|
29
|
-
try {
|
|
30
|
-
const regex = new RegExp(query, "i");
|
|
31
|
-
results = requirements.filter((r) => regex.test(r.id) || regex.test(r.content) || regex.test(r.label));
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
return errorResponse(`Invalid regular expression: ${error instanceof Error ? error.message : String(error)}`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
// MCP-SEARCH-1.0, MCP-SEARCH-1.1, MCP-SEARCH-1.2: Text search across ID, content, label
|
|
39
|
-
results = searchRequirements(requirements, query);
|
|
40
|
-
}
|
|
41
|
-
// MCP-SEARCH-1.6: Collect unique root IDs from all matches (nested matches bubble up to their root)
|
|
42
|
-
const matchedRootIds = new Set(results.map((r) => r.rootId));
|
|
43
|
-
const rootResults = requirements.filter((r) => r.path.length === 0 && matchedRootIds.has(r.rootId));
|
|
44
|
-
// MCP-SEARCH-1.5: No matches returns informative message
|
|
45
|
-
if (rootResults.length === 0) {
|
|
46
|
-
return textResponse(`No requirements found matching "${query}"${useRegex ? " (regex)" : ""}`);
|
|
47
|
-
}
|
|
48
|
-
// MCP-SEARCH-1.7: Each result includes full tree as code block
|
|
49
|
-
const formatted = rootResults
|
|
50
|
-
.map((r) => {
|
|
51
|
-
const tree = getRequirementTree(requirements, r.id);
|
|
52
|
-
const treeFormatted = formatRequirementTree(tree);
|
|
53
|
-
return `**${r.id}** (${r.label})\n${r.content}\n_Source: ${r.documentTitle}_\n\n\`\`\`\n${treeFormatted}\n\`\`\``;
|
|
54
|
-
})
|
|
55
|
-
.join("\n\n---\n\n");
|
|
56
|
-
return textResponse(`Found ${rootResults.length} requirement(s) matching "${query}"${useRegex ? " (regex)" : ""}:\n\n${formatted}`);
|
|
57
|
-
}
|
|
58
|
-
//# sourceMappingURL=search.js.map
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* get_requirements_by_test and get_tests_by_requirement handlers
|
|
3
|
-
*
|
|
4
|
-
* Maps between test files and requirements in both directions.
|
|
5
|
-
*/
|
|
6
|
-
import type { HandlerContext, ToolResponse } from "./types.js";
|
|
7
|
-
/**
|
|
8
|
-
* Arguments for get_requirements_by_test tool
|
|
9
|
-
*/
|
|
10
|
-
export interface GetRequirementsByTestArgs {
|
|
11
|
-
testFile: string;
|
|
12
|
-
projectId?: string;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Handler for get_requirements_by_test tool
|
|
16
|
-
*
|
|
17
|
-
* Requirements covered:
|
|
18
|
-
* - MCP-MAP-1.0: The response lists each requirement() call found in the test file
|
|
19
|
-
* - MCP-MAP-1.1: For each reference, the requirement content and line number are shown
|
|
20
|
-
* - MCP-MAP-1.2: When the test file does not exist, an error is returned
|
|
21
|
-
*/
|
|
22
|
-
export declare function handleGetRequirementsByTest(args: GetRequirementsByTestArgs, context: HandlerContext): Promise<ToolResponse>;
|
|
23
|
-
/**
|
|
24
|
-
* Arguments for get_tests_by_requirement tool
|
|
25
|
-
*/
|
|
26
|
-
export interface GetTestsByRequirementArgs {
|
|
27
|
-
requirementsFile: string;
|
|
28
|
-
projectId?: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Handler for get_tests_by_requirement tool
|
|
32
|
-
*
|
|
33
|
-
* Requirements covered:
|
|
34
|
-
* - MCP-MAP-2.0: The response lists which requirements have test references
|
|
35
|
-
* - MCP-MAP-2.1: For each covered requirement, the test file and line number are shown
|
|
36
|
-
* - MCP-MAP-2.2: Requirements without test references are listed separately
|
|
37
|
-
*/
|
|
38
|
-
export declare function handleGetTestsByRequirement(args: GetTestsByRequirementArgs, context: HandlerContext): Promise<ToolResponse>;
|
|
39
|
-
//# sourceMappingURL=test-mapping.d.ts.map
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* get_requirements_by_test and get_tests_by_requirement handlers
|
|
3
|
-
*
|
|
4
|
-
* Maps between test files and requirements in both directions.
|
|
5
|
-
*/
|
|
6
|
-
import { existsSync } from "node:fs";
|
|
7
|
-
import { relative, resolve } from "node:path";
|
|
8
|
-
import { findAllTestReferences, findRequirementsInFile, } from "../../requirements/grep.js";
|
|
9
|
-
import { getRequirementById } from "../../requirements/index.js";
|
|
10
|
-
import { errorResponse, textResponse } from "./types.js";
|
|
11
|
-
/**
|
|
12
|
-
* Handler for get_requirements_by_test tool
|
|
13
|
-
*
|
|
14
|
-
* Requirements covered:
|
|
15
|
-
* - MCP-MAP-1.0: The response lists each requirement() call found in the test file
|
|
16
|
-
* - MCP-MAP-1.1: For each reference, the requirement content and line number are shown
|
|
17
|
-
* - MCP-MAP-1.2: When the test file does not exist, an error is returned
|
|
18
|
-
*/
|
|
19
|
-
export async function handleGetRequirementsByTest(args, context) {
|
|
20
|
-
const { testFile, projectId } = args;
|
|
21
|
-
// MCP-MAP-1.2: Check if file exists before processing
|
|
22
|
-
const fullPath = resolve(context.workspaceRoot, testFile);
|
|
23
|
-
if (!existsSync(fullPath)) {
|
|
24
|
-
return errorResponse(`Test file not found: ${testFile}`);
|
|
25
|
-
}
|
|
26
|
-
const requirements = await context.getRequirements(projectId);
|
|
27
|
-
// MCP-MAP-1.0: Find all requirement references in the test file
|
|
28
|
-
const refs = await findRequirementsInFile(testFile);
|
|
29
|
-
if (refs.length === 0) {
|
|
30
|
-
return textResponse(`No requirement references found in "${testFile}"`);
|
|
31
|
-
}
|
|
32
|
-
// MCP-MAP-1.1: For each reference, get the requirement content and show line number
|
|
33
|
-
const entries = refs.map((ref) => {
|
|
34
|
-
const req = getRequirementById(requirements, ref.requirementId);
|
|
35
|
-
const reqContent = req
|
|
36
|
-
? `**${req.label}:** ${req.content}`
|
|
37
|
-
: `(requirement not found)`;
|
|
38
|
-
return `**Line ${ref.line}:** \`${ref.requirementId}\` - ${reqContent}`;
|
|
39
|
-
});
|
|
40
|
-
return textResponse(`Requirements referenced in "${testFile}":\n\n${entries.join("\n\n")}`);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Handler for get_tests_by_requirement tool
|
|
44
|
-
*
|
|
45
|
-
* Requirements covered:
|
|
46
|
-
* - MCP-MAP-2.0: The response lists which requirements have test references
|
|
47
|
-
* - MCP-MAP-2.1: For each covered requirement, the test file and line number are shown
|
|
48
|
-
* - MCP-MAP-2.2: Requirements without test references are listed separately
|
|
49
|
-
*/
|
|
50
|
-
export async function handleGetTestsByRequirement(args, context) {
|
|
51
|
-
const { requirementsFile, projectId } = args;
|
|
52
|
-
const { workspaceRoot } = context;
|
|
53
|
-
const fullPath = resolve(workspaceRoot, requirementsFile);
|
|
54
|
-
// Verify file exists
|
|
55
|
-
if (!existsSync(fullPath)) {
|
|
56
|
-
return errorResponse(`Requirements file not found: ${requirementsFile}`);
|
|
57
|
-
}
|
|
58
|
-
// Verify it's a requirements file
|
|
59
|
-
if (!requirementsFile.endsWith(".requirements.md")) {
|
|
60
|
-
return errorResponse(`File must be a requirements file: *.requirements.md`);
|
|
61
|
-
}
|
|
62
|
-
// Load all requirements from this file
|
|
63
|
-
const requirements = await context.getRequirements(projectId);
|
|
64
|
-
const fileRequirements = requirements.filter((req) => resolve(workspaceRoot, req.sourceFile) === fullPath);
|
|
65
|
-
if (fileRequirements.length === 0) {
|
|
66
|
-
return textResponse(`No requirements found in "${requirementsFile}"`);
|
|
67
|
-
}
|
|
68
|
-
// Get unique root requirement IDs from this file
|
|
69
|
-
const rootIds = new Set(fileRequirements.map((req) => req.rootId));
|
|
70
|
-
// Find all test references in the workspace
|
|
71
|
-
const allTestRefs = await findAllTestReferences(workspaceRoot);
|
|
72
|
-
// Build coverage map: requirement ID -> list of test references
|
|
73
|
-
const coverageMap = new Map();
|
|
74
|
-
for (const ref of allTestRefs) {
|
|
75
|
-
if (!coverageMap.has(ref.requirementId)) {
|
|
76
|
-
coverageMap.set(ref.requirementId, []);
|
|
77
|
-
}
|
|
78
|
-
coverageMap.get(ref.requirementId).push(ref);
|
|
79
|
-
}
|
|
80
|
-
// MCP-MAP-2.0: Categorize requirements as covered or not covered
|
|
81
|
-
const covered = [];
|
|
82
|
-
const notCovered = [];
|
|
83
|
-
for (const rootId of rootIds) {
|
|
84
|
-
// Check if this requirement ID or any of its children are referenced
|
|
85
|
-
const reqAndChildren = fileRequirements.filter((r) => r.rootId === rootId);
|
|
86
|
-
const allIds = reqAndChildren.map((r) => r.id);
|
|
87
|
-
const testsForThisReq = [];
|
|
88
|
-
for (const id of allIds) {
|
|
89
|
-
const refs = coverageMap.get(id);
|
|
90
|
-
if (refs) {
|
|
91
|
-
testsForThisReq.push(...refs);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (testsForThisReq.length > 0) {
|
|
95
|
-
covered.push({ id: rootId, tests: testsForThisReq });
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
notCovered.push(rootId);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
// Format output
|
|
102
|
-
let output = `# Test Coverage for \`${requirementsFile}\`\n\n`;
|
|
103
|
-
// MCP-MAP-2.1: For each covered requirement, show test file and line number
|
|
104
|
-
if (covered.length > 0) {
|
|
105
|
-
output += `## Covered (${covered.length})\n\n`;
|
|
106
|
-
for (const { id, tests } of covered) {
|
|
107
|
-
// Group tests by file
|
|
108
|
-
const testsByFile = new Map();
|
|
109
|
-
for (const test of tests) {
|
|
110
|
-
if (!testsByFile.has(test.file)) {
|
|
111
|
-
testsByFile.set(test.file, []);
|
|
112
|
-
}
|
|
113
|
-
testsByFile.get(test.file).push(test.line);
|
|
114
|
-
}
|
|
115
|
-
output += `- **${id}**: ${tests.length} test${tests.length === 1 ? "" : "s"}\n`;
|
|
116
|
-
for (const [file, lines] of testsByFile.entries()) {
|
|
117
|
-
const relPath = relative(workspaceRoot, file);
|
|
118
|
-
const lineList = lines.sort((a, b) => a - b).join(", ");
|
|
119
|
-
output += ` - \`${relPath}\` (lines ${lineList})\n`;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
output += "\n";
|
|
123
|
-
}
|
|
124
|
-
// MCP-MAP-2.2: Requirements without test references are listed separately
|
|
125
|
-
if (notCovered.length > 0) {
|
|
126
|
-
output += `## Not Covered (${notCovered.length})\n\n`;
|
|
127
|
-
for (const id of notCovered) {
|
|
128
|
-
output += `- **${id}**: No tests found\n`;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return textResponse(output);
|
|
132
|
-
}
|
|
133
|
-
//# sourceMappingURL=test-mapping.js.map
|