@popoverai/dotrequirements 0.15.0 → 0.17.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.
@@ -0,0 +1,36 @@
1
+ /**
2
+ * list_all_requirements and list_untested_requirements handlers
3
+ *
4
+ * Lists requirements in a project with optional filtering for untested requirements.
5
+ */
6
+ import type { HandlerContext, ToolResponse } from './types.js';
7
+ /**
8
+ * Arguments for list_all_requirements tool
9
+ */
10
+ export interface ListAllRequirementsArgs {
11
+ projectId?: string;
12
+ }
13
+ /**
14
+ * Handler for list_all_requirements tool
15
+ *
16
+ * Requirements covered:
17
+ * - MCP-LIST-1.0: Response includes count of total requirements and total nodes
18
+ * - MCP-LIST-1.1: Each requirement displays its key, label, and child count
19
+ * - MCP-LIST-1.2: Requirements are listed from all files discovered in the project
20
+ */
21
+ export declare function handleListAllRequirements(args: ListAllRequirementsArgs, context: HandlerContext): Promise<ToolResponse>;
22
+ /**
23
+ * Arguments for list_untested_requirements tool
24
+ */
25
+ export interface ListUntestedRequirementsArgs {
26
+ projectId?: string;
27
+ }
28
+ /**
29
+ * Handler for list_untested_requirements tool
30
+ *
31
+ * Requirements covered:
32
+ * - MCP-LIST-2.0: Only requirements with no test references are included
33
+ * - MCP-LIST-2.1: Response indicates count of requirements lacking coverage
34
+ */
35
+ export declare function handleListUntestedRequirements(args: ListUntestedRequirementsArgs, context: HandlerContext): Promise<ToolResponse>;
36
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1,65 @@
1
+ /**
2
+ * list_all_requirements and list_untested_requirements handlers
3
+ *
4
+ * Lists requirements in a project with optional filtering for untested requirements.
5
+ */
6
+ import { textResponse } from './types.js';
7
+ import { getReferencedRequirementIds } from '../grep.js';
8
+ /**
9
+ * Handler for list_all_requirements tool
10
+ *
11
+ * Requirements covered:
12
+ * - MCP-LIST-1.0: Response includes count of total requirements and total nodes
13
+ * - MCP-LIST-1.1: Each requirement displays its key, label, and child count
14
+ * - MCP-LIST-1.2: Requirements are listed from all files discovered in the project
15
+ */
16
+ export async function handleListAllRequirements(args, context) {
17
+ const { projectId } = args;
18
+ const project = await context.getProjectFromDiscovery(projectId);
19
+ const requirements = await context.getRequirements(projectId);
20
+ if (requirements.length === 0) {
21
+ return textResponse(`No requirements found in project: ${project.path}\n\nMake sure .requirements/ directory exists with Markdown files (*.requirements.md).`);
22
+ }
23
+ // MCP-LIST-1.2: Group requirements from all discovered files by root ID
24
+ const grouped = new Map();
25
+ for (const req of requirements) {
26
+ const existing = grouped.get(req.rootId) || [];
27
+ existing.push(req);
28
+ grouped.set(req.rootId, existing);
29
+ }
30
+ // MCP-LIST-1.1: Each requirement displays key, label (from content), and child count
31
+ const formatted = Array.from(grouped.entries())
32
+ .map(([rootId, reqs]) => {
33
+ const root = reqs.find((r) => r.path.length === 0);
34
+ const childCount = reqs.length - 1;
35
+ return `- **${rootId}**: ${root?.content || '(no content)'} (${childCount} children)`;
36
+ })
37
+ .join('\n');
38
+ // MCP-LIST-1.0: Response includes count of total requirements and total nodes
39
+ return textResponse(`Found ${grouped.size} requirement(s) with ${requirements.length} total nodes:\n\n${formatted}`);
40
+ }
41
+ /**
42
+ * Handler for list_untested_requirements tool
43
+ *
44
+ * Requirements covered:
45
+ * - MCP-LIST-2.0: Only requirements with no test references are included
46
+ * - MCP-LIST-2.1: Response indicates count of requirements lacking coverage
47
+ */
48
+ export async function handleListUntestedRequirements(args, context) {
49
+ const { projectId } = args;
50
+ const project = await context.getProjectFromDiscovery(projectId);
51
+ const requirements = await context.getRequirements(projectId);
52
+ const referencedIds = await getReferencedRequirementIds(project.path);
53
+ // MCP-LIST-2.0: Filter to root requirements without test references
54
+ const rootRequirements = requirements.filter((r) => r.path.length === 0);
55
+ const untested = rootRequirements.filter((r) => !referencedIds.has(r.id));
56
+ if (untested.length === 0) {
57
+ return textResponse(`All ${rootRequirements.length} requirements have test references!`);
58
+ }
59
+ const formatted = untested
60
+ .map((r) => `- **${r.id}**: ${r.content}`)
61
+ .join('\n');
62
+ // MCP-LIST-2.1: Response indicates count of requirements lacking coverage
63
+ return textResponse(`Found ${untested.length} untested requirement(s) out of ${rootRequirements.length} total:\n\n${formatted}`);
64
+ }
65
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Push handler for MCP tools
3
+ *
4
+ * Provides requirements push functionality:
5
+ * - push_requirements: Push local requirements to cloud
6
+ */
7
+ import type { HandlerContext, ToolResponse } from './types.js';
8
+ /**
9
+ * Arguments for push_requirements tool
10
+ */
11
+ export interface PushRequirementsArgs {
12
+ filePath?: string;
13
+ confirmed?: boolean;
14
+ projectId?: string;
15
+ }
16
+ /**
17
+ * Handler for push_requirements tool
18
+ *
19
+ * Requirements covered:
20
+ * - MCP-PUSH-1.0: When a push is requested without confirmed flag, a diff preview is returned
21
+ * - MCP-PUSH-1.1: When confirmed is true, the push executes and returns success message
22
+ * - MCP-PUSH-1.2: When credentials are missing or invalid, an error explains how to authenticate
23
+ * - MCP-PUSH-1.3: When filePath is provided, only that file is pushed
24
+ */
25
+ export declare function handlePushRequirements(args: PushRequirementsArgs, context: HandlerContext): Promise<ToolResponse>;
26
+ //# sourceMappingURL=push.d.ts.map
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Push handler for MCP tools
3
+ *
4
+ * Provides requirements push functionality:
5
+ * - push_requirements: Push local requirements to cloud
6
+ */
7
+ import { textResponse, errorResponse } from './types.js';
8
+ import { existsSync } from 'fs';
9
+ import { resolve, basename } from 'path';
10
+ import { CONVEX_URL } from '../convexClient.js';
11
+ import { findRequirementsFiles } from '../requirements.js';
12
+ import { parseFilesForPush, dryRunPush, executePush, } from '../../push/index.js';
13
+ /**
14
+ * Handler for push_requirements tool
15
+ *
16
+ * Requirements covered:
17
+ * - MCP-PUSH-1.0: When a push is requested without confirmed flag, a diff preview is returned
18
+ * - MCP-PUSH-1.1: When confirmed is true, the push executes and returns success message
19
+ * - MCP-PUSH-1.2: When credentials are missing or invalid, an error explains how to authenticate
20
+ * - MCP-PUSH-1.3: When filePath is provided, only that file is pushed
21
+ */
22
+ export async function handlePushRequirements(args, context) {
23
+ const { filePath, confirmed = false, projectId } = args;
24
+ // MCP-PUSH-1.2: Get project credentials (throws if not configured)
25
+ let project;
26
+ try {
27
+ project = await context.getProjectFromDiscovery(projectId);
28
+ }
29
+ catch (error) {
30
+ return errorResponse(`Push requires project credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
31
+ }
32
+ // Determine files to push
33
+ let filesToPush;
34
+ // MCP-PUSH-1.3: When filePath is provided, only that file is pushed
35
+ if (filePath) {
36
+ const fullPath = resolve(project.path, filePath);
37
+ if (!existsSync(fullPath)) {
38
+ return errorResponse(`File not found: ${filePath}`);
39
+ }
40
+ filesToPush = [fullPath];
41
+ }
42
+ else {
43
+ // Use same file discovery as CLI - searches entire workspace
44
+ filesToPush = await findRequirementsFiles(project.path);
45
+ if (filesToPush.length === 0) {
46
+ return textResponse('No *.requirements.md files found. Nothing to push.');
47
+ }
48
+ }
49
+ // Parse files
50
+ const { parsedFiles, totalRequirements } = parseFilesForPush(filesToPush);
51
+ // Build credentials
52
+ const credentials = {
53
+ projectId: project.projectId,
54
+ projectSecret: project.projectSecret,
55
+ convexUrl: CONVEX_URL,
56
+ };
57
+ // Run dry run (always, even when confirmed - ensures fresh state)
58
+ const dryRunResult = await dryRunPush(parsedFiles, credentials);
59
+ // Check if there's anything to push
60
+ const pushableCount = dryRunResult.updates.length +
61
+ dryRunResult.creates.length +
62
+ dryRunResult.notFound.length;
63
+ if (pushableCount === 0 && dryRunResult.invalid.length > 0) {
64
+ // Only invalid files
65
+ const invalidList = dryRunResult.invalid
66
+ .map(({ file, result }) => `- ${basename(file.filePath)}: ${result.error}`)
67
+ .join('\n');
68
+ return {
69
+ content: [{ type: 'text', text: `✗ No valid documents to push.\n\n**Invalid files:**\n${invalidList}` }],
70
+ isError: true,
71
+ };
72
+ }
73
+ // MCP-PUSH-1.0: When not confirmed, show preview
74
+ if (!confirmed) {
75
+ let summary = `# Push Preview\n\n**Files:** ${parsedFiles.length}\n**Requirements:** ${totalRequirements}\n\n`;
76
+ if (dryRunResult.updates.length > 0) {
77
+ summary += `## Updates (${dryRunResult.updates.length})\n`;
78
+ for (const { file, result } of dryRunResult.updates) {
79
+ const fileName = basename(file.filePath);
80
+ const hasConflict = dryRunResult.conflicts.some((c) => c.item.file === file);
81
+ const conflictNote = hasConflict ? ' ⚠️ [cloud changed since pull]' : '';
82
+ const warningNote = result.warning ? ` (${result.warning})` : '';
83
+ summary += `- ~ ${fileName}${conflictNote}${warningNote}\n`;
84
+ }
85
+ summary += '\n';
86
+ }
87
+ if (dryRunResult.creates.length > 0) {
88
+ summary += `## New Documents (${dryRunResult.creates.length})\n`;
89
+ for (const { file, result } of dryRunResult.creates) {
90
+ const fileName = basename(file.filePath);
91
+ const warningNote = result.warning ? ` (${result.warning})` : '';
92
+ summary += `- + ${fileName}${warningNote}\n`;
93
+ }
94
+ summary += '\n';
95
+ }
96
+ if (dryRunResult.notFound.length > 0) {
97
+ summary += `## Not Found in Cloud (${dryRunResult.notFound.length}) - will create new\n`;
98
+ for (const { file, result } of dryRunResult.notFound) {
99
+ const fileName = basename(file.filePath);
100
+ summary += `- ! ${fileName} (ID: ${result.documentId})\n`;
101
+ }
102
+ summary += '\n';
103
+ }
104
+ if (dryRunResult.invalid.length > 0) {
105
+ summary += `## Skipped - Invalid (${dryRunResult.invalid.length})\n`;
106
+ for (const { file, result } of dryRunResult.invalid) {
107
+ const fileName = basename(file.filePath);
108
+ summary += `- ✗ ${fileName}: ${result.error}\n`;
109
+ }
110
+ summary += '\n';
111
+ }
112
+ if (dryRunResult.conflicts.length > 0) {
113
+ summary += `⚠️ **Warning:** ${dryRunResult.conflicts.length} file(s) have cloud changes since last pull. Pushing will overwrite those changes.\n\n`;
114
+ }
115
+ summary += `**To proceed:** Call this tool again with \`confirmed: true\``;
116
+ return textResponse(summary);
117
+ }
118
+ // MCP-PUSH-1.1: When confirmed, execute push
119
+ try {
120
+ const result = await executePush(dryRunResult, credentials);
121
+ let output = '✓ Push complete!\n\n';
122
+ if (result.created > 0) {
123
+ output += `**Created:** ${result.created} document(s)\n`;
124
+ }
125
+ if (result.updated > 0) {
126
+ output += `**Updated:** ${result.updated} document(s)\n`;
127
+ }
128
+ if (result.errors.length > 0) {
129
+ output += `\n**Errors:**\n`;
130
+ for (const { fileName, error } of result.errors) {
131
+ output += `- ${fileName}: ${error}\n`;
132
+ }
133
+ }
134
+ return textResponse(output);
135
+ }
136
+ catch (error) {
137
+ return {
138
+ content: [{ type: 'text', text: `✗ Push failed:\n\n${error instanceof Error ? error.message : String(error)}` }],
139
+ isError: true,
140
+ };
141
+ }
142
+ }
143
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1,51 @@
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 vague language, missing preconditions, and style violations
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: The review validates that test setup matches requirement preconditions
42
+ * - MCP-REVIEW-2.1: The review validates that test actions match requirement triggers
43
+ * - MCP-REVIEW-2.2: The review validates that test assertions 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
@@ -0,0 +1,219 @@
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 { textResponse, errorResponse } from './types.js';
9
+ import { existsSync, readFileSync } from 'fs';
10
+ import { resolve, dirname } from 'path';
11
+ import { filterRequirementsByKeys, getRequirementTree, formatRequirementTree, } from '../requirements.js';
12
+ import { discoverProjects, } from '../../utils/project-discovery.js';
13
+ const STYLE_CHECK_GUIDANCE = `
14
+
15
+ ---
16
+
17
+ ## How to use this feedback
18
+
19
+ 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.
20
+
21
+ 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.
22
+
23
+ 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.`;
24
+ /**
25
+ * Handler for style_check tool
26
+ *
27
+ * Requirements covered:
28
+ * - MCP-REVIEW-1.0: For requirements files, feedback identifies vague language, missing preconditions, and style violations
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 async function handleStyleCheck(args, context, options) {
34
+ const { filePath, requirementKeys, model } = args;
35
+ const { useEnvAuth = false, apiBaseUrl = 'https://app.dotrequirements.io' } = options ?? {};
36
+ const fullPath = resolve(context.workspaceRoot, filePath);
37
+ if (!existsSync(fullPath)) {
38
+ return errorResponse(`File not found: ${filePath}`);
39
+ }
40
+ // Read file contents
41
+ const fileContents = readFileSync(fullPath, 'utf-8');
42
+ // Detect file type
43
+ // MCP-REVIEW-1.0 & MCP-REVIEW-1.1: Different feedback for different file types
44
+ const isRequirementsFile = filePath.endsWith('.requirements.md');
45
+ const isTestFile = /\.(test|spec)\.(js|jsx|ts|tsx)$/.test(filePath);
46
+ if (!isRequirementsFile && !isTestFile) {
47
+ 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}`);
48
+ }
49
+ const fileType = isRequirementsFile ? 'requirements' : 'test';
50
+ // If requirementKeys provided, filter the file to only those requirements
51
+ let fileContentsToCheck = fileContents;
52
+ let scopeNote = '';
53
+ if (requirementKeys && requirementKeys.length > 0) {
54
+ if (!isRequirementsFile) {
55
+ return errorResponse(`requirementKeys can only be used with requirements files (*.requirements.md), not test files.`);
56
+ }
57
+ try {
58
+ const { filteredContent, foundKeys, missingKeys } = filterRequirementsByKeys(fileContents, requirementKeys);
59
+ if (foundKeys.length === 0) {
60
+ return errorResponse(`None of the specified requirement keys were found in ${filePath}: ${requirementKeys.join(', ')}`);
61
+ }
62
+ fileContentsToCheck = filteredContent;
63
+ if (missingKeys.length > 0) {
64
+ scopeNote = `\n\n> **Note:** Some specified keys were not found in the file: ${missingKeys.join(', ')}`;
65
+ }
66
+ }
67
+ catch (error) {
68
+ return errorResponse(`Failed to filter requirements: ${error instanceof Error ? error.message : String(error)}`);
69
+ }
70
+ }
71
+ // MCP-REVIEW-1.3: Get credentials - walk up from file's directory to find project
72
+ const fileDir = dirname(fullPath);
73
+ let project;
74
+ try {
75
+ if (useEnvAuth) {
76
+ // Using env auth - go straight to context
77
+ project = await context.getProjectFromDiscovery();
78
+ }
79
+ else {
80
+ // Try to find a project starting from the file's directory
81
+ const result = await discoverProjects(fileDir);
82
+ if (result.type === 'none') {
83
+ // If no project found from file dir, try from context
84
+ project = await context.getProjectFromDiscovery();
85
+ }
86
+ else if (result.type === 'single') {
87
+ project = result.project;
88
+ }
89
+ else {
90
+ // Multiple projects - can't auto-detect which one to use
91
+ throw new Error('Multiple projects found - cannot auto-detect for this file');
92
+ }
93
+ }
94
+ }
95
+ catch (error) {
96
+ return errorResponse(`Style check requires project credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
97
+ }
98
+ // Call API endpoint for style checking
99
+ try {
100
+ const response = await fetch(`${apiBaseUrl}/api/style-check`, {
101
+ method: 'POST',
102
+ headers: {
103
+ 'Content-Type': 'application/json',
104
+ },
105
+ body: JSON.stringify({
106
+ projectId: project.projectId,
107
+ projectSecret: project.projectSecret,
108
+ fileContents: fileContentsToCheck,
109
+ fileType,
110
+ model,
111
+ }),
112
+ });
113
+ if (!response.ok) {
114
+ const errorData = await response.json();
115
+ return errorResponse(`Style check failed: ${errorData.error || response.statusText}`);
116
+ }
117
+ const data = await response.json();
118
+ const scopeLabel = requirementKeys && requirementKeys.length > 0
119
+ ? ` (${requirementKeys.join(', ')})`
120
+ : '';
121
+ // MCP-REVIEW-1.2: Feedback includes severity categories (in the API response)
122
+ return textResponse(`# Style Check Results for \`${filePath}\`${scopeLabel}\n\n${data.feedback}${scopeNote}${STYLE_CHECK_GUIDANCE}`);
123
+ }
124
+ catch (error) {
125
+ return errorResponse(`Style check error: ${error instanceof Error ? error.message : String(error)}`);
126
+ }
127
+ }
128
+ /**
129
+ * Handler for review_test tool
130
+ *
131
+ * Requirements covered:
132
+ * - MCP-REVIEW-2.0: The review validates that test setup matches requirement preconditions
133
+ * - MCP-REVIEW-2.1: The review validates that test actions match requirement triggers
134
+ * - MCP-REVIEW-2.2: The review validates that test assertions match requirement outcomes
135
+ * - MCP-REVIEW-2.3: Feedback identifies requirements without test coverage
136
+ * - MCP-REVIEW-2.4: Feedback identifies tests that reference non-existent requirements
137
+ * - MCP-REVIEW-2.5: When cloud credentials are unavailable, an error explains how to authenticate
138
+ */
139
+ export async function handleReviewTest(args, context, options) {
140
+ const { testFilePath, projectId } = args;
141
+ const { apiBaseUrl = 'https://app.dotrequirements.io' } = options ?? {};
142
+ const fullPath = resolve(context.workspaceRoot, testFilePath);
143
+ if (!existsSync(fullPath)) {
144
+ return errorResponse(`File not found: ${testFilePath}`);
145
+ }
146
+ // Verify it's a test file
147
+ const isTestFile = /\.(test|spec)\.(js|jsx|ts|tsx)$/.test(testFilePath);
148
+ if (!isTestFile) {
149
+ return errorResponse(`File must be a test file: *.test.{js,jsx,ts,tsx} or *.spec.{js,jsx,ts,tsx}`);
150
+ }
151
+ // Read test file contents
152
+ const testFileContents = readFileSync(fullPath, 'utf-8');
153
+ // Extract requirement IDs from the test file using regex
154
+ const requirementIdMatches = testFileContents.matchAll(/requirement\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g);
155
+ const requirementIds = new Set();
156
+ for (const match of requirementIdMatches) {
157
+ requirementIds.add(match[1]);
158
+ }
159
+ // MCP-REVIEW-2.5: Get project credentials
160
+ let project;
161
+ try {
162
+ project = await context.getProjectFromDiscovery(projectId);
163
+ }
164
+ catch (error) {
165
+ return errorResponse(`Test review requires a configured dotrequirements project.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
166
+ }
167
+ // Load all requirements for this project
168
+ const allRequirements = await context.getRequirements(projectId);
169
+ // Group requirement IDs by their root to avoid sending duplicate trees
170
+ // e.g., NAV-3.0, NAV-3.1, NAV-3.2 all belong to root NAV-3
171
+ const rootToTestedIds = new Map();
172
+ for (const reqId of requirementIds) {
173
+ const req = allRequirements.find(r => r.id === reqId || r.rootId === reqId);
174
+ if (req) {
175
+ if (!rootToTestedIds.has(req.rootId)) {
176
+ rootToTestedIds.set(req.rootId, new Set());
177
+ }
178
+ rootToTestedIds.get(req.rootId).add(reqId);
179
+ }
180
+ }
181
+ // Build requirements array with one entry per unique root tree
182
+ // MCP-REVIEW-2.0, 2.1, 2.2: These validations happen in the API based on the tree structure
183
+ const requirements = [];
184
+ for (const [rootId, testedIds] of rootToTestedIds) {
185
+ const tree = getRequirementTree(allRequirements, rootId);
186
+ const formattedTree = formatRequirementTree(tree);
187
+ requirements.push({
188
+ id: rootId,
189
+ content: formattedTree,
190
+ testedIds: Array.from(testedIds).sort(),
191
+ });
192
+ }
193
+ // Call API endpoint for test review
194
+ // MCP-REVIEW-2.3 & 2.4: API identifies coverage gaps and non-existent references
195
+ try {
196
+ const response = await fetch(`${apiBaseUrl}/api/review-test`, {
197
+ method: 'POST',
198
+ headers: {
199
+ 'Content-Type': 'application/json',
200
+ },
201
+ body: JSON.stringify({
202
+ projectId: project.projectId,
203
+ projectSecret: project.projectSecret,
204
+ testFileContents,
205
+ requirements,
206
+ }),
207
+ });
208
+ if (!response.ok) {
209
+ const errorData = await response.json();
210
+ return errorResponse(`Test review failed: ${errorData.error || response.statusText}`);
211
+ }
212
+ const data = await response.json();
213
+ return textResponse(`# Test Review Results for \`${testFilePath}\`\n\n${data.feedback}`);
214
+ }
215
+ catch (error) {
216
+ return errorResponse(`Test review error: ${error instanceof Error ? error.message : String(error)}`);
217
+ }
218
+ }
219
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1,30 @@
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 root requirement
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
@@ -0,0 +1,60 @@
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 { textResponse, errorResponse } from './types.js';
8
+ import { searchRequirements, getRequirementTree, formatRequirementTree, } from '../requirements.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 root requirement
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) ||
32
+ regex.test(r.content) ||
33
+ regex.test(r.label));
34
+ }
35
+ catch (error) {
36
+ return errorResponse(`Invalid regular expression: ${error instanceof Error ? error.message : String(error)}`);
37
+ }
38
+ }
39
+ else {
40
+ // MCP-SEARCH-1.0, MCP-SEARCH-1.1, MCP-SEARCH-1.2: Text search across ID, content, label
41
+ results = searchRequirements(requirements, query);
42
+ }
43
+ // MCP-SEARCH-1.6: Collect unique root IDs from all matches (nested matches bubble up to their root)
44
+ const matchedRootIds = new Set(results.map((r) => r.rootId));
45
+ const rootResults = requirements.filter((r) => r.path.length === 0 && matchedRootIds.has(r.rootId));
46
+ // MCP-SEARCH-1.5: No matches returns informative message
47
+ if (rootResults.length === 0) {
48
+ return textResponse(`No requirements found matching "${query}"${useRegex ? ' (regex)' : ''}`);
49
+ }
50
+ // MCP-SEARCH-1.7: Each result includes full tree as code block
51
+ const formatted = rootResults
52
+ .map((r) => {
53
+ const tree = getRequirementTree(requirements, r.id);
54
+ const treeFormatted = formatRequirementTree(tree);
55
+ return `**${r.id}** (${r.label})\n${r.content}\n_Source: ${r.documentTitle}_\n\n\`\`\`\n${treeFormatted}\n\`\`\``;
56
+ })
57
+ .join('\n\n---\n\n');
58
+ return textResponse(`Found ${rootResults.length} requirement(s) matching "${query}"${useRegex ? ' (regex)' : ''}:\n\n${formatted}`);
59
+ }
60
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1,39 @@
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