@popoverai/dotrequirements 0.23.0 → 0.24.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 (229) hide show
  1. package/README.md +167 -20
  2. package/dist/cli.js +121 -60
  3. package/dist/codebase-to-spec/budget.d.ts +53 -0
  4. package/dist/codebase-to-spec/budget.js +80 -0
  5. package/dist/codebase-to-spec/cache.d.ts +49 -0
  6. package/dist/codebase-to-spec/cache.js +54 -0
  7. package/dist/codebase-to-spec/claude.d.ts +69 -0
  8. package/dist/codebase-to-spec/claude.js +126 -0
  9. package/dist/codebase-to-spec/compose.d.ts +49 -0
  10. package/dist/codebase-to-spec/compose.js +124 -0
  11. package/dist/codebase-to-spec/edit-loop.d.ts +54 -0
  12. package/dist/codebase-to-spec/edit-loop.js +195 -0
  13. package/dist/codebase-to-spec/editor.d.ts +54 -0
  14. package/dist/codebase-to-spec/editor.js +74 -0
  15. package/dist/codebase-to-spec/exit-codes.d.ts +40 -0
  16. package/dist/codebase-to-spec/exit-codes.js +58 -0
  17. package/dist/codebase-to-spec/fan-out.d.ts +63 -0
  18. package/dist/codebase-to-spec/fan-out.js +215 -0
  19. package/dist/codebase-to-spec/interactive.d.ts +30 -0
  20. package/dist/codebase-to-spec/interactive.js +48 -0
  21. package/dist/codebase-to-spec/outline-review-loop.d.ts +51 -0
  22. package/dist/codebase-to-spec/outline-review-loop.js +187 -0
  23. package/dist/codebase-to-spec/pack.d.ts +51 -0
  24. package/dist/codebase-to-spec/pack.js +127 -0
  25. package/dist/codebase-to-spec/planner.d.ts +41 -0
  26. package/dist/codebase-to-spec/planner.js +76 -0
  27. package/dist/codebase-to-spec/present.d.ts +94 -0
  28. package/dist/codebase-to-spec/present.js +288 -0
  29. package/dist/codebase-to-spec/progress.d.ts +33 -0
  30. package/dist/codebase-to-spec/progress.js +28 -0
  31. package/dist/codebase-to-spec/prompts/editor.d.ts +13 -0
  32. package/dist/codebase-to-spec/prompts/editor.js +57 -0
  33. package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +12 -0
  34. package/dist/codebase-to-spec/prompts/outline-reviewer.js +87 -0
  35. package/dist/codebase-to-spec/prompts/planner-apply.d.ts +12 -0
  36. package/dist/codebase-to-spec/prompts/planner-apply.js +32 -0
  37. package/dist/codebase-to-spec/prompts/planner-initial.d.ts +11 -0
  38. package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
  39. package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
  40. package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
  41. package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
  42. package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
  43. package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
  44. package/dist/codebase-to-spec/prompts/specifier.js +100 -0
  45. package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
  46. package/dist/codebase-to-spec/prompts/style-check.js +78 -0
  47. package/dist/codebase-to-spec/schemas.d.ts +257 -0
  48. package/dist/codebase-to-spec/schemas.js +183 -0
  49. package/dist/codebase-to-spec/skill-install.d.ts +57 -0
  50. package/dist/codebase-to-spec/skill-install.js +79 -0
  51. package/dist/codebase-to-spec/slice.d.ts +49 -0
  52. package/dist/codebase-to-spec/slice.js +111 -0
  53. package/dist/codebase-to-spec/specifier.d.ts +60 -0
  54. package/dist/codebase-to-spec/specifier.js +79 -0
  55. package/dist/codebase-to-spec/style-check.d.ts +29 -0
  56. package/dist/codebase-to-spec/style-check.js +33 -0
  57. package/dist/codebase-to-spec/summary.d.ts +51 -0
  58. package/dist/codebase-to-spec/summary.js +183 -0
  59. package/dist/codebase-to-spec/validate.d.ts +46 -0
  60. package/dist/codebase-to-spec/validate.js +130 -0
  61. package/dist/commands/acceptance-test.d.ts +6 -0
  62. package/dist/commands/acceptance-test.js +212 -0
  63. package/dist/commands/ai-setup.d.ts +5 -0
  64. package/dist/commands/ai-setup.js +441 -0
  65. package/dist/commands/browsertest.js +34 -27
  66. package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
  67. package/dist/commands/codebase-to-spec/compose.js +57 -0
  68. package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
  69. package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
  70. package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
  71. package/dist/commands/codebase-to-spec/fan-out.js +77 -0
  72. package/dist/commands/codebase-to-spec/index.d.ts +9 -0
  73. package/dist/commands/codebase-to-spec/index.js +135 -0
  74. package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
  75. package/dist/commands/codebase-to-spec/pack.js +76 -0
  76. package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
  77. package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
  78. package/dist/commands/codebase-to-spec/present.d.ts +21 -0
  79. package/dist/commands/codebase-to-spec/present.js +92 -0
  80. package/dist/commands/codebase-to-spec/run.d.ts +20 -0
  81. package/dist/commands/codebase-to-spec/run.js +85 -0
  82. package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
  83. package/dist/commands/codebase-to-spec/skill-install.js +51 -0
  84. package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
  85. package/dist/commands/codebase-to-spec/specify-area.js +82 -0
  86. package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
  87. package/dist/commands/codebase-to-spec/style-check.js +42 -0
  88. package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
  89. package/dist/commands/codebase-to-spec/validate.js +38 -0
  90. package/dist/commands/create-requirement-document.d.ts +2 -0
  91. package/dist/commands/create-requirement-document.js +41 -0
  92. package/dist/commands/finalize.js +7 -7
  93. package/dist/commands/get.d.ts +2 -0
  94. package/dist/commands/get.js +55 -0
  95. package/dist/commands/init.js +132 -117
  96. package/dist/commands/link.js +27 -27
  97. package/dist/commands/list.d.ts +6 -0
  98. package/dist/commands/list.js +43 -0
  99. package/dist/commands/mcp-setup.js +159 -149
  100. package/dist/commands/mcp.js +1 -1
  101. package/dist/commands/prepare.js +4 -4
  102. package/dist/commands/pull.js +116 -121
  103. package/dist/commands/push.js +106 -112
  104. package/dist/commands/report.d.ts +6 -2
  105. package/dist/commands/report.js +177 -122
  106. package/dist/commands/requirements-for.d.ts +2 -0
  107. package/dist/commands/requirements-for.js +29 -0
  108. package/dist/commands/review-test.d.ts +2 -0
  109. package/dist/commands/review-test.js +75 -0
  110. package/dist/commands/search.d.ts +6 -0
  111. package/dist/commands/search.js +39 -0
  112. package/dist/commands/style-check.d.ts +7 -0
  113. package/dist/commands/style-check.js +75 -0
  114. package/dist/commands/test.js +53 -59
  115. package/dist/commands/tests-for.d.ts +2 -0
  116. package/dist/commands/tests-for.js +80 -0
  117. package/dist/commands/validate.d.ts +6 -0
  118. package/dist/commands/validate.js +72 -0
  119. package/dist/config.js +1 -1
  120. package/dist/convex.d.ts +34 -22
  121. package/dist/convex.js +38 -22
  122. package/dist/harness/cache.d.ts +1 -5
  123. package/dist/harness/cache.js +49 -59
  124. package/dist/harness/convexReporting.d.ts +1 -1
  125. package/dist/harness/convexReporting.js +9 -7
  126. package/dist/harness/coverageCache.js +3 -3
  127. package/dist/harness/finalize.js +59 -46
  128. package/dist/harness/index.d.ts +6 -7
  129. package/dist/harness/index.js +9 -10
  130. package/dist/harness/prepare.js +6 -5
  131. package/dist/harness/requirementsLoader.d.ts +2 -2
  132. package/dist/harness/requirementsLoader.js +13 -35
  133. package/dist/harness/tracking.js +18 -18
  134. package/dist/harness/types.d.ts +1 -1
  135. package/dist/mcp/convexClient.d.ts +0 -39
  136. package/dist/mcp/convexClient.js +2 -107
  137. package/dist/mcp/grep.d.ts +1 -1
  138. package/dist/mcp/grep.js +87 -42
  139. package/dist/mcp/handlers/authoring.d.ts +1 -1
  140. package/dist/mcp/handlers/authoring.js +30 -234
  141. package/dist/mcp/handlers/coverage.d.ts +1 -1
  142. package/dist/mcp/handlers/coverage.js +13 -15
  143. package/dist/mcp/handlers/debug.d.ts +2 -3
  144. package/dist/mcp/handlers/debug.js +10 -10
  145. package/dist/mcp/handlers/get.d.ts +1 -1
  146. package/dist/mcp/handlers/get.js +11 -10
  147. package/dist/mcp/handlers/index.d.ts +20 -20
  148. package/dist/mcp/handlers/index.js +10 -10
  149. package/dist/mcp/handlers/list.d.ts +4 -33
  150. package/dist/mcp/handlers/list.js +16 -38
  151. package/dist/mcp/handlers/push.d.ts +1 -1
  152. package/dist/mcp/handlers/push.js +28 -18
  153. package/dist/mcp/handlers/report.d.ts +16 -0
  154. package/dist/mcp/handlers/report.js +134 -0
  155. package/dist/mcp/handlers/review.d.ts +1 -1
  156. package/dist/mcp/handlers/review.js +40 -59
  157. package/dist/mcp/handlers/search.d.ts +1 -1
  158. package/dist/mcp/handlers/search.js +7 -9
  159. package/dist/mcp/handlers/test-mapping.d.ts +1 -1
  160. package/dist/mcp/handlers/test-mapping.js +14 -14
  161. package/dist/mcp/handlers/types.d.ts +3 -3
  162. package/dist/mcp/handlers/types.js +2 -2
  163. package/dist/mcp/index.d.ts +1 -1
  164. package/dist/mcp/index.js +147 -167
  165. package/dist/mcp/requirements.d.ts +2 -2
  166. package/dist/mcp/requirements.js +30 -30
  167. package/dist/mcp/testCodeExtractor.js +24 -26
  168. package/dist/mcp/types.d.ts +1 -1
  169. package/dist/push/core.d.ts +2 -2
  170. package/dist/push/core.js +20 -20
  171. package/dist/push/index.d.ts +1 -1
  172. package/dist/push/index.js +2 -2
  173. package/dist/requirements/cloud-ai.d.ts +57 -0
  174. package/dist/requirements/cloud-ai.js +104 -0
  175. package/dist/requirements/cloud-coverage.d.ts +41 -0
  176. package/dist/requirements/cloud-coverage.js +60 -0
  177. package/dist/requirements/coverage.d.ts +45 -0
  178. package/dist/requirements/coverage.js +114 -0
  179. package/dist/requirements/grep.d.ts +33 -0
  180. package/dist/requirements/grep.js +306 -0
  181. package/dist/requirements/index.d.ts +73 -0
  182. package/dist/requirements/index.js +174 -0
  183. package/dist/requirements/style-guide.d.ts +67 -0
  184. package/dist/requirements/style-guide.js +299 -0
  185. package/dist/requirements/testCodeExtractor.d.ts +22 -0
  186. package/dist/requirements/testCodeExtractor.js +150 -0
  187. package/dist/schema/browser.d.ts +8 -8
  188. package/dist/schema/browser.js +13 -15
  189. package/dist/schema/builder.d.ts +1 -1
  190. package/dist/schema/builder.js +13 -44
  191. package/dist/schema/conversions.d.ts +2 -2
  192. package/dist/schema/conversions.js +11 -11
  193. package/dist/schema/index.d.ts +9 -9
  194. package/dist/schema/index.js +15 -15
  195. package/dist/schema/parser-core.d.ts +1 -1
  196. package/dist/schema/parser-core.js +23 -22
  197. package/dist/schema/parser.d.ts +3 -3
  198. package/dist/schema/parser.js +27 -31
  199. package/dist/schema/resolver.d.ts +1 -1
  200. package/dist/schema/resolver.js +9 -9
  201. package/dist/schema/scenario.d.ts +1 -1
  202. package/dist/schema/scenario.js +1 -1
  203. package/dist/schema/schemas.d.ts +3 -3
  204. package/dist/schema/schemas.js +41 -28
  205. package/dist/schema/test-schema.js +27 -27
  206. package/dist/templates/context-file-section.md +3 -2
  207. package/dist/templates/example-requirements.js +1 -1
  208. package/dist/templates/example-requirements.ts +3 -1
  209. package/dist/templates/requirements-readme.js +1 -1
  210. package/dist/templates/requirements-readme.ts +1 -1
  211. package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
  212. package/dist/utils/brand.js +3 -3
  213. package/dist/utils/browser-launch.js +4 -4
  214. package/dist/utils/context-file.d.ts +1 -1
  215. package/dist/utils/context-file.js +26 -26
  216. package/dist/utils/env.js +7 -7
  217. package/dist/utils/gitignore.js +7 -7
  218. package/dist/utils/oauth-callback-server.d.ts +1 -1
  219. package/dist/utils/oauth-callback-server.js +27 -25
  220. package/dist/utils/oauth-flow.js +32 -29
  221. package/dist/utils/project-discovery.d.ts +3 -3
  222. package/dist/utils/project-discovery.js +18 -17
  223. package/dist/utils/project-name.js +8 -8
  224. package/dist/utils/project-selector.d.ts +1 -1
  225. package/dist/utils/project-selector.js +24 -21
  226. package/dist/utils/project-settings.d.ts +1 -1
  227. package/dist/utils/project-settings.js +24 -22
  228. package/dist/utils/templates.js +6 -6
  229. package/package.json +2 -1
@@ -6,25 +6,26 @@
6
6
  * - tracking.jsonl: Append-only tracking of requirement() calls (cross-process safe)
7
7
  * - coverage.json: Deduplication cache for cloud reporting
8
8
  */
9
- import * as fs from 'fs';
10
- import * as path from 'path';
11
- import { globSync } from 'glob';
9
+ import * as fs from "node:fs";
10
+ import * as path from "node:path";
11
+ import { findRequirementsFilesSync } from "../requirements/index.js";
12
12
  // Cache directory structure
13
- const CACHE_DIR = '.cache';
14
- const LOOKUP_FILE = 'lookup.json';
15
- const TRACKING_FILE = 'tracking.jsonl';
16
- const COVERAGE_FILE = 'coverage.json';
17
- const PROJECT_ROOT_FILE = 'project-root';
13
+ const CACHE_DIR = ".cache";
14
+ const LOOKUP_FILE = "lookup.json";
15
+ const TRACKING_FILE = "tracking.jsonl";
16
+ const COVERAGE_FILE = "coverage.json";
17
+ const PROJECT_ROOT_FILE = "project-root";
18
18
  // Test run ID file (outside cache, in .requirements/)
19
- const TEST_RUN_ID_FILE = '.test-run-id';
19
+ const TEST_RUN_ID_FILE = ".test-run-id";
20
20
  /**
21
21
  * Find the nearest .requirements directory by walking up from startDir
22
22
  */
23
23
  export function findRequirementsDir(startDir = process.cwd()) {
24
24
  let currentDir = path.resolve(startDir);
25
25
  while (true) {
26
- const requirementsDir = path.join(currentDir, '.requirements');
27
- if (fs.existsSync(requirementsDir) && fs.statSync(requirementsDir).isDirectory()) {
26
+ const requirementsDir = path.join(currentDir, ".requirements");
27
+ if (fs.existsSync(requirementsDir) &&
28
+ fs.statSync(requirementsDir).isDirectory()) {
28
29
  return requirementsDir;
29
30
  }
30
31
  const parentDir = path.dirname(currentDir);
@@ -52,28 +53,6 @@ export function getCacheDir(requirementsDir, create = false) {
52
53
  }
53
54
  return cacheDir;
54
55
  }
55
- /**
56
- * Find all *.requirements.md files under project root
57
- */
58
- export function findRequirementsFiles(projectRoot) {
59
- const pattern = '**/*.requirements.md';
60
- const matches = globSync(pattern, {
61
- cwd: projectRoot,
62
- ignore: [
63
- '**/node_modules/**',
64
- '**/dist/**',
65
- '**/.git/**',
66
- '**/build/**',
67
- '**/example/**',
68
- '**/examples/**',
69
- '**/__tests__/**',
70
- '**/fixtures/**',
71
- '**/.fixtures/**',
72
- ],
73
- dot: true, // Include dotfiles/dotdirs like .requirements/
74
- });
75
- return matches.map(m => path.join(projectRoot, m));
76
- }
77
56
  /**
78
57
  * Write the lookup cache with all pre-parsed requirements
79
58
  */
@@ -101,14 +80,15 @@ export function writeLookupCache(requirementsDir, requirements) {
101
80
  const labelCounts = new Map();
102
81
  for (const child of node.children) {
103
82
  let childLabelPath = null;
104
- if (child.label && child.label !== 'requirementHeader') {
105
- const normalizedLabel = child.label.toLowerCase().replace(/\s+/g, '-');
83
+ if (child.label && child.label !== "requirementHeader") {
84
+ const normalizedLabel = child.label.toLowerCase().replace(/\s+/g, "-");
106
85
  const count = labelCounts.get(normalizedLabel) || 0;
107
86
  labelCounts.set(normalizedLabel, count + 1);
108
87
  const parentPath = labelPath || node.id;
109
- childLabelPath = count === 0
110
- ? `${parentPath}.${normalizedLabel}`
111
- : `${parentPath}.${normalizedLabel}#${count}`;
88
+ childLabelPath =
89
+ count === 0
90
+ ? `${parentPath}.${normalizedLabel}`
91
+ : `${parentPath}.${normalizedLabel}#${count}`;
112
92
  }
113
93
  flatten(child, childLabelPath);
114
94
  }
@@ -124,7 +104,7 @@ export function writeLookupCache(requirementsDir, requirements) {
124
104
  */
125
105
  export function isCacheStale(requirementsDir, cacheMtime) {
126
106
  const projectRoot = path.dirname(requirementsDir);
127
- const sourceFiles = findRequirementsFiles(projectRoot);
107
+ const sourceFiles = findRequirementsFilesSync(projectRoot);
128
108
  for (const file of sourceFiles) {
129
109
  try {
130
110
  const stat = fs.statSync(file);
@@ -159,7 +139,7 @@ export function readLookupCache(requirementsDir) {
159
139
  return null;
160
140
  }
161
141
  try {
162
- const content = fs.readFileSync(lookupPath, 'utf-8');
142
+ const content = fs.readFileSync(lookupPath, "utf-8");
163
143
  return JSON.parse(content);
164
144
  }
165
145
  catch {
@@ -183,7 +163,7 @@ export function getTestRunId(requirementsDir) {
183
163
  if (!fs.existsSync(testRunIdPath)) {
184
164
  return null;
185
165
  }
186
- return fs.readFileSync(testRunIdPath, 'utf-8').trim();
166
+ return fs.readFileSync(testRunIdPath, "utf-8").trim();
187
167
  }
188
168
  /**
189
169
  * Clean up the test run ID file
@@ -212,7 +192,7 @@ export function readProjectRoot(requirementsDir) {
212
192
  if (!fs.existsSync(projectRootPath)) {
213
193
  return null;
214
194
  }
215
- return fs.readFileSync(projectRootPath, 'utf-8').trim();
195
+ return fs.readFileSync(projectRootPath, "utf-8").trim();
216
196
  }
217
197
  /**
218
198
  * Try to find the project root from any known .requirements cache directory.
@@ -221,10 +201,10 @@ export function readProjectRoot(requirementsDir) {
221
201
  export function findCachedProjectRoot(startDir = process.cwd()) {
222
202
  let currentDir = path.resolve(startDir);
223
203
  while (true) {
224
- const requirementsDir = path.join(currentDir, '.requirements');
204
+ const requirementsDir = path.join(currentDir, ".requirements");
225
205
  const projectRootPath = path.join(requirementsDir, CACHE_DIR, PROJECT_ROOT_FILE);
226
206
  if (fs.existsSync(projectRootPath)) {
227
- return fs.readFileSync(projectRootPath, 'utf-8').trim();
207
+ return fs.readFileSync(projectRootPath, "utf-8").trim();
228
208
  }
229
209
  const parentDir = path.dirname(currentDir);
230
210
  if (parentDir === currentDir) {
@@ -249,7 +229,7 @@ export function clearTrackingFile(requirementsDir) {
249
229
  export function appendTrackingEntry(requirementsDir, entry) {
250
230
  const cacheDir = getCacheDir(requirementsDir, true);
251
231
  const trackingPath = path.join(cacheDir, TRACKING_FILE);
252
- const line = JSON.stringify(entry) + '\n';
232
+ const line = `${JSON.stringify(entry)}\n`;
253
233
  fs.appendFileSync(trackingPath, line);
254
234
  }
255
235
  /**
@@ -261,9 +241,9 @@ export function readTrackingEntries(requirementsDir) {
261
241
  if (!fs.existsSync(trackingPath)) {
262
242
  return [];
263
243
  }
264
- const content = fs.readFileSync(trackingPath, 'utf-8');
244
+ const content = fs.readFileSync(trackingPath, "utf-8");
265
245
  const entries = [];
266
- for (const line of content.split('\n')) {
246
+ for (const line of content.split("\n")) {
267
247
  if (line.trim()) {
268
248
  try {
269
249
  entries.push(JSON.parse(line));
@@ -294,20 +274,28 @@ export function deleteTrackingFile(requirementsDir) {
294
274
  * can treat both shapes uniformly.
295
275
  */
296
276
  function normalizeEntry(raw) {
297
- if (!raw || typeof raw !== 'object')
277
+ if (!raw || typeof raw !== "object")
278
+ return null;
279
+ // Cache entry shape from disk; the guards below check each field at runtime.
280
+ const entry = raw;
281
+ if (typeof entry.testRunId !== "string")
282
+ return null;
283
+ if (typeof entry.timestamp !== "number")
298
284
  return null;
299
- if (Array.isArray(raw.tuples)) {
285
+ if (Array.isArray(entry.tuples)) {
300
286
  return {
301
- testRunId: raw.testRunId,
302
- timestamp: raw.timestamp,
303
- tuples: raw.tuples,
287
+ testRunId: entry.testRunId,
288
+ timestamp: entry.timestamp,
289
+ tuples: entry.tuples,
304
290
  };
305
291
  }
306
- if (Array.isArray(raw.requirementKeys)) {
292
+ if (Array.isArray(entry.requirementKeys)) {
307
293
  return {
308
- testRunId: raw.testRunId,
309
- timestamp: raw.timestamp,
310
- tuples: raw.requirementKeys.map((requirementKey) => ({ requirementKey })),
294
+ testRunId: entry.testRunId,
295
+ timestamp: entry.timestamp,
296
+ tuples: entry.requirementKeys.map((requirementKey) => ({
297
+ requirementKey,
298
+ })),
311
299
  };
312
300
  }
313
301
  return null;
@@ -319,7 +307,7 @@ export function readCoverageCache(requirementsDir) {
319
307
  return { current: null, previous: null };
320
308
  }
321
309
  try {
322
- const content = fs.readFileSync(coveragePath, 'utf-8');
310
+ const content = fs.readFileSync(coveragePath, "utf-8");
323
311
  const raw = JSON.parse(content);
324
312
  return {
325
313
  current: normalizeEntry(raw?.current),
@@ -337,7 +325,7 @@ export function readCoverageCache(requirementsDir) {
337
325
  export function updateCoverageCache(requirementsDir, testRunId, tuples) {
338
326
  const cacheDir = getCacheDir(requirementsDir, true);
339
327
  const coveragePath = path.join(cacheDir, COVERAGE_FILE);
340
- const normalized = tuples.map((t) => typeof t === 'string' ? { requirementKey: t } : t);
328
+ const normalized = tuples.map((t) => typeof t === "string" ? { requirementKey: t } : t);
341
329
  const existing = readCoverageCache(requirementsDir);
342
330
  const newCache = {
343
331
  current: {
@@ -360,7 +348,9 @@ function tuplesMatch(a, b) {
360
348
  * Returns true if no matching tuple was reported within the debounce window.
361
349
  */
362
350
  export function needsReporting(tupleOrKey, cache) {
363
- const tuple = typeof tupleOrKey === 'string' ? { requirementKey: tupleOrKey } : tupleOrKey;
351
+ const tuple = typeof tupleOrKey === "string"
352
+ ? { requirementKey: tupleOrKey }
353
+ : tupleOrKey;
364
354
  // If no current run, everything needs reporting
365
355
  if (!cache.current) {
366
356
  return true;
@@ -1,4 +1,4 @@
1
- import { TrackedRequirement } from "./tracking.js";
1
+ import { type TrackedRequirement } from "./tracking.js";
2
2
  /**
3
3
  * Get the current git branch, or null if not in a git repo
4
4
  */
@@ -1,7 +1,7 @@
1
- import { execSync } from "child_process";
2
- import { getTrackedRequirements } from "./tracking.js";
3
- import { saveCache, getRequirementsToReport } from "./coverageCache.js";
1
+ import { execSync } from "node:child_process";
4
2
  import { getProjectInfo } from "../utils/project-settings.js";
3
+ import { getRequirementsToReport, saveCache } from "./coverageCache.js";
4
+ import { getTrackedRequirements } from "./tracking.js";
5
5
  /**
6
6
  * Get the current git branch, or null if not in a git repo
7
7
  */
@@ -14,7 +14,7 @@ export function getCurrentBranch(cwd = process.cwd()) {
14
14
  }).trim();
15
15
  return branch;
16
16
  }
17
- catch (error) {
17
+ catch {
18
18
  // Not in a git repo or git not available
19
19
  return "unknown";
20
20
  }
@@ -22,7 +22,7 @@ export function getCurrentBranch(cwd = process.cwd()) {
22
22
  /**
23
23
  * Production Convex deployment URL
24
24
  */
25
- const CONVEX_URL = 'https://data.dotrequirements.io';
25
+ const CONVEX_URL = "https://data.dotrequirements.io";
26
26
  /**
27
27
  * Extract file path and line number from caller location string
28
28
  */
@@ -53,7 +53,9 @@ export async function reportCoverageToConvex(cwd = process.cwd(), trackedReqs) {
53
53
  // Get project info from .requirements/project-settings.json
54
54
  const projectInfo = getProjectInfo(cwd);
55
55
  // Skip if no project found or not connected to cloud
56
- if (!projectInfo || projectInfo.status !== 'cloud-connected' || !projectInfo.credentials) {
56
+ if (!projectInfo ||
57
+ projectInfo.status !== "cloud-connected" ||
58
+ !projectInfo.credentials) {
57
59
  console.log("\nℹ️ Skipping cloud coverage reporting (project not connected to cloud)");
58
60
  return;
59
61
  }
@@ -113,7 +115,7 @@ export async function reportCoverageToConvex(cwd = process.cwd(), trackedReqs) {
113
115
  console.warn(`\n⚠️ Failed to report coverage to cloud: ${response.status} ${error}`);
114
116
  return;
115
117
  }
116
- const result = await response.json();
118
+ await response.json();
117
119
  console.log(`\n✓ Reported ${keysToReport.length} requirement(s) to cloud (branch: ${branch})`);
118
120
  // Update cache with current test run
119
121
  saveCache({
@@ -1,5 +1,5 @@
1
- import { readFileSync, writeFileSync, existsSync } from "fs";
2
- import { join } from "path";
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
3
  import { v4 as uuidv4 } from "uuid";
4
4
  const CACHE_FILE = ".requirements/.coverage-cache.json";
5
5
  const STALENESS_THRESHOLD_MS = 4 * 60 * 60 * 1000; // 4 hours
@@ -15,7 +15,7 @@ export function loadCache(cwd = process.cwd()) {
15
15
  const content = readFileSync(cachePath, "utf-8");
16
16
  return JSON.parse(content);
17
17
  }
18
- catch (error) {
18
+ catch {
19
19
  // If cache is corrupt, start fresh
20
20
  console.warn("Warning: Coverage cache is corrupt, starting fresh");
21
21
  return { current: null, previous: null };
@@ -9,10 +9,10 @@
9
9
  *
10
10
  * Implements: HARNESS-FINALIZE-1, HARNESS-FINALIZE-2, HARNESS-FINALIZE-3, HARNESS-FINALIZE-4
11
11
  */
12
- import { execSync } from 'child_process';
13
- import { randomUUID } from 'crypto';
14
- import { findRequirementsDir, findProjectRoot, getTestRunId, cleanupTestRunId, readTrackingEntries, deleteTrackingFile, readLookupCache, readCoverageCache, updateCoverageCache, needsReporting, } from './cache.js';
15
- import { getProjectInfo } from '../utils/project-settings.js';
12
+ import { execSync } from "node:child_process";
13
+ import { randomUUID } from "node:crypto";
14
+ import { getProjectInfo } from "../utils/project-settings.js";
15
+ import { cleanupTestRunId, deleteTrackingFile, findProjectRoot, findRequirementsDir, getTestRunId, needsReporting, readCoverageCache, readLookupCache, readTrackingEntries, updateCoverageCache, } from "./cache.js";
16
16
  /**
17
17
  * Aggregate tracking entries by requirement key
18
18
  */
@@ -30,15 +30,15 @@ function aggregateEntries(entries) {
30
30
  */
31
31
  function getCurrentBranch(cwd) {
32
32
  try {
33
- const branch = execSync('git rev-parse --abbrev-ref HEAD', {
33
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
34
34
  cwd,
35
- encoding: 'utf-8',
36
- stdio: ['pipe', 'pipe', 'ignore'],
35
+ encoding: "utf-8",
36
+ stdio: ["pipe", "pipe", "ignore"],
37
37
  }).trim();
38
38
  return branch;
39
39
  }
40
40
  catch {
41
- return 'unknown';
41
+ return "unknown";
42
42
  }
43
43
  }
44
44
  /**
@@ -51,8 +51,8 @@ function getLineAuthor(cwd, file, line) {
51
51
  try {
52
52
  const output = execSync(`git blame -L ${line},${line} --porcelain -- "${file}"`, {
53
53
  cwd,
54
- encoding: 'utf-8',
55
- stdio: ['pipe', 'pipe', 'ignore'],
54
+ encoding: "utf-8",
55
+ stdio: ["pipe", "pipe", "ignore"],
56
56
  });
57
57
  const match = output.match(/^author-mail\s+<([^>]+)>/m);
58
58
  return match?.[1];
@@ -68,15 +68,17 @@ function getLineAuthor(cwd, file, line) {
68
68
  */
69
69
  function printLocalReport(testedKeys, lookup, options) {
70
70
  const { showSummary, showTestedList, showUntestedList } = options;
71
- const allKeys = lookup ? Object.keys(lookup.requirements).filter(k => !lookup.requirements[k].isAlias) : [];
72
- const untestedKeys = allKeys.filter(key => !testedKeys.includes(key));
71
+ const allKeys = lookup
72
+ ? Object.keys(lookup.requirements).filter((k) => !lookup.requirements[k].isAlias)
73
+ : [];
74
+ const untestedKeys = allKeys.filter((key) => !testedKeys.includes(key));
73
75
  const total = allKeys.length;
74
76
  const tested = testedKeys.length;
75
- const coverage = total > 0 ? ((tested / total) * 100).toFixed(1) : '0.0';
76
- let report = '';
77
+ const coverage = total > 0 ? ((tested / total) * 100).toFixed(1) : "0.0";
78
+ let report = "";
77
79
  // Show summary stats
78
80
  if (showSummary) {
79
- report += '\n=== Requirements Coverage Report ===\n';
81
+ report += "\n=== Requirements Coverage Report ===\n";
80
82
  report += `\nTotal Requirements: ${total}\n`;
81
83
  report += `Tested Requirements: ${tested}\n`;
82
84
  report += `Untested Requirements: ${untestedKeys.length}\n`;
@@ -84,14 +86,14 @@ function printLocalReport(testedKeys, lookup, options) {
84
86
  }
85
87
  // Show tested requirements
86
88
  if (showTestedList && testedKeys.length > 0) {
87
- report += '\n✓ Tested Requirements:\n';
89
+ report += "\n✓ Tested Requirements:\n";
88
90
  for (const key of testedKeys) {
89
91
  const req = lookup?.requirements[key];
90
92
  if (req) {
91
- const label = req.label || '';
92
- const content = req.content || '';
93
- const preview = content.length > 60 ? content.substring(0, 60) + '...' : content;
94
- const labelPart = label ? ` (${label})` : '';
93
+ const label = req.label || "";
94
+ const content = req.content || "";
95
+ const preview = content.length > 60 ? `${content.substring(0, 60)}...` : content;
96
+ const labelPart = label ? ` (${label})` : "";
95
97
  report += ` - ${key}${labelPart}: ${preview}\n`;
96
98
  }
97
99
  else {
@@ -101,14 +103,14 @@ function printLocalReport(testedKeys, lookup, options) {
101
103
  }
102
104
  // Show untested requirements
103
105
  if (showUntestedList && untestedKeys.length > 0) {
104
- report += '\n✗ Untested Requirements:\n';
106
+ report += "\n✗ Untested Requirements:\n";
105
107
  for (const key of untestedKeys) {
106
108
  const req = lookup?.requirements[key];
107
109
  if (req) {
108
- const label = req.label || '';
109
- const content = req.content || '';
110
- const preview = content.length > 60 ? content.substring(0, 60) + '...' : content;
111
- const labelPart = label ? ` (${label})` : '';
110
+ const label = req.label || "";
111
+ const content = req.content || "";
112
+ const preview = content.length > 60 ? `${content.substring(0, 60)}...` : content;
113
+ const labelPart = label ? ` (${label})` : "";
112
114
  report += ` - ${key}${labelPart}: ${preview}\n`;
113
115
  }
114
116
  else {
@@ -117,7 +119,7 @@ function printLocalReport(testedKeys, lookup, options) {
117
119
  }
118
120
  }
119
121
  if (showSummary) {
120
- report += '\n====================================\n';
122
+ report += "\n====================================\n";
121
123
  }
122
124
  if (report) {
123
125
  console.log(report);
@@ -126,7 +128,7 @@ function printLocalReport(testedKeys, lookup, options) {
126
128
  /**
127
129
  * Production Convex deployment URL
128
130
  */
129
- const CONVEX_URL = 'https://data.dotrequirements.io';
131
+ const CONVEX_URL = "https://data.dotrequirements.io";
130
132
  /**
131
133
  * Report coverage to Convex cloud
132
134
  *
@@ -138,9 +140,11 @@ async function reportToConvex(projectRoot, testRunId, aggregated, showCloudStatu
138
140
  // Get project info from .requirements/project-settings.json
139
141
  const projectInfo = getProjectInfo(projectRoot);
140
142
  // Skip if no project found or not connected to cloud
141
- if (!projectInfo || projectInfo.status !== 'cloud-connected' || !projectInfo.credentials) {
143
+ if (!projectInfo ||
144
+ projectInfo.status !== "cloud-connected" ||
145
+ !projectInfo.credentials) {
142
146
  if (showCloudStatus) {
143
- console.log('\nℹ️ Skipping cloud coverage reporting (project not connected to cloud)');
147
+ console.log("\nℹ️ Skipping cloud coverage reporting (project not connected to cloud)");
144
148
  }
145
149
  return { sent: false, count: 0 };
146
150
  }
@@ -161,7 +165,7 @@ async function reportToConvex(projectRoot, testRunId, aggregated, showCloudStatu
161
165
  const tuplesToReport = allTuples.filter((tuple) => needsReporting(tuple, coverageCache));
162
166
  if (tuplesToReport.length === 0) {
163
167
  if (showCloudStatus) {
164
- console.log('\n✓ Coverage unchanged since last run (skipping cloud report)');
168
+ console.log("\n✓ Coverage unchanged since last run (skipping cloud report)");
165
169
  }
166
170
  // Still update the cache so the window continues tracking
167
171
  updateCoverageCache(requirementsDir, testRunId, allTuples);
@@ -180,7 +184,7 @@ async function reportToConvex(projectRoot, testRunId, aggregated, showCloudStatu
180
184
  // Parse "filename.ts:42" format
181
185
  let testFile;
182
186
  let testLine;
183
- if (location !== 'unknown') {
187
+ if (location !== "unknown") {
184
188
  const match = location.match(/^(.+):(\d+)$/);
185
189
  if (match) {
186
190
  testFile = match[1];
@@ -213,19 +217,19 @@ async function reportToConvex(projectRoot, testRunId, aggregated, showCloudStatu
213
217
  // Note: projectId from env is always a slug (e.g., "reduced-cephalopod-288"),
214
218
  // not a Convex ID. Use projectSlug field for authentication and slug for target.
215
219
  const response = await fetch(`${CONVEX_URL}/api/mutation`, {
216
- method: 'POST',
220
+ method: "POST",
217
221
  headers: {
218
- 'Content-Type': 'application/json',
222
+ "Content-Type": "application/json",
219
223
  },
220
224
  body: JSON.stringify({
221
- path: 'testCoverage/mutations:recordCoverage',
225
+ path: "testCoverage/mutations:recordCoverage",
222
226
  args: {
223
227
  projectAuth: {
224
228
  projectSlug: projectId,
225
229
  projectSecret,
226
230
  },
227
231
  target: {
228
- type: 'project',
232
+ type: "project",
229
233
  slug: projectId,
230
234
  },
231
235
  branch,
@@ -233,7 +237,7 @@ async function reportToConvex(projectRoot, testRunId, aggregated, showCloudStatu
233
237
  runId,
234
238
  coverage,
235
239
  },
236
- format: 'json',
240
+ format: "json",
237
241
  }),
238
242
  });
239
243
  if (!response.ok) {
@@ -253,10 +257,12 @@ async function reportToConvex(projectRoot, testRunId, aggregated, showCloudStatu
253
257
  catch {
254
258
  // Not JSON, treat as success
255
259
  }
256
- if (responseData && typeof responseData === 'object' && 'status' in responseData) {
260
+ if (responseData &&
261
+ typeof responseData === "object" &&
262
+ "status" in responseData) {
257
263
  const convexResponse = responseData;
258
- if (convexResponse.status === 'error') {
259
- const errorMsg = convexResponse.errorMessage || 'Unknown Convex error';
264
+ if (convexResponse.status === "error") {
265
+ const errorMsg = convexResponse.errorMessage || "Unknown Convex error";
260
266
  if (showCloudStatus) {
261
267
  console.warn(`\n⚠️ Failed to report coverage to cloud: ${errorMsg}`);
262
268
  }
@@ -298,7 +304,7 @@ export async function finalize(options = {}) {
298
304
  projectRoot = findProjectRoot(cwd);
299
305
  }
300
306
  if (!projectRoot) {
301
- console.log('Could not find .requirements directory');
307
+ console.log("Could not find .requirements directory");
302
308
  return {
303
309
  requirementsTested: 0,
304
310
  totalRequirements: 0,
@@ -311,7 +317,7 @@ export async function finalize(options = {}) {
311
317
  // Get test run ID
312
318
  const testRunId = getTestRunId(requirementsDir);
313
319
  if (!testRunId) {
314
- console.log('No test run ID found - was prepare() called?');
320
+ console.log("No test run ID found - was prepare() called?");
315
321
  return {
316
322
  requirementsTested: 0,
317
323
  totalRequirements: 0,
@@ -343,17 +349,24 @@ export async function finalize(options = {}) {
343
349
  }
344
350
  }
345
351
  const testedKeys = Array.from(aggregated.keys());
346
- const totalRequirements = lookup ? Object.keys(lookup.requirements).filter(k => !lookup.requirements[k].isAlias).length : 0;
347
- const coveragePercent = totalRequirements > 0
348
- ? (testedKeys.length / totalRequirements) * 100
352
+ const totalRequirements = lookup
353
+ ? Object.keys(lookup.requirements).filter((k) => !lookup.requirements[k].isAlias).length
349
354
  : 0;
355
+ const coveragePercent = totalRequirements > 0 ? (testedKeys.length / totalRequirements) * 100 : 0;
350
356
  // HARNESS-FINALIZE-1: Print local report
351
357
  const shouldPrintLocal = showSummary || showTestedList || showUntestedList;
352
358
  if (shouldPrintLocal) {
353
- printLocalReport(testedKeys, lookup, { showSummary, showTestedList, showUntestedList });
359
+ printLocalReport(testedKeys, lookup, {
360
+ showSummary,
361
+ showTestedList,
362
+ showUntestedList,
363
+ });
354
364
  }
355
365
  // HARNESS-FINALIZE-2, HARNESS-FINALIZE-3: Report to cloud
356
- let cloudResult = { sent: false, count: 0 };
366
+ let cloudResult = {
367
+ sent: false,
368
+ count: 0,
369
+ };
357
370
  if (shouldReportToCloud) {
358
371
  cloudResult = await reportToConvex(projectRoot, testRunId, aggregated, showCloudStatus, context);
359
372
  }
@@ -60,11 +60,10 @@ export interface RequirementOptions {
60
60
  * @returns A formatted string with the first requirement's label and content
61
61
  */
62
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';
63
+ export type { FinalizeOptions, FinalizeResult } from "./finalize.js";
64
+ export { finalize } from "./finalize.js";
65
+ export type { PrepareOptions, PrepareResult } from "./prepare.js";
66
+ export { prepare } from "./prepare.js";
67
+ export { clearTracking, finalizeTestRun, initTestRun } from "./tracking.js";
68
+ export type { Requirement } from "./types.js";
70
69
  //# sourceMappingURL=index.d.ts.map
@@ -27,8 +27,8 @@
27
27
  * }
28
28
  * ```
29
29
  */
30
- import { getRequirement, loadRequirements } from './requirementsLoader.js';
31
- import { ensureTestRun, trackRequirement, saveTrackingData } from './tracking.js';
30
+ import { getRequirement, loadRequirements } from "./requirementsLoader.js";
31
+ import { ensureTestRun, saveTrackingData, trackRequirement, } from "./tracking.js";
32
32
  /**
33
33
  * Main requirements function for test harness.
34
34
  *
@@ -56,15 +56,15 @@ import { ensureTestRun, trackRequirement, saveTrackingData } from './tracking.js
56
56
  */
57
57
  export function requirement(...args) {
58
58
  if (args.length === 0) {
59
- throw new Error('requirement() requires at least one argument');
59
+ throw new Error("requirement() requires at least one argument");
60
60
  }
61
61
  // Check if last argument is options object
62
62
  const lastArg = args[args.length - 1];
63
- const hasOptions = typeof lastArg === 'object' && lastArg !== null;
63
+ const hasOptions = typeof lastArg === "object" && lastArg !== null;
64
64
  const options = hasOptions ? lastArg : {};
65
65
  const requirementRefs = (hasOptions ? args.slice(0, -1) : args);
66
66
  if (requirementRefs.length === 0) {
67
- throw new Error('requirement() requires at least one requirement reference');
67
+ throw new Error("requirement() requires at least one requirement reference");
68
68
  }
69
69
  // If projectRoot provided, reload requirements from that path
70
70
  if (options.projectRoot) {
@@ -87,17 +87,16 @@ export function requirement(...args) {
87
87
  }
88
88
  // HARNESS-REQUIREMENT-1: Format output as human-readable string
89
89
  // Root requirements use 'requirementHeader' label which should not be shown in output
90
- if (!req.label || req.label === 'requirementHeader') {
90
+ if (!req.label || req.label === "requirementHeader") {
91
91
  return req.content;
92
92
  }
93
93
  const label = req.label.charAt(0).toUpperCase() + req.label.slice(1);
94
94
  return `${label}: ${req.content}`;
95
95
  }
96
+ export { finalize } from "./finalize.js";
96
97
  // Export new lifecycle functions
97
- export { prepare } from './prepare.js';
98
- export { finalize } from './finalize.js';
98
+ export { prepare } from "./prepare.js";
99
99
  // Export tracking utilities
100
- export { clearTracking } from './tracking.js';
101
100
  // Deprecated exports - use prepare() and finalize() instead
102
- export { initTestRun, finalizeTestRun, } from './tracking.js';
101
+ export { clearTracking, finalizeTestRun, initTestRun } from "./tracking.js";
103
102
  //# sourceMappingURL=index.js.map
@@ -8,9 +8,10 @@
8
8
  *
9
9
  * Implements: HARNESS-PREPARE-1, HARNESS-PREPARE-2, HARNESS-PREPARE-3
10
10
  */
11
- import { findRequirementsDir, findRequirementsFiles, writeLookupCache, initTestRunId, clearTrackingFile, writeProjectRoot, } from './cache.js';
12
- import { parseRequirementsFromFile, } from '../schema/index.js';
13
- import { findProjectRoot } from '../utils/project-settings.js';
11
+ import { findRequirementsFilesSync } from "../requirements/index.js";
12
+ import { parseRequirementsFromFile, } from "../schema/index.js";
13
+ import { findProjectRoot } from "../utils/project-settings.js";
14
+ import { clearTrackingFile, findRequirementsDir, initTestRunId, writeLookupCache, writeProjectRoot, } from "./cache.js";
14
15
  /**
15
16
  * Prepare the test harness for a test run.
16
17
  *
@@ -26,14 +27,14 @@ export function prepare(options = {}) {
26
27
  // Find the project root by looking for .requirements directory
27
28
  const projectRoot = findProjectRoot(cwd);
28
29
  if (!projectRoot) {
29
- throw new Error('Could not find .requirements directory. ' +
30
+ throw new Error("Could not find .requirements directory. " +
30
31
  'Please run "dotrequirements init" to initialize your project.');
31
32
  }
32
33
  // Set environment variable for cross-process persistence (globalSetup → test workers)
33
34
  process.env.DOTREQUIREMENTS_PROJECT_ROOT = projectRoot;
34
35
  const requirementsDir = findRequirementsDir(projectRoot);
35
36
  // Find all requirements files
36
- const files = findRequirementsFiles(projectRoot);
37
+ const files = findRequirementsFilesSync(projectRoot);
37
38
  // Parse all files, collecting requirements and errors
38
39
  const allRequirements = [];
39
40
  const parseErrors = [];