@popoverai/dotrequirements 0.11.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.
Files changed (127) hide show
  1. package/README.md +478 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.js +82 -0
  4. package/dist/commands/init.d.ts +6 -0
  5. package/dist/commands/init.js +355 -0
  6. package/dist/commands/link.d.ts +15 -0
  7. package/dist/commands/link.js +156 -0
  8. package/dist/commands/login.d.ts +12 -0
  9. package/dist/commands/login.js +117 -0
  10. package/dist/commands/logout.d.ts +5 -0
  11. package/dist/commands/logout.js +17 -0
  12. package/dist/commands/mcp-setup.d.ts +5 -0
  13. package/dist/commands/mcp-setup.js +367 -0
  14. package/dist/commands/mcp.d.ts +6 -0
  15. package/dist/commands/mcp.js +10 -0
  16. package/dist/commands/pull.d.ts +7 -0
  17. package/dist/commands/pull.js +171 -0
  18. package/dist/commands/push.d.ts +11 -0
  19. package/dist/commands/push.js +310 -0
  20. package/dist/commands/test.d.ts +6 -0
  21. package/dist/commands/test.js +78 -0
  22. package/dist/config.d.ts +5 -0
  23. package/dist/config.js +16 -0
  24. package/dist/convex.d.ts +56 -0
  25. package/dist/convex.js +58 -0
  26. package/dist/harness/cache.d.ts +135 -0
  27. package/dist/harness/cache.js +342 -0
  28. package/dist/harness/convexReporting.d.ts +15 -0
  29. package/dist/harness/convexReporting.js +136 -0
  30. package/dist/harness/coverageCache.d.ts +30 -0
  31. package/dist/harness/coverageCache.js +70 -0
  32. package/dist/harness/finalize.d.ts +48 -0
  33. package/dist/harness/finalize.js +299 -0
  34. package/dist/harness/index.d.ts +70 -0
  35. package/dist/harness/index.js +103 -0
  36. package/dist/harness/localReporting.d.ts +6 -0
  37. package/dist/harness/localReporting.js +49 -0
  38. package/dist/harness/prepare.d.ts +41 -0
  39. package/dist/harness/prepare.js +83 -0
  40. package/dist/harness/requirementsLoader.d.ts +45 -0
  41. package/dist/harness/requirementsLoader.js +201 -0
  42. package/dist/harness/tracking.d.ts +49 -0
  43. package/dist/harness/tracking.js +179 -0
  44. package/dist/harness/types.d.ts +12 -0
  45. package/dist/harness/types.js +6 -0
  46. package/dist/mcp/convexClient.d.ts +43 -0
  47. package/dist/mcp/convexClient.js +101 -0
  48. package/dist/mcp/grep.d.ts +24 -0
  49. package/dist/mcp/grep.js +261 -0
  50. package/dist/mcp/index.d.ts +3 -0
  51. package/dist/mcp/index.js +1758 -0
  52. package/dist/mcp/requirements.d.ts +47 -0
  53. package/dist/mcp/requirements.js +141 -0
  54. package/dist/mcp/testCodeExtractor.d.ts +22 -0
  55. package/dist/mcp/testCodeExtractor.js +152 -0
  56. package/dist/mcp/types.d.ts +27 -0
  57. package/dist/mcp/types.js +2 -0
  58. package/dist/schema/browser.d.ts +12 -0
  59. package/dist/schema/browser.js +24 -0
  60. package/dist/schema/builder.d.ts +25 -0
  61. package/dist/schema/builder.js +125 -0
  62. package/dist/schema/conversions.d.ts +69 -0
  63. package/dist/schema/conversions.js +201 -0
  64. package/dist/schema/index.d.ts +14 -0
  65. package/dist/schema/index.js +24 -0
  66. package/dist/schema/parser-core.d.ts +61 -0
  67. package/dist/schema/parser-core.js +247 -0
  68. package/dist/schema/parser.d.ts +44 -0
  69. package/dist/schema/parser.js +295 -0
  70. package/dist/schema/resolver.d.ts +66 -0
  71. package/dist/schema/resolver.js +185 -0
  72. package/dist/schema/schemas.d.ts +312 -0
  73. package/dist/schema/schemas.js +258 -0
  74. package/dist/schema/test-schema.d.ts +5 -0
  75. package/dist/schema/test-schema.js +81 -0
  76. package/dist/templates/antigravity-gemini.md +3 -0
  77. package/dist/templates/antigravity-overview-rule.md +3 -0
  78. package/dist/templates/antigravity-test-rule.md +3 -0
  79. package/dist/templates/behavioral-core.md +25 -0
  80. package/dist/templates/claude-code-overview-skill.md +6 -0
  81. package/dist/templates/claude-code-skill.md +6 -0
  82. package/dist/templates/claude-code-test-skill.md +6 -0
  83. package/dist/templates/codex-agents.md +3 -0
  84. package/dist/templates/codex-overview-agents.md +3 -0
  85. package/dist/templates/codex-test-agents.md +3 -0
  86. package/dist/templates/cursor-overview-rule.mdc +5 -0
  87. package/dist/templates/cursor-rule.mdc +5 -0
  88. package/dist/templates/cursor-test-rule.mdc +5 -0
  89. package/dist/templates/example-requirements.d.ts +8 -0
  90. package/dist/templates/example-requirements.js +88 -0
  91. package/dist/templates/example-requirements.ts +88 -0
  92. package/dist/templates/overview-core.md +27 -0
  93. package/dist/templates/requirements-readme.d.ts +5 -0
  94. package/dist/templates/requirements-readme.js +31 -0
  95. package/dist/templates/requirements-readme.ts +30 -0
  96. package/dist/templates/test-writing-core.md +72 -0
  97. package/dist/utils/brand.d.ts +5 -0
  98. package/dist/utils/brand.js +8 -0
  99. package/dist/utils/browser-launch.d.ts +19 -0
  100. package/dist/utils/browser-launch.js +36 -0
  101. package/dist/utils/detect-existing-project.d.ts +5 -0
  102. package/dist/utils/detect-existing-project.js +34 -0
  103. package/dist/utils/env.d.ts +19 -0
  104. package/dist/utils/env.js +56 -0
  105. package/dist/utils/gitignore.d.ts +7 -0
  106. package/dist/utils/gitignore.js +29 -0
  107. package/dist/utils/local-project.d.ts +31 -0
  108. package/dist/utils/local-project.js +33 -0
  109. package/dist/utils/oauth-callback-server.d.ts +28 -0
  110. package/dist/utils/oauth-callback-server.js +156 -0
  111. package/dist/utils/oauth-flow.d.ts +22 -0
  112. package/dist/utils/oauth-flow.js +120 -0
  113. package/dist/utils/project-discovery.d.ts +57 -0
  114. package/dist/utils/project-discovery.js +146 -0
  115. package/dist/utils/project-name.d.ts +8 -0
  116. package/dist/utils/project-name.js +48 -0
  117. package/dist/utils/project-selector.d.ts +25 -0
  118. package/dist/utils/project-selector.js +69 -0
  119. package/dist/utils/prompts.d.ts +33 -0
  120. package/dist/utils/prompts.js +60 -0
  121. package/dist/utils/templates.d.ts +29 -0
  122. package/dist/utils/templates.js +67 -0
  123. package/dist/utils/token-refresh.d.ts +24 -0
  124. package/dist/utils/token-refresh.js +69 -0
  125. package/dist/utils/token-storage.d.ts +31 -0
  126. package/dist/utils/token-storage.js +57 -0
  127. package/package.json +82 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Finalize function for the test harness.
3
+ *
4
+ * Called after tests complete (e.g., in Vitest globalTeardown) to:
5
+ * 1. Read tracking data
6
+ * 2. Generate local coverage report
7
+ * 3. Report to Convex cloud (if configured)
8
+ * 4. Clean up tracking data
9
+ *
10
+ * Implements: HARNESS-FINALIZE-1, HARNESS-FINALIZE-2, HARNESS-FINALIZE-3, HARNESS-FINALIZE-4
11
+ */
12
+ import { execSync } from 'child_process';
13
+ import { findRequirementsDir, findProjectRoot, getTestRunId, cleanupTestRunId, readTrackingEntries, deleteTrackingFile, readLookupCache, readCoverageCache, updateCoverageCache, needsReporting, } from './cache.js';
14
+ import { loadEnvFile, getProjectCredentials } from '../utils/env.js';
15
+ import { isLocalOnlyProject } from '../utils/local-project.js';
16
+ /**
17
+ * Aggregate tracking entries by requirement key
18
+ */
19
+ function aggregateEntries(entries) {
20
+ const byKey = new Map();
21
+ for (const entry of entries) {
22
+ const existing = byKey.get(entry.requirementKey) || [];
23
+ existing.push(entry);
24
+ byKey.set(entry.requirementKey, existing);
25
+ }
26
+ return byKey;
27
+ }
28
+ /**
29
+ * Get the current git branch
30
+ */
31
+ function getCurrentBranch(cwd) {
32
+ try {
33
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
34
+ cwd,
35
+ encoding: 'utf-8',
36
+ stdio: ['pipe', 'pipe', 'ignore'],
37
+ }).trim();
38
+ return branch;
39
+ }
40
+ catch {
41
+ return 'unknown';
42
+ }
43
+ }
44
+ /**
45
+ * Print local coverage report to console
46
+ *
47
+ * HARNESS-FINALIZE-1: Shows which requirements were tested
48
+ */
49
+ function printLocalReport(testedKeys, lookup) {
50
+ const allKeys = lookup ? Object.keys(lookup.requirements) : [];
51
+ const untestedKeys = allKeys.filter(key => !testedKeys.includes(key));
52
+ const total = allKeys.length;
53
+ const tested = testedKeys.length;
54
+ const coverage = total > 0 ? ((tested / total) * 100).toFixed(1) : '0.0';
55
+ let report = '\n=== Requirements Coverage Report ===\n';
56
+ report += `\nTotal Requirements: ${total}\n`;
57
+ report += `Tested Requirements: ${tested}\n`;
58
+ report += `Untested Requirements: ${untestedKeys.length}\n`;
59
+ report += `Coverage: ${coverage}%\n`;
60
+ // Show tested requirements
61
+ if (testedKeys.length > 0) {
62
+ report += '\n✓ Tested Requirements:\n';
63
+ for (const key of testedKeys) {
64
+ const req = lookup?.requirements[key];
65
+ if (req) {
66
+ const label = req.label || '';
67
+ const content = req.content || '';
68
+ const preview = content.length > 60 ? content.substring(0, 60) + '...' : content;
69
+ const labelPart = label ? ` (${label})` : '';
70
+ report += ` - ${key}${labelPart}: ${preview}\n`;
71
+ }
72
+ else {
73
+ report += ` - ${key}\n`;
74
+ }
75
+ }
76
+ }
77
+ // Show untested requirements
78
+ if (untestedKeys.length > 0) {
79
+ report += '\n✗ Untested Requirements:\n';
80
+ for (const key of untestedKeys) {
81
+ const req = lookup?.requirements[key];
82
+ if (req) {
83
+ const label = req.label || '';
84
+ const content = req.content || '';
85
+ const preview = content.length > 60 ? content.substring(0, 60) + '...' : content;
86
+ const labelPart = label ? ` (${label})` : '';
87
+ report += ` - ${key}${labelPart}: ${preview}\n`;
88
+ }
89
+ else {
90
+ report += ` - ${key}\n`;
91
+ }
92
+ }
93
+ }
94
+ report += '\n====================================\n';
95
+ console.log(report);
96
+ }
97
+ /**
98
+ * Production Convex deployment URL
99
+ */
100
+ const CONVEX_URL = 'https://data.dotrequirements.io';
101
+ /**
102
+ * Report coverage to Convex cloud
103
+ *
104
+ * HARNESS-FINALIZE-2: Cloud reporting with error tolerance
105
+ * HARNESS-FINALIZE-3: Coverage records include requirement, file, line, branch
106
+ */
107
+ async function reportToConvex(projectRoot, testRunId, aggregated) {
108
+ try {
109
+ // Load credentials
110
+ loadEnvFile(projectRoot);
111
+ const credentials = getProjectCredentials();
112
+ if (!credentials) {
113
+ console.log('\nℹ️ Skipping cloud coverage reporting (DOTREQUIREMENTS_PROJECT_ID or DOTREQUIREMENTS_PROJECT_SECRET not configured)');
114
+ return { sent: false, count: 0 };
115
+ }
116
+ const { projectId, projectSecret } = credentials;
117
+ // Skip for local-only projects
118
+ if (isLocalOnlyProject(projectId)) {
119
+ return { sent: false, count: 0 };
120
+ }
121
+ const requirementsDir = findRequirementsDir(projectRoot);
122
+ // Get all tracked requirement keys
123
+ const requirementKeys = Array.from(aggregated.keys());
124
+ // Use coverage cache for deduplication
125
+ const coverageCache = readCoverageCache(requirementsDir);
126
+ const keysToReport = requirementKeys.filter(key => needsReporting(key, coverageCache));
127
+ if (keysToReport.length === 0) {
128
+ console.log('\n✓ Coverage unchanged since last run (skipping cloud report)');
129
+ // Still update the cache
130
+ updateCoverageCache(requirementsDir, testRunId, requirementKeys);
131
+ return { sent: false, count: 0 };
132
+ }
133
+ const branch = getCurrentBranch(projectRoot);
134
+ // Build coverage payload
135
+ // HARNESS-FINALIZE-3: Include requirement path, test file, line, branch
136
+ const coverage = keysToReport.map(key => {
137
+ const entries = aggregated.get(key);
138
+ // Use the first access location
139
+ const firstEntry = entries[0];
140
+ const location = firstEntry.callerLocation;
141
+ // Parse "filename.ts:42" format
142
+ let testFile;
143
+ let testLine;
144
+ if (location !== 'unknown') {
145
+ const match = location.match(/^(.+):(\d+)$/);
146
+ if (match) {
147
+ testFile = match[1];
148
+ testLine = parseInt(match[2], 10);
149
+ }
150
+ else {
151
+ testFile = location;
152
+ }
153
+ }
154
+ return {
155
+ requirementKey: key,
156
+ testFile,
157
+ testLine,
158
+ };
159
+ });
160
+ // Send to Convex
161
+ // Note: projectId from env is always a slug (e.g., "reduced-cephalopod-288"),
162
+ // not a Convex ID. Use projectSlug field for authentication and slug for target.
163
+ const response = await fetch(`${CONVEX_URL}/api/mutation`, {
164
+ method: 'POST',
165
+ headers: {
166
+ 'Content-Type': 'application/json',
167
+ },
168
+ body: JSON.stringify({
169
+ path: 'testCoverage/mutations:recordCoverage',
170
+ args: {
171
+ projectAuth: {
172
+ projectSlug: projectId,
173
+ projectSecret,
174
+ },
175
+ target: {
176
+ type: 'project',
177
+ slug: projectId,
178
+ },
179
+ branch,
180
+ coverage,
181
+ },
182
+ format: 'json',
183
+ }),
184
+ });
185
+ if (!response.ok) {
186
+ const error = await response.text();
187
+ console.warn(`\n⚠️ Failed to report coverage to cloud: ${response.status} ${error}`);
188
+ return { sent: false, count: 0, error: `${response.status} ${error}` };
189
+ }
190
+ // Check for Convex-level errors in the response body
191
+ // Convex returns 200 OK even for validation errors, with the error in the body
192
+ const responseText = await response.text();
193
+ let responseData;
194
+ try {
195
+ responseData = JSON.parse(responseText);
196
+ }
197
+ catch {
198
+ // Not JSON, treat as success
199
+ }
200
+ if (responseData && typeof responseData === 'object' && 'status' in responseData) {
201
+ const convexResponse = responseData;
202
+ if (convexResponse.status === 'error') {
203
+ const errorMsg = convexResponse.errorMessage || 'Unknown Convex error';
204
+ console.warn(`\n⚠️ Failed to report coverage to cloud: ${errorMsg}`);
205
+ return { sent: false, count: 0, error: errorMsg };
206
+ }
207
+ }
208
+ console.log(`\n✓ Reported ${keysToReport.length} requirement(s) to cloud (branch: ${branch})`);
209
+ // Update coverage cache
210
+ updateCoverageCache(requirementsDir, testRunId, requirementKeys);
211
+ return { sent: true, count: keysToReport.length };
212
+ }
213
+ catch (error) {
214
+ const errorMessage = error instanceof Error ? error.message : String(error);
215
+ console.warn(`\n⚠️ Error reporting coverage to cloud: ${errorMessage}`);
216
+ return { sent: false, count: 0, error: errorMessage };
217
+ }
218
+ }
219
+ /**
220
+ * Finalize the test run after all tests complete.
221
+ *
222
+ * This function should be called once after tests complete (e.g., in globalTeardown).
223
+ * It reads tracking data, generates reports, and cleans up.
224
+ *
225
+ * HARNESS-FINALIZE-1: After finalize() runs, developers see which requirements were tested
226
+ * HARNESS-FINALIZE-2: After finalize() runs, coverage data is available in the cloud
227
+ * HARNESS-FINALIZE-3: Cloud coverage records identify which requirement was tested and where
228
+ * HARNESS-FINALIZE-4: Finalize cleans up after itself
229
+ */
230
+ export async function finalize(options = {}) {
231
+ const { cwd = process.cwd(), reportToCloud: shouldReportToCloud = true, printLocalReport: shouldPrintLocal = true, cleanup: shouldCleanup = true, } = options;
232
+ // Priority 1: Use environment variable (cross-process persistence from globalSetup)
233
+ let projectRoot = process.env.DOTREQUIREMENTS_PROJECT_ROOT || null;
234
+ // Priority 2: Find from cwd
235
+ if (!projectRoot) {
236
+ projectRoot = findProjectRoot(cwd);
237
+ }
238
+ if (!projectRoot) {
239
+ console.log('Could not find .requirements directory');
240
+ return {
241
+ requirementsTested: 0,
242
+ totalRequirements: 0,
243
+ coveragePercent: 0,
244
+ cloudReportSent: false,
245
+ requirementsReportedToCloud: 0,
246
+ };
247
+ }
248
+ const requirementsDir = findRequirementsDir(projectRoot);
249
+ // Get test run ID
250
+ const testRunId = getTestRunId(requirementsDir);
251
+ if (!testRunId) {
252
+ console.log('No test run ID found - was prepare() called?');
253
+ return {
254
+ requirementsTested: 0,
255
+ totalRequirements: 0,
256
+ coveragePercent: 0,
257
+ cloudReportSent: false,
258
+ requirementsReportedToCloud: 0,
259
+ };
260
+ }
261
+ // Read tracking entries
262
+ const entries = readTrackingEntries(requirementsDir);
263
+ // Aggregate by requirement key
264
+ const aggregated = aggregateEntries(entries);
265
+ const testedKeys = Array.from(aggregated.keys());
266
+ // Read lookup cache for report
267
+ const lookup = readLookupCache(requirementsDir);
268
+ const totalRequirements = lookup ? Object.keys(lookup.requirements).length : 0;
269
+ const coveragePercent = totalRequirements > 0
270
+ ? (testedKeys.length / totalRequirements) * 100
271
+ : 0;
272
+ // HARNESS-FINALIZE-1: Print local report
273
+ if (shouldPrintLocal) {
274
+ printLocalReport(testedKeys, lookup);
275
+ }
276
+ // HARNESS-FINALIZE-2, HARNESS-FINALIZE-3: Report to cloud
277
+ let cloudResult = { sent: false, count: 0 };
278
+ if (shouldReportToCloud) {
279
+ cloudResult = await reportToConvex(projectRoot, testRunId, aggregated);
280
+ }
281
+ // HARNESS-FINALIZE-4: Clean up tracking data
282
+ if (shouldCleanup && !cloudResult.error) {
283
+ // HARNESS-FINALIZE-4.0: Remove tracking data from this test run
284
+ deleteTrackingFile(requirementsDir);
285
+ // Clean up test run ID file
286
+ cleanupTestRunId(requirementsDir);
287
+ // HARNESS-FINALIZE-4.1: Lookup cache is preserved (no deletion)
288
+ }
289
+ // HARNESS-FINALIZE-4.2: On error, tracking data is preserved for debugging
290
+ return {
291
+ requirementsTested: testedKeys.length,
292
+ totalRequirements,
293
+ coveragePercent,
294
+ cloudReportSent: cloudResult.sent,
295
+ requirementsReportedToCloud: cloudResult.count,
296
+ cloudError: cloudResult.error,
297
+ };
298
+ }
299
+ //# sourceMappingURL=finalize.js.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Test Harness for dot•requirements.
3
+ *
4
+ * This module provides the test harness for tracking requirement coverage.
5
+ *
6
+ * Usage:
7
+ *
8
+ * ```typescript
9
+ * // In globalSetup (e.g., vitest.setup.ts)
10
+ * import { prepare } from '@popoverai/dotrequirements/test';
11
+ * export default function() {
12
+ * prepare();
13
+ * }
14
+ *
15
+ * // In tests
16
+ * import { requirement } from '@popoverai/dotrequirements/test';
17
+ * describe(requirement('REQ-123'), () => {
18
+ * it(requirement('REQ-123.0'), () => {
19
+ * // test implementation
20
+ * });
21
+ * });
22
+ *
23
+ * // In globalTeardown
24
+ * import { finalize } from '@popoverai/dotrequirements/test';
25
+ * export default async function() {
26
+ * await finalize();
27
+ * }
28
+ * ```
29
+ */
30
+ /**
31
+ * Options for the requirement() function.
32
+ */
33
+ export interface RequirementOptions {
34
+ /** Explicit project root path for loading requirements (useful when cwd is changed) */
35
+ projectRoot?: string;
36
+ }
37
+ /**
38
+ * Main requirements function for test harness.
39
+ *
40
+ * HARNESS-REQUIREMENT-1: Test frameworks can use requirement() as test descriptions
41
+ * HARNESS-REQUIREMENT-2: Invalid requirement paths fail the individual test
42
+ * HARNESS-REQUIREMENT-3: Each requirement() call is recorded for coverage reporting
43
+ * HARNESS-REQUIREMENT-4: Developers can run tests without calling prepare() first
44
+ * HARNESS-REQUIREMENT-5: A single test can exercise multiple requirements
45
+ *
46
+ * Supports both numeric and label-based paths.
47
+ * Accepts multiple requirement refs to track coverage for multiple requirements in one test.
48
+ *
49
+ * Examples:
50
+ * - requirement('REQ-123') - Returns root requirement content
51
+ * - requirement('REQ-123.0') - Returns first child (numeric path)
52
+ * - requirement('REQ-123.given') - Returns first "given" child (label path)
53
+ * - requirement('REQ-123.given#1') - Returns second "given" child
54
+ * - requirement('REQ-123.0.1') - Returns nested child
55
+ * - requirement('REQ-123.then.and') - Returns first "and" under first "then"
56
+ * - requirement('REQ-AUTH', 'REQ-SECURITY') - Tracks both, returns first
57
+ * - requirement('REQ-123', { projectRoot: '/path/to/project' }) - Explicit project root
58
+ *
59
+ * @param args - One or more requirement reference paths, optionally followed by options
60
+ * @returns A formatted string with the first requirement's label and content
61
+ */
62
+ export declare function requirement(...args: (string | RequirementOptions)[]): string;
63
+ export { prepare } from './prepare.js';
64
+ export type { PrepareOptions, PrepareResult } from './prepare.js';
65
+ export { finalize } from './finalize.js';
66
+ export type { FinalizeOptions, FinalizeResult } from './finalize.js';
67
+ export { clearTracking } from './tracking.js';
68
+ export { initTestRun, finalizeTestRun, } from './tracking.js';
69
+ export type { Requirement } from './types.js';
70
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Test Harness for dot•requirements.
3
+ *
4
+ * This module provides the test harness for tracking requirement coverage.
5
+ *
6
+ * Usage:
7
+ *
8
+ * ```typescript
9
+ * // In globalSetup (e.g., vitest.setup.ts)
10
+ * import { prepare } from '@popoverai/dotrequirements/test';
11
+ * export default function() {
12
+ * prepare();
13
+ * }
14
+ *
15
+ * // In tests
16
+ * import { requirement } from '@popoverai/dotrequirements/test';
17
+ * describe(requirement('REQ-123'), () => {
18
+ * it(requirement('REQ-123.0'), () => {
19
+ * // test implementation
20
+ * });
21
+ * });
22
+ *
23
+ * // In globalTeardown
24
+ * import { finalize } from '@popoverai/dotrequirements/test';
25
+ * export default async function() {
26
+ * await finalize();
27
+ * }
28
+ * ```
29
+ */
30
+ import { getRequirement, loadRequirements } from './requirementsLoader.js';
31
+ import { ensureTestRun, trackRequirement, saveTrackingData } from './tracking.js';
32
+ /**
33
+ * Main requirements function for test harness.
34
+ *
35
+ * HARNESS-REQUIREMENT-1: Test frameworks can use requirement() as test descriptions
36
+ * HARNESS-REQUIREMENT-2: Invalid requirement paths fail the individual test
37
+ * HARNESS-REQUIREMENT-3: Each requirement() call is recorded for coverage reporting
38
+ * HARNESS-REQUIREMENT-4: Developers can run tests without calling prepare() first
39
+ * HARNESS-REQUIREMENT-5: A single test can exercise multiple requirements
40
+ *
41
+ * Supports both numeric and label-based paths.
42
+ * Accepts multiple requirement refs to track coverage for multiple requirements in one test.
43
+ *
44
+ * Examples:
45
+ * - requirement('REQ-123') - Returns root requirement content
46
+ * - requirement('REQ-123.0') - Returns first child (numeric path)
47
+ * - requirement('REQ-123.given') - Returns first "given" child (label path)
48
+ * - requirement('REQ-123.given#1') - Returns second "given" child
49
+ * - requirement('REQ-123.0.1') - Returns nested child
50
+ * - requirement('REQ-123.then.and') - Returns first "and" under first "then"
51
+ * - requirement('REQ-AUTH', 'REQ-SECURITY') - Tracks both, returns first
52
+ * - requirement('REQ-123', { projectRoot: '/path/to/project' }) - Explicit project root
53
+ *
54
+ * @param args - One or more requirement reference paths, optionally followed by options
55
+ * @returns A formatted string with the first requirement's label and content
56
+ */
57
+ export function requirement(...args) {
58
+ if (args.length === 0) {
59
+ throw new Error('requirement() requires at least one argument');
60
+ }
61
+ // Check if last argument is options object
62
+ const lastArg = args[args.length - 1];
63
+ const hasOptions = typeof lastArg === 'object' && lastArg !== null;
64
+ const options = hasOptions ? lastArg : {};
65
+ const requirementRefs = (hasOptions ? args.slice(0, -1) : args);
66
+ if (requirementRefs.length === 0) {
67
+ throw new Error('requirement() requires at least one requirement reference');
68
+ }
69
+ // If projectRoot provided, reload requirements from that path
70
+ if (options.projectRoot) {
71
+ loadRequirements({ projectRoot: options.projectRoot });
72
+ }
73
+ // Ensure test run is initialized
74
+ ensureTestRun();
75
+ // Track all requirements for coverage at the full path level
76
+ // e.g., 'REQ-123.given' is tracked as 'REQ-123.given', not just 'REQ-123'
77
+ for (const ref of requirementRefs) {
78
+ trackRequirement(ref);
79
+ }
80
+ // Save after tracking all requirements (no-op in new JSONL approach)
81
+ saveTrackingData();
82
+ // HARNESS-REQUIREMENT-2: Invalid requirement paths fail the individual test
83
+ const firstRef = requirementRefs[0];
84
+ const req = getRequirement(firstRef);
85
+ if (!req) {
86
+ throw new Error(`Requirement ${firstRef} not found`);
87
+ }
88
+ // HARNESS-REQUIREMENT-1: Format output as human-readable string
89
+ // Root requirements use 'requirementHeader' label which should not be shown in output
90
+ if (!req.label || req.label === 'requirementHeader') {
91
+ return req.content;
92
+ }
93
+ const label = req.label.charAt(0).toUpperCase() + req.label.slice(1);
94
+ return `${label}: ${req.content}`;
95
+ }
96
+ // Export new lifecycle functions
97
+ export { prepare } from './prepare.js';
98
+ export { finalize } from './finalize.js';
99
+ // Export tracking utilities
100
+ export { clearTracking } from './tracking.js';
101
+ // Deprecated exports - use prepare() and finalize() instead
102
+ export { initTestRun, finalizeTestRun, } from './tracking.js';
103
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
1
+ import { TrackedRequirement } from "./tracking.js";
2
+ /**
3
+ * Generate a simple console report showing which requirements were tested
4
+ */
5
+ export declare function reportLocalCoverage(trackedReqs?: Map<string, TrackedRequirement>): void;
6
+ //# sourceMappingURL=localReporting.d.ts.map
@@ -0,0 +1,49 @@
1
+ import { getAllRequirements } from "./requirementsLoader.js";
2
+ /**
3
+ * Generate a simple console report showing which requirements were tested
4
+ */
5
+ export function reportLocalCoverage(trackedReqs) {
6
+ const allRequirements = getAllRequirements();
7
+ // If no tracked requirements provided, return empty report
8
+ if (!trackedReqs) {
9
+ trackedReqs = new Map();
10
+ }
11
+ const allRequirementKeys = Array.from(allRequirements.keys());
12
+ const testedKeys = Array.from(trackedReqs.keys());
13
+ const untestedKeys = allRequirementKeys.filter((key) => !trackedReqs.has(key));
14
+ const coverage = allRequirementKeys.length > 0
15
+ ? ((testedKeys.length / allRequirementKeys.length) * 100).toFixed(1)
16
+ : "0.0";
17
+ let report = "\n=== Requirements Coverage Report ===\n";
18
+ report += `\nTotal Requirements: ${allRequirementKeys.length}\n`;
19
+ report += `Tested Requirements: ${testedKeys.length}\n`;
20
+ report += `Untested Requirements: ${untestedKeys.length}\n`;
21
+ report += `Coverage: ${coverage}%\n`;
22
+ // Show tested requirements
23
+ if (testedKeys.length > 0) {
24
+ report += "\n✓ Tested Requirements:\n";
25
+ testedKeys.forEach((key) => {
26
+ const req = allRequirements.get(key);
27
+ const label = req?.label || "";
28
+ const content = req?.content || "";
29
+ const preview = content.length > 60 ? content.substring(0, 60) + "..." : content;
30
+ const labelPart = label ? ` (${label})` : "";
31
+ report += ` - ${key}${labelPart}: ${preview}\n`;
32
+ });
33
+ }
34
+ // Show untested requirements
35
+ if (untestedKeys.length > 0) {
36
+ report += "\n✗ Untested Requirements:\n";
37
+ untestedKeys.forEach((key) => {
38
+ const req = allRequirements.get(key);
39
+ const label = req?.label || "";
40
+ const content = req?.content || "";
41
+ const preview = content.length > 60 ? content.substring(0, 60) + "..." : content;
42
+ const labelPart = label ? ` (${label})` : "";
43
+ report += ` - ${key}${labelPart}: ${preview}\n`;
44
+ });
45
+ }
46
+ report += "\n====================================\n";
47
+ console.log(report);
48
+ }
49
+ //# sourceMappingURL=localReporting.js.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Prepare function for the test harness.
3
+ *
4
+ * Called before tests run (e.g., in Vitest globalSetup) to:
5
+ * 1. Parse all .requirements.md files
6
+ * 2. Build lookup cache for fast path resolution
7
+ * 3. Initialize tracking for the test run
8
+ *
9
+ * Implements: HARNESS-PREPARE-1, HARNESS-PREPARE-2, HARNESS-PREPARE-3
10
+ */
11
+ export interface PrepareOptions {
12
+ /** Working directory to start search from (defaults to cwd) */
13
+ cwd?: string;
14
+ /** Whether to log warnings (defaults to true) */
15
+ logWarnings?: boolean;
16
+ }
17
+ export interface PrepareResult {
18
+ /** Number of requirements files processed */
19
+ filesProcessed: number;
20
+ /** Number of requirements loaded into cache */
21
+ requirementsLoaded: number;
22
+ /** Files that had parse errors */
23
+ parseErrors: Array<{
24
+ file: string;
25
+ error: string;
26
+ }>;
27
+ /** The test run ID for this run */
28
+ testRunId: string;
29
+ }
30
+ /**
31
+ * Prepare the test harness for a test run.
32
+ *
33
+ * This function should be called once before tests run (e.g., in globalSetup).
34
+ * It parses all requirements files and builds a lookup cache for fast resolution.
35
+ *
36
+ * HARNESS-PREPARE-1: After prepare() runs, requirement paths resolve quickly
37
+ * HARNESS-PREPARE-2: When prepare() encounters invalid requirements, it warns but continues
38
+ * HARNESS-PREPARE-3: prepare() initializes the cache directory for tracking
39
+ */
40
+ export declare function prepare(options?: PrepareOptions): PrepareResult;
41
+ //# sourceMappingURL=prepare.d.ts.map
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Prepare function for the test harness.
3
+ *
4
+ * Called before tests run (e.g., in Vitest globalSetup) to:
5
+ * 1. Parse all .requirements.md files
6
+ * 2. Build lookup cache for fast path resolution
7
+ * 3. Initialize tracking for the test run
8
+ *
9
+ * Implements: HARNESS-PREPARE-1, HARNESS-PREPARE-2, HARNESS-PREPARE-3
10
+ */
11
+ import { findProjectRootWithCredentials, findRequirementsDir, findRequirementsFiles, writeLookupCache, initTestRunId, clearTrackingFile, writeProjectRoot, } from './cache.js';
12
+ import { parseRequirementsFromFile, } from '../schema/index.js';
13
+ /**
14
+ * Prepare the test harness for a test run.
15
+ *
16
+ * This function should be called once before tests run (e.g., in globalSetup).
17
+ * It parses all requirements files and builds a lookup cache for fast resolution.
18
+ *
19
+ * HARNESS-PREPARE-1: After prepare() runs, requirement paths resolve quickly
20
+ * HARNESS-PREPARE-2: When prepare() encounters invalid requirements, it warns but continues
21
+ * HARNESS-PREPARE-3: prepare() initializes the cache directory for tracking
22
+ */
23
+ export function prepare(options = {}) {
24
+ const { cwd = process.cwd(), logWarnings = true } = options;
25
+ // Find the project root - prefer root with credentials (.env.local) for monorepo support
26
+ let projectRoot;
27
+ try {
28
+ projectRoot = findProjectRootWithCredentials(cwd);
29
+ }
30
+ catch {
31
+ throw new Error('Could not find .requirements directory or .env.local with credentials. ' +
32
+ 'Please create a .requirements directory in your project root.');
33
+ }
34
+ // Set environment variable for cross-process persistence (globalSetup → test workers)
35
+ process.env.DOTREQUIREMENTS_PROJECT_ROOT = projectRoot;
36
+ const requirementsDir = findRequirementsDir(projectRoot);
37
+ // Find all requirements files
38
+ const files = findRequirementsFiles(projectRoot);
39
+ // Parse all files, collecting requirements and errors
40
+ const allRequirements = [];
41
+ const parseErrors = [];
42
+ for (const file of files) {
43
+ try {
44
+ const { requirements } = parseRequirementsFromFile(file);
45
+ allRequirements.push(...requirements);
46
+ }
47
+ catch (error) {
48
+ const errorMessage = error instanceof Error ? error.message : String(error);
49
+ parseErrors.push({ file, error: errorMessage });
50
+ // HARNESS-PREPARE-2.0: Log warning with file path and error details
51
+ if (logWarnings) {
52
+ console.warn(`⚠️ Warning: Failed to parse ${file}: ${errorMessage}`);
53
+ }
54
+ }
55
+ }
56
+ // HARNESS-PREPARE-1: Write lookup cache for fast resolution
57
+ writeLookupCache(requirementsDir, allRequirements);
58
+ // Write project root to cache (for cross-process persistence in test workers)
59
+ writeProjectRoot(requirementsDir, projectRoot);
60
+ // HARNESS-PREPARE-3.0: Initialize cache directory (done by writeLookupCache)
61
+ // HARNESS-PREPARE-3.1: Clear any previous tracking data
62
+ clearTrackingFile(requirementsDir);
63
+ // Initialize test run ID
64
+ const testRunId = initTestRunId(requirementsDir);
65
+ // Count total requirements (including children)
66
+ let requirementsLoaded = 0;
67
+ function countRequirements(node) {
68
+ requirementsLoaded++;
69
+ for (const child of node.children) {
70
+ countRequirements(child);
71
+ }
72
+ }
73
+ for (const req of allRequirements) {
74
+ countRequirements(req);
75
+ }
76
+ return {
77
+ filesProcessed: files.length,
78
+ requirementsLoaded,
79
+ parseErrors,
80
+ testRunId,
81
+ };
82
+ }
83
+ //# sourceMappingURL=prepare.js.map