@popoverai/dotrequirements 0.16.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,41 @@
1
+ /**
2
+ * Authoring handlers for MCP tools
3
+ *
4
+ * Provides document creation and validation:
5
+ * - create_requirement_document: Generate requirements template with format guidance
6
+ * - validate_requirements: Validate requirements file syntax offline
7
+ */
8
+ import type { HandlerContext, ToolResponse } from './types.js';
9
+ /**
10
+ * Arguments for create_requirement_document tool
11
+ */
12
+ export interface CreateRequirementDocumentArgs {
13
+ filePath?: string;
14
+ }
15
+ /**
16
+ * Arguments for validate_requirements tool
17
+ */
18
+ export interface ValidateRequirementsArgs {
19
+ filePath: string;
20
+ }
21
+ /**
22
+ * Handler for create_requirement_document tool
23
+ *
24
+ * Requirements covered:
25
+ * - MCP-AUTHOR-1.0: The template includes format guidance with code block examples
26
+ * - MCP-AUTHOR-1.1: The template includes guidance on concrete examples, concise prose, and testable conditions
27
+ * - MCP-AUTHOR-1.2: When the project has requirementsStyleContext configured, it is included in the template
28
+ * - MCP-AUTHOR-1.3: When cloud credentials are unavailable, the template works without the custom context
29
+ */
30
+ export declare function handleCreateRequirementDocument(args: CreateRequirementDocumentArgs, context: HandlerContext): Promise<ToolResponse>;
31
+ /**
32
+ * Handler for validate_requirements tool
33
+ *
34
+ * Requirements covered:
35
+ * - MCP-AUTHOR-2.0: When the file has valid syntax, the response confirms validation passed
36
+ * - MCP-AUTHOR-2.1: When the file has syntax errors, the response lists each error with location
37
+ * - MCP-AUTHOR-2.2: Validation does not require network access or cloud credentials
38
+ * - MCP-AUTHOR-2.3: When the file does not exist, an error is returned
39
+ */
40
+ export declare function handleValidateRequirements(args: ValidateRequirementsArgs, context: HandlerContext): Promise<ToolResponse>;
41
+ //# sourceMappingURL=authoring.d.ts.map
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Authoring handlers for MCP tools
3
+ *
4
+ * Provides document creation and validation:
5
+ * - create_requirement_document: Generate requirements template with format guidance
6
+ * - validate_requirements: Validate requirements file syntax offline
7
+ */
8
+ import { textResponse, errorResponse } from './types.js';
9
+ import { existsSync } from 'fs';
10
+ import { resolve } from 'path';
11
+ import { parseRequirementsFromFile, validateForPush } from '../../schema/index.js';
12
+ import { CONVEX_URL, getProjectContext, } from '../convexClient.js';
13
+ /**
14
+ * Handler for create_requirement_document tool
15
+ *
16
+ * Requirements covered:
17
+ * - MCP-AUTHOR-1.0: The template includes format guidance with code block examples
18
+ * - MCP-AUTHOR-1.1: The template includes guidance on concrete examples, concise prose, and testable conditions
19
+ * - MCP-AUTHOR-1.2: When the project has requirementsStyleContext configured, it is included in the template
20
+ * - MCP-AUTHOR-1.3: When cloud credentials are unavailable, the template works without the custom context
21
+ */
22
+ export async function handleCreateRequirementDocument(args, context) {
23
+ const { filePath = '.requirements/example.requirements.md' } = args;
24
+ // Discover existing label and key patterns in the codebase
25
+ // Try to get requirements, but don't fail if no project is configured
26
+ let requirements = [];
27
+ try {
28
+ requirements = await context.getRequirements();
29
+ }
30
+ catch {
31
+ // MCP-AUTHOR-1.3: No project configured - that's okay, we'll just not have discovered patterns
32
+ }
33
+ const labels = new Set();
34
+ const keyPrefixes = new Set();
35
+ for (const req of requirements) {
36
+ // Collect labels (empty string means unlabeled, so skip those)
37
+ if (req.label && req.label.trim()) {
38
+ labels.add(req.label);
39
+ }
40
+ // Collect requirement key prefixes (root IDs only)
41
+ // Extract prefix from keys like "AUTH-1" -> "AUTH", "LOGIN-FLOW-2" -> "LOGIN-FLOW"
42
+ if (req.path.length === 0 && req.id) {
43
+ const lastDashIndex = req.id.lastIndexOf('-');
44
+ if (lastDashIndex > 0) {
45
+ const prefix = req.id.substring(0, lastDashIndex);
46
+ keyPrefixes.add(prefix);
47
+ }
48
+ }
49
+ }
50
+ const discoveredLabels = Array.from(labels).sort();
51
+ const discoveredPrefixes = Array.from(keyPrefixes).sort();
52
+ let labelGuidance = '';
53
+ let keyGuidance = '';
54
+ // Generate label guidance
55
+ if (discoveredLabels.length > 0) {
56
+ const labelList = discoveredLabels.slice(0, 10).map(l => `"${l}"`).join(', ');
57
+ const more = discoveredLabels.length > 10 ? ` (and ${discoveredLabels.length - 10} more)` : '';
58
+ labelGuidance = `**Existing labels in this codebase:** ${labelList}${more}
59
+
60
+ **Use these existing labels** to maintain consistency. If you're unsure which labels to use for a new requirement, ask the user.`;
61
+ }
62
+ else {
63
+ labelGuidance = `**No existing requirements found in this codebase.**
64
+
65
+ **Default to unlabeled requirements** (\`0. → content\`). If the user wants labels, ask them which format they prefer. Do not choose an opinionated framework like Given/When/Then without explicit user consent.`;
66
+ }
67
+ // Generate key guidance
68
+ if (discoveredPrefixes.length > 0) {
69
+ const prefixList = discoveredPrefixes.map(p => `"${p}"`).join(', ');
70
+ keyGuidance = `**Existing requirement key prefixes in this codebase:** ${prefixList}
71
+
72
+ **Match the existing pattern** when creating new requirement keys. Use the same domain prefixes and sequential numbering style.`;
73
+ }
74
+ else {
75
+ keyGuidance = `**No existing requirements found in this codebase.**
76
+
77
+ **Use concise domain prefixes** like \`AUTH-1\`, \`LOGIN-1\`, etc. Start numbering at 1 and increment sequentially.`;
78
+ }
79
+ // MCP-AUTHOR-1.2: Try to fetch user-provided style context from cloud
80
+ let userStyleGuidance = '';
81
+ try {
82
+ const project = await context.getProjectFromDiscovery();
83
+ const contextData = await getProjectContext(project.projectId, project.projectSecret, CONVEX_URL);
84
+ if (contextData?.requirementsStyleContext?.trim()) {
85
+ userStyleGuidance = `
86
+
87
+ ## User-Provided Style Guidelines
88
+
89
+ The following style guidelines were provided by the project owner. When these conflict with the defaults above, prioritize the user's guidelines.
90
+
91
+ ${contextData.requirementsStyleContext.trim()}`;
92
+ }
93
+ }
94
+ catch {
95
+ // MCP-AUTHOR-1.3: No credentials or cloud unavailable - continue without user context
96
+ }
97
+ // MCP-AUTHOR-1.0: Template with format guidance and code block examples
98
+ // MCP-AUTHOR-1.1: Template includes style principles (concrete examples, concise prose, testable conditions)
99
+ const template = `---
100
+ document:
101
+ title: "Example Requirements"
102
+ ---
103
+
104
+ # Example Requirements
105
+
106
+ This template demonstrates the dotrequirements Markdown format and style guidelines.
107
+
108
+ ## Syntax Overview
109
+
110
+ **File naming:** Use \`*.requirements.md\` pattern (colocated: \`auth.requirements.md\` or centralized: \`.requirements/auth.requirements.md\`)
111
+
112
+ **Block format:**
113
+ \`\`\`dotrequirements
114
+ KEY: Root requirement content
115
+ 0. → First criterion (unlabeled)
116
+ 1. Label → Second criterion (with label)
117
+ 1.0. → Nested criterion (unlabeled)
118
+ \`\`\`
119
+
120
+ - First line: \`KEY: content\` (requirement key and description)
121
+ - Criteria: \`position. Label → content\` or \`position. → content\` (unlabeled)
122
+ - Position: \`0\`, \`1\`, \`2\` (top-level) or \`0.0\`, \`1.0\` (nested) - defines hierarchy
123
+ - Delimiter: \`→\` or \`->\` separates optional label from content
124
+
125
+ ## Requirement Keys: Concise and Sequential
126
+
127
+ ${keyGuidance}
128
+
129
+ **Key format:** \`DOMAIN-FEATURE-N\` where N is sequential (1, 2, 3...)
130
+
131
+ **Best practices:**
132
+ 1. **Concise domains** - Use short, clear prefixes
133
+ - ✅ \`AUTHZ-1\` (authorization)
134
+ - ✅ \`AUTH-1\` (authentication)
135
+ - ❌ \`AUTHORIZATION-1\` (too verbose)
136
+ - ❌ \`REQ-IDENTITY-ACCESS-AUTHZ-1\` (too nested)
137
+
138
+ 2. **Sequential, 1-indexed numbering** - Start at 1, no padding
139
+ - ✅ \`LOGIN-1\`, \`LOGIN-2\`, \`LOGIN-3\`
140
+ - ❌ \`LOGIN-0\` (don't use 0-indexing for requirement IDs)
141
+ - ❌ \`LOGIN-001\` (no zero-padding)
142
+
143
+ 3. **Unique across project** - Each key must be unique in the entire project
144
+
145
+ 4. **Match existing patterns** - Check existing requirements first and follow their convention
146
+
147
+ ## Labels: Match Your Codebase or Ask the User
148
+
149
+ ${labelGuidance}
150
+
151
+ ## Style Principles for Requirements
152
+
153
+ **1. Use Concrete Examples**: Replace vague language with specific, testable conditions.
154
+ - ❌ "users can log in" or "works properly"
155
+ - ✅ "When a registered user provides valid credentials, they are authenticated"
156
+
157
+ **2. Write Natural, Concise Prose**: Avoid terseness and verbosity. Use declarative style (not "should").
158
+ - ❌ "registered user with valid credentials is authenticated" (too terse)
159
+ - ❌ "A registered user, whose account was created on Tuesday and whose life story is as follows..." (too verbose)
160
+ - ✅ "When a registered user provides valid credentials, they are authenticated"
161
+
162
+ **3. Keep Arrange/Act/Assert in Mind**: Well-written requirements describe preconditions, trigger, and result.
163
+ - ✅ "When [preconditions:] a registered user [trigger:] provides valid credentials, [result:] they are authenticated"
164
+
165
+ **4. Be Framework Neutral**: Don't prescribe Given/When/Then vs AC vs other formats - focus on content quality.
166
+
167
+ **5. Use Named Personas**: Establish personas in parent requirements, reuse in children.
168
+ - ✅ Parent: "A registered user, Jamie, can log in normally" → Child: "When Jamie provides valid credentials, they are authenticated"
169
+
170
+ **6. Use User-Centric Language**: Describe user experience, not technical internals.
171
+ - ❌ "they are redirected to app.dotrequirements.io/redirect/dashboard"
172
+ - ✅ "they are automatically brought to the dashboard"
173
+
174
+ **7. Single Action Per Requirement**: Don't chain multiple actions with "and then".
175
+ - ❌ "When Robin provides credentials, requests an OTP, then provides the OTP..."
176
+ - ✅ Break into separate requirements for each action
177
+
178
+ **8. Each Requirement Should Be Independent**: Requirements within a block should be independently testable. If they share preconditions or form a sequence, either nest them or restate context.
179
+ - ❌ "0. → When Jordan submits signup, an account is created" / "1. → Welcome email is sent" / "2. → Dashboard appears"
180
+ - ✅ Option A: Restate context - "1. → When Jordan submits signup, a welcome email is sent"
181
+ - ✅ Option B: Use nesting - "0. When → Jordan submits signup" / " 0.0. Then → an account is created"
182
+ - Test: Can you understand what's being tested by reading just one requirement, or do you need to read its siblings?
183
+
184
+ **9. Focus on Behavior, Not Design**: Describe what happens, not UI specifics.
185
+ - ❌ "enters valid credentials into two single-line input fields and presses a green button"
186
+ - ✅ "provides valid credentials"
187
+
188
+ **10. Focus on Outcomes, Not Implementation**: User perspective, even for technical requirements.
189
+ - ❌ "When the app requests that Twilio send Casey an OTP from /email/POST endpoint..."
190
+ - ✅ "When Casey requests an email OTP..."
191
+ - Note: Even technical requirements can be user-centric: "95 percent of users experience under 1 second of delay"
192
+
193
+ **11. Decompose Large Requirements**: If it can't be validated with a single test, break it down.
194
+ ${userStyleGuidance}
195
+
196
+ ## Example Requirements
197
+
198
+ **Unlabeled (recommended default):**
199
+ \`\`\`dotrequirements
200
+ AUTH-LOGIN-1: A registered user, Jamie, can log in to their account
201
+ 0. → When Jamie provides their registered email and correct password, they are authenticated and brought to their dashboard
202
+ 1. → When Jamie provides an incorrect password, they see an error message and remain on the login page
203
+ 2. → When Jamie's account has been deactivated, they see a message explaining their account status
204
+ \`\`\`
205
+
206
+ **With labels (example only - DO NOT use opinionated formats like Given/When/Then without asking the user first):**
207
+ \`\`\`dotrequirements
208
+ PAYMENT-REFUND-1: A customer, Alex, receives a refund after returning an item
209
+ 0. Given → Alex purchased a laptop from the store 10 days ago
210
+ 1. Given → Alex initiates a return through their order history
211
+ 2. When → Alex's returned laptop is received and inspected at the warehouse
212
+ 3. Then → Alex receives a refund to their original payment method within 5 business days
213
+ 4. Then → Alex receives an email confirmation with the refund amount and expected timeline
214
+ \`\`\`
215
+
216
+ ## Referencing Requirements in Tests
217
+
218
+ **Use \`requirement()\` as the test description** - it returns a string:
219
+
220
+ \`\`\`typescript
221
+ import { describe, it, expect } from 'vitest';
222
+ import { requirement } from '@popoverai/dotrequirements/test';
223
+
224
+ describe(requirement('AUTH-LOGIN-1'), () => {
225
+ describe(requirement('AUTH-LOGIN-1.given'), () => {
226
+ // Arrange: Create Jamie's account
227
+ });
228
+
229
+ describe(requirement('AUTH-LOGIN-1.when'), () => {
230
+ // Act: Submit login with valid credentials
231
+
232
+ it(requirement('AUTH-LOGIN-1.then'), () => {
233
+ // Assert: Jamie is authenticated
234
+ });
235
+ });
236
+ });
237
+ \`\`\`
238
+
239
+ **Test Style Principles:**
240
+
241
+ **1. Use requirement() AS the description**: Don't put requirement() inside test body or in comments.
242
+ - ✅ \`test(requirement('AUTH-LOGIN-1'), () => { /* test code */ })\`
243
+ - ✅ \`it(requirement('LOGIN-1.then'), () => { /* assert */ })\`
244
+ - ❌ \`test("user can log in", () => { requirement('AUTH-LOGIN-1'); /* test code */ })\`
245
+ - ❌ \`// LOGIN-1: User can log in\` (comment instead of using requirement() as description)
246
+
247
+ **2. Comments describe the TEST, not the requirement**: Don't copy requirement text verbatim.
248
+ - ✅ \`describe(requirement('REQ-1.0'), () => { // registered user, valid credentials\`
249
+ - ❌ \`describe(requirement('REQ-1.0'), () => { // 0. When a registered user provides valid credentials, they are authenticated\` (verbatim copy is a red flag)
250
+ - Note: Comments should reflect what the test actually does, not just repeat what the requirement says
251
+
252
+ **3. Structure tests to match requirements**: Nest describe/it blocks for structured requirements.
253
+ - ✅ For structured requirements: \`describe(requirement('LOGIN-1.given'))\` nested with \`describe(requirement('LOGIN-1.when'))\` and \`it(requirement('LOGIN-1.then'))\`
254
+ - ✅ For simple requirements: \`test(requirement('AUTH-LOGIN-1'), () => { /* arrange, act, assert all in one */ })\`
255
+
256
+ **Path formats:**
257
+ - \`requirement('AUTH-LOGIN-1')\` - root requirement
258
+ - \`requirement('AUTH-LOGIN-1.0')\` - by numeric position
259
+ - \`requirement('AUTH-LOGIN-1.given')\` - by label (case-insensitive)
260
+ - \`requirement('AUTH-LOGIN-1.given#1')\` - disambiguate duplicate labels`;
261
+ return textResponse(`# Requirements File Template\n\nHere's a comprehensive template for \`${filePath}\` with format and style guidance:\n\n\`\`\`markdown\n${template}\`\`\`\n\n## Next Steps\n\n1. **Create file**: Use Write tool to create \`${filePath}\` based on this template\n2. **Refine style** (optional): Call \`style_check\` for AI feedback on style and best practices\n3. **Validate syntax**: Call \`validate_requirements\` to verify format is correct\n4. **Push to Convex**: Call \`push_requirements\` to sync requirements to data layer\n\n**Note**: Requirements files can be colocated with code (\`src/auth.requirements.md\`) or centralized in \`.requirements/\` directory.`);
262
+ }
263
+ /**
264
+ * Handler for validate_requirements tool
265
+ *
266
+ * Requirements covered:
267
+ * - MCP-AUTHOR-2.0: When the file has valid syntax, the response confirms validation passed
268
+ * - MCP-AUTHOR-2.1: When the file has syntax errors, the response lists each error with location
269
+ * - MCP-AUTHOR-2.2: Validation does not require network access or cloud credentials
270
+ * - MCP-AUTHOR-2.3: When the file does not exist, an error is returned
271
+ */
272
+ export async function handleValidateRequirements(args, context) {
273
+ const { filePath } = args;
274
+ // MCP-AUTHOR-2.2: Validation works offline - just use workspaceRoot
275
+ const fullPath = resolve(context.workspaceRoot, filePath);
276
+ // MCP-AUTHOR-2.3: Check if file exists
277
+ if (!existsSync(fullPath)) {
278
+ return errorResponse(`File not found: ${filePath}`);
279
+ }
280
+ try {
281
+ // MCP-AUTHOR-2.0 & MCP-AUTHOR-2.1: Parse and validate
282
+ const parsed = parseRequirementsFromFile(fullPath);
283
+ const reqCount = Object.keys(parsed.requirements).length;
284
+ // Check push readiness
285
+ const pushValidation = validateForPush(parsed.metadata);
286
+ let headline;
287
+ let pushDetails = '';
288
+ if (!pushValidation.valid) {
289
+ headline = `❌ \`${filePath}\` - Not push-ready\n- ${pushValidation.reason}`;
290
+ }
291
+ else {
292
+ const actionLabel = pushValidation.action === 'create' ? 'Will create new document' : 'Will update existing document';
293
+ headline = `✓ \`${filePath}\` - ${actionLabel}`;
294
+ if (pushValidation.warning) {
295
+ pushDetails = `\n- ⚠️ ${pushValidation.warning}`;
296
+ }
297
+ }
298
+ return textResponse(`${headline}${pushDetails}\n\n**Metadata:**\n- Version: ${parsed.metadata.version ?? '(none)'}\n- Document: ${parsed.metadata.document?.title || '(none)'}\n- Document ID: ${parsed.metadata.document?.id || '(none)'}\n\n**Requirements:** ${reqCount} root requirement(s) found`);
299
+ }
300
+ catch (error) {
301
+ // MCP-AUTHOR-2.1: Syntax errors are returned with details
302
+ return {
303
+ content: [{ type: 'text', text: `✗ Validation failed for \`${filePath}\`:\n\n${error instanceof Error ? error.message : String(error)}` }],
304
+ isError: true,
305
+ };
306
+ }
307
+ }
308
+ //# sourceMappingURL=authoring.js.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Coverage handlers for MCP tools
3
+ *
4
+ * Provides cloud coverage query functionality:
5
+ * - get_requirement_coverage: Coverage for a specific requirement
6
+ * - get_project_coverage_summary: Project-wide coverage summary
7
+ */
8
+ import type { HandlerContext, ToolResponse } from './types.js';
9
+ /**
10
+ * Arguments for get_requirement_coverage tool
11
+ */
12
+ export interface GetRequirementCoverageArgs {
13
+ requirementKey: string;
14
+ projectId?: string;
15
+ }
16
+ /**
17
+ * Arguments for get_project_coverage_summary tool
18
+ */
19
+ export interface GetProjectCoverageSummaryArgs {
20
+ branch?: string;
21
+ sinceTimestamp?: number;
22
+ projectId?: string;
23
+ }
24
+ /**
25
+ * Handler for get_requirement_coverage tool
26
+ *
27
+ * Requirements covered:
28
+ * - MCP-COVERAGE-1.0: When credentials are valid, the response shows when the requirement was last tested
29
+ * - MCP-COVERAGE-1.1: The response includes the most recent branch and test file where coverage was recorded
30
+ * - MCP-COVERAGE-1.2: When the requirement has no coverage records, the response indicates this
31
+ * - MCP-COVERAGE-1.3: When credentials are missing or invalid, an error explains how to authenticate
32
+ */
33
+ export declare function handleGetRequirementCoverage(args: GetRequirementCoverageArgs, context: HandlerContext): Promise<ToolResponse>;
34
+ /**
35
+ * Handler for get_project_coverage_summary tool
36
+ *
37
+ * Requirements covered:
38
+ * - MCP-COVERAGE-2.0: The response summarizes which requirements have been tested and which have not
39
+ * - MCP-COVERAGE-2.1: When a branch filter is provided, coverage results include only records from that branch
40
+ * - MCP-COVERAGE-2.2: When sinceTimestamp is provided, only coverage recorded after that time is included
41
+ * - MCP-COVERAGE-2.3: When credentials are missing or invalid, an error explains how to authenticate
42
+ */
43
+ export declare function handleGetProjectCoverageSummary(args: GetProjectCoverageSummaryArgs, context: HandlerContext): Promise<ToolResponse>;
44
+ //# sourceMappingURL=coverage.d.ts.map
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Coverage handlers for MCP tools
3
+ *
4
+ * Provides cloud coverage query functionality:
5
+ * - get_requirement_coverage: Coverage for a specific requirement
6
+ * - get_project_coverage_summary: Project-wide coverage summary
7
+ */
8
+ import { textResponse, errorResponse } from './types.js';
9
+ import { CONVEX_URL, getRequirementCoverage as queryRequirementCoverage, getProjectCoverage as queryProjectCoverage, } from '../convexClient.js';
10
+ /**
11
+ * Handler for get_requirement_coverage tool
12
+ *
13
+ * Requirements covered:
14
+ * - MCP-COVERAGE-1.0: When credentials are valid, the response shows when the requirement was last tested
15
+ * - MCP-COVERAGE-1.1: The response includes the most recent branch and test file where coverage was recorded
16
+ * - MCP-COVERAGE-1.2: When the requirement has no coverage records, the response indicates this
17
+ * - MCP-COVERAGE-1.3: When credentials are missing or invalid, an error explains how to authenticate
18
+ */
19
+ export async function handleGetRequirementCoverage(args, context) {
20
+ const { requirementKey, projectId } = args;
21
+ // MCP-COVERAGE-1.3: Get project credentials (throws if not configured)
22
+ let project;
23
+ try {
24
+ project = await context.getProjectFromDiscovery(projectId);
25
+ }
26
+ catch (error) {
27
+ return errorResponse(`Coverage query requires project credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
28
+ }
29
+ // Query cloud for coverage
30
+ const coverage = await queryRequirementCoverage(requirementKey, project.projectId, project.projectSecret, CONVEX_URL);
31
+ // MCP-COVERAGE-1.2: No coverage records
32
+ if (!coverage.lastTestedAt) {
33
+ return textResponse(`**${requirementKey}** has not been tested yet.`);
34
+ }
35
+ // MCP-COVERAGE-1.0: Show when requirement was last tested
36
+ const lastTested = new Date(coverage.lastTestedAt);
37
+ // MCP-COVERAGE-1.1: Include branch and test file
38
+ const testLocation = coverage.testFile
39
+ ? `\n**Test Location:** \`${coverage.testFile}${coverage.testLine ? `:${coverage.testLine}` : ''}\``
40
+ : '';
41
+ let branchInfo = `\n**Branch:** ${coverage.branch}`;
42
+ if (coverage.allBranches.length > 1) {
43
+ const otherBranches = coverage.allBranches
44
+ .filter((b) => b.branch !== coverage.branch)
45
+ .map((b) => `- ${b.branch}: ${new Date(b.lastTestedAt).toISOString()}`)
46
+ .join('\n');
47
+ branchInfo += `\n\n**Also tested on:**\n${otherBranches}`;
48
+ }
49
+ return textResponse(`# Coverage for ${requirementKey}\n\n**Last Tested:** ${lastTested.toISOString()}${branchInfo}${testLocation}`);
50
+ }
51
+ /**
52
+ * Handler for get_project_coverage_summary tool
53
+ *
54
+ * Requirements covered:
55
+ * - MCP-COVERAGE-2.0: The response summarizes which requirements have been tested and which have not
56
+ * - MCP-COVERAGE-2.1: When a branch filter is provided, coverage results include only records from that branch
57
+ * - MCP-COVERAGE-2.2: When sinceTimestamp is provided, only coverage recorded after that time is included
58
+ * - MCP-COVERAGE-2.3: When credentials are missing or invalid, an error explains how to authenticate
59
+ */
60
+ export async function handleGetProjectCoverageSummary(args, context) {
61
+ const { branch, sinceTimestamp, projectId } = args;
62
+ // MCP-COVERAGE-2.3: Get project credentials (throws if not configured)
63
+ let project;
64
+ try {
65
+ project = await context.getProjectFromDiscovery(projectId);
66
+ }
67
+ catch (error) {
68
+ return errorResponse(`Coverage query requires project credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
69
+ }
70
+ // Query cloud for project coverage with optional filters
71
+ const coverage = await queryProjectCoverage(project.projectId, project.projectSecret, CONVEX_URL, { branch, sinceTimestamp });
72
+ // MCP-COVERAGE-2.0: Calculate and display coverage summary
73
+ const total = coverage.tested.length + coverage.untested.length;
74
+ const percentage = total > 0 ? ((coverage.tested.length / total) * 100).toFixed(1) : '0.0';
75
+ // MCP-COVERAGE-2.1 & MCP-COVERAGE-2.2: Show filter info if provided
76
+ let filterInfo = '';
77
+ if (branch) {
78
+ filterInfo += `\n**Branch:** ${branch}`;
79
+ }
80
+ if (sinceTimestamp) {
81
+ filterInfo += `\n**Since:** ${new Date(sinceTimestamp).toISOString()}`;
82
+ }
83
+ let testedSection = '';
84
+ if (coverage.tested.length > 0) {
85
+ const testedList = coverage.tested
86
+ .map((t) => {
87
+ const date = new Date(t.lastTestedAt).toISOString();
88
+ const location = t.testFile
89
+ ? ` (\`${t.testFile}${t.testLine ? `:${t.testLine}` : ''}\`)`
90
+ : '';
91
+ return `- **${t.requirementKey}** [${t.branch}]: ${date}${location}`;
92
+ })
93
+ .join('\n');
94
+ testedSection = `\n\n## Tested Requirements (${coverage.tested.length})\n\n${testedList}`;
95
+ }
96
+ let untestedSection = '';
97
+ if (coverage.untested.length > 0) {
98
+ const untestedList = coverage.untested
99
+ .map((key) => `- ${key}`)
100
+ .join('\n');
101
+ untestedSection = `\n\n## Untested Requirements (${coverage.untested.length})\n\n${untestedList}`;
102
+ }
103
+ return textResponse(`# Project Coverage Summary\n\n**Total Requirements:** ${total}\n**Coverage:** ${percentage}%${filterInfo}${testedSection}${untestedSection}`);
104
+ }
105
+ //# sourceMappingURL=coverage.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * debug_mcp_environment handler
3
+ *
4
+ * Returns diagnostic information about the MCP server environment.
5
+ * Useful for debugging configuration issues.
6
+ */
7
+ import type { HandlerContext, ToolResponse } from './types.js';
8
+ /**
9
+ * Arguments for debug_mcp_environment tool
10
+ */
11
+ export interface DebugMcpEnvironmentArgs {
12
+ }
13
+ /**
14
+ * Handler for debug_mcp_environment tool
15
+ */
16
+ export declare function handleDebugMcpEnvironment(_args: DebugMcpEnvironmentArgs, context: HandlerContext): Promise<ToolResponse>;
17
+ //# sourceMappingURL=debug.d.ts.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * debug_mcp_environment handler
3
+ *
4
+ * Returns diagnostic information about the MCP server environment.
5
+ * Useful for debugging configuration issues.
6
+ */
7
+ import { textResponse } from './types.js';
8
+ /**
9
+ * Handler for debug_mcp_environment tool
10
+ */
11
+ export async function handleDebugMcpEnvironment(_args, context) {
12
+ // Check for Antigravity-specific env vars
13
+ const antigravityVars = Object.entries(context.env)
14
+ .filter(([key]) => key.startsWith('GEMINI_') || key.startsWith('ANTIGRAVITY_'))
15
+ .map(([key, value]) => ` ${key}: ${value}`)
16
+ .join('\n');
17
+ // Show discovered projects from env vars
18
+ const projectList = Array.from(context.projectPaths.entries())
19
+ .map(([id, path]) => ` ${id}: ${path}`)
20
+ .join('\n');
21
+ const text = `# MCP Server Environment Debug Info
22
+
23
+ **process.cwd():** ${process.cwd()}
24
+ **WORKSPACE_ROOT:** ${context.workspaceRoot}
25
+ **REQUIREMENTS_DIR env:** ${context.env.REQUIREMENTS_DIR || '(not set)'}
26
+ **HOME:** ${context.env.HOME || '(not set)'}
27
+ **PWD:** ${context.env.PWD || '(not set)'}
28
+ **OLDPWD:** ${context.env.OLDPWD || '(not set)'}
29
+
30
+ **Antigravity/Gemini env vars:**
31
+ ${antigravityVars || '(none found)'}
32
+
33
+ **Projects from PROJ_* env vars:**
34
+ ${projectList || '(none found)'}`;
35
+ return textResponse(text);
36
+ }
37
+ //# sourceMappingURL=debug.js.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * get_requirement handler
3
+ *
4
+ * Retrieves a specific requirement by ID with its full tree and test coverage.
5
+ */
6
+ import type { HandlerContext, ToolResponse } from './types.js';
7
+ /**
8
+ * Arguments for get_requirement tool
9
+ */
10
+ export interface GetRequirementArgs {
11
+ id: string;
12
+ projectId?: string;
13
+ }
14
+ /**
15
+ * Handler for get_requirement tool
16
+ *
17
+ * Requirements covered:
18
+ * - MCP-GET-1.0: When the ID matches a root requirement, the full tree is returned
19
+ * - MCP-GET-1.1: When the ID matches a child requirement, that subtree is returned
20
+ * - MCP-GET-1.2: When the ID does not exist, an error message is returned
21
+ * - MCP-GET-1.3: When a requirement is retrieved, test references are shown with file path and line number
22
+ */
23
+ export declare function handleGetRequirement(args: GetRequirementArgs, context: HandlerContext): Promise<ToolResponse>;
24
+ //# sourceMappingURL=get.d.ts.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * get_requirement handler
3
+ *
4
+ * Retrieves a specific requirement by ID with its full tree and test coverage.
5
+ */
6
+ import { textResponse } from './types.js';
7
+ import { glob } from 'glob';
8
+ import { getRequirementTree, formatRequirementTree, } from '../requirements.js';
9
+ import { findFilesWithRequirement, findTestCodeForRequirement, } from '../testCodeExtractor.js';
10
+ /**
11
+ * Handler for get_requirement tool
12
+ *
13
+ * Requirements covered:
14
+ * - MCP-GET-1.0: When the ID matches a root requirement, the full tree is returned
15
+ * - MCP-GET-1.1: When the ID matches a child requirement, that subtree is returned
16
+ * - MCP-GET-1.2: When the ID does not exist, an error message is returned
17
+ * - MCP-GET-1.3: When a requirement is retrieved, test references are shown with file path and line number
18
+ */
19
+ export async function handleGetRequirement(args, context) {
20
+ const { id, projectId } = args;
21
+ const project = await context.getProjectFromDiscovery(projectId);
22
+ const requirements = await context.getRequirements(projectId);
23
+ // Get the tree starting from this ID
24
+ // MCP-GET-1.0: Full tree for root requirement
25
+ // MCP-GET-1.1: Subtree for child requirement
26
+ const tree = getRequirementTree(requirements, id);
27
+ // MCP-GET-1.2: Return error if not found
28
+ if (tree.length === 0) {
29
+ return textResponse(`Requirement "${id}" not found`);
30
+ }
31
+ const root = tree[0];
32
+ const formatted = formatRequirementTree(tree);
33
+ // MCP-GET-1.3: Find test files that reference this requirement
34
+ const testFiles = await glob('**/*.{test,spec}.{js,jsx,ts,tsx}', {
35
+ cwd: project.path,
36
+ absolute: true,
37
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**'],
38
+ });
39
+ const matchingFiles = findFilesWithRequirement(testFiles, id);
40
+ const testCodeSections = [];
41
+ for (const file of matchingFiles) {
42
+ const testRefs = findTestCodeForRequirement(file, id);
43
+ // Deduplicate by removing nested blocks - keep only outermost blocks
44
+ const deduplicated = testRefs.filter((ref, i) => {
45
+ // Check if this ref is contained within any other ref
46
+ const isNested = testRefs.some((other, j) => {
47
+ if (i === j)
48
+ return false;
49
+ // other contains ref if it starts before or at the same line and ends after or at the same line
50
+ return other.startLine <= ref.startLine && other.endLine >= ref.endLine &&
51
+ (other.startLine < ref.startLine || other.endLine > ref.endLine);
52
+ });
53
+ return !isNested;
54
+ });
55
+ for (const ref of deduplicated) {
56
+ testCodeSections.push(`**${ref.requirementId}** tested in \`${ref.file}:${ref.startLine}-${ref.endLine}\`:\n\n\`\`\`typescript\n${ref.code}\n\`\`\``);
57
+ }
58
+ }
59
+ const testSection = testCodeSections.length > 0
60
+ ? `\n\n## Test Coverage (${testCodeSections.length} reference(s)):\n\n${testCodeSections.join('\n\n---\n\n')}`
61
+ : '\n\n## Test Coverage\n\nNo tests found referencing this requirement.';
62
+ return textResponse(`# ${id}\n\n**Document:** ${root.documentTitle}\n**Source:** ${root.sourceFile}\n\n## Requirement Tree (${tree.length} node(s)):\n\n\`\`\`\n${formatted}\n\`\`\`${testSection}`);
63
+ }
64
+ //# sourceMappingURL=get.js.map