@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
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Cloud-coverage library — hosted endpoints for querying persisted test
3
+ * coverage from dot•requirements cloud. Shared between the MCP `report`
4
+ * tool and the CLI `report` verb's `--source cloud` mode.
5
+ */
6
+ /**
7
+ * Query cloud for coverage on a single requirement.
8
+ */
9
+ export async function getRequirementCoverage(requirementKey, projectId, projectSecret, convexUrl) {
10
+ const response = await fetch(`${convexUrl}/api/query`, {
11
+ method: "POST",
12
+ headers: { "Content-Type": "application/json" },
13
+ body: JSON.stringify({
14
+ path: "testCoverage/queries:getRequirementCoverage",
15
+ args: {
16
+ projectAuth: { projectSlug: projectId, projectSecret },
17
+ target: { type: "project", slug: projectId },
18
+ requirementKey,
19
+ },
20
+ format: "json",
21
+ }),
22
+ });
23
+ if (!response.ok) {
24
+ throw new Error(`Failed to query coverage: ${response.statusText}`);
25
+ }
26
+ const json = (await response.json());
27
+ if (json.status === "error") {
28
+ throw new Error(`Convex query failed: ${json.errorMessage}`);
29
+ }
30
+ return json.value;
31
+ }
32
+ /**
33
+ * Query cloud for project-wide coverage summary, optionally filtered by
34
+ * branch or recorded-since timestamp.
35
+ */
36
+ export async function getProjectCoverage(projectId, projectSecret, convexUrl, options) {
37
+ const response = await fetch(`${convexUrl}/api/query`, {
38
+ method: "POST",
39
+ headers: { "Content-Type": "application/json" },
40
+ body: JSON.stringify({
41
+ path: "testCoverage/queries:getProjectCoverage",
42
+ args: {
43
+ projectAuth: { projectSlug: projectId, projectSecret },
44
+ target: { type: "project", slug: projectId },
45
+ branch: options?.branch,
46
+ sinceTimestamp: options?.sinceTimestamp,
47
+ },
48
+ format: "json",
49
+ }),
50
+ });
51
+ if (!response.ok) {
52
+ throw new Error(`Failed to query coverage: ${response.statusText}`);
53
+ }
54
+ const json = (await response.json());
55
+ if (json.status === "error") {
56
+ throw new Error(`Convex query failed: ${json.errorMessage}`);
57
+ }
58
+ return json.value;
59
+ }
60
+ //# sourceMappingURL=cloud-coverage.js.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Local-coverage library — reads the test-harness cache produced by
3
+ * `dotreq harness prepare` + your test run, and turns it into a structured
4
+ * coverage report. Shared between the CLI `report` verb's local mode and
5
+ * the MCP `report` tool's `source: "local"` mode.
6
+ *
7
+ * Cloud-side coverage queries live next door in cloud-coverage.ts.
8
+ */
9
+ export interface CoverageEntry {
10
+ key: string;
11
+ label: string | null;
12
+ content: string;
13
+ tested: boolean;
14
+ locations: string[];
15
+ }
16
+ export interface LocalReportSummary {
17
+ total: number;
18
+ tested: number;
19
+ untested: number;
20
+ coveragePercent: number;
21
+ }
22
+ export interface LocalReportResult {
23
+ entries: CoverageEntry[];
24
+ summary: LocalReportSummary;
25
+ /** Soft warnings the caller may want to surface (stale cache, empty
26
+ * tracking file, etc.). Hard errors throw instead. */
27
+ warnings: string[];
28
+ }
29
+ export declare class LocalReportCacheMissingError extends Error {
30
+ constructor(message: string);
31
+ }
32
+ /**
33
+ * Build a coverage report from the project's local test-harness cache.
34
+ *
35
+ * Throws {@link LocalReportCacheMissingError} when the cache is missing
36
+ * entirely (the caller hasn't run `dotreq harness prepare` yet). Soft
37
+ * issues — stale cache, missing or empty tracking file — are returned
38
+ * as `warnings` so the caller can decide where they belong (stderr for
39
+ * CLI, response body for MCP).
40
+ */
41
+ export declare function buildLocalReport(opts: {
42
+ projectRoot: string;
43
+ requirementFilter?: string;
44
+ }): LocalReportResult;
45
+ //# sourceMappingURL=coverage.d.ts.map
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Local-coverage library — reads the test-harness cache produced by
3
+ * `dotreq harness prepare` + your test run, and turns it into a structured
4
+ * coverage report. Shared between the CLI `report` verb's local mode and
5
+ * the MCP `report` tool's `source: "local"` mode.
6
+ *
7
+ * Cloud-side coverage queries live next door in cloud-coverage.ts.
8
+ */
9
+ import * as fs from "node:fs";
10
+ import * as path from "node:path";
11
+ import { findRequirementsDir, isCacheStale, readTrackingEntries, } from "../harness/cache.js";
12
+ export class LocalReportCacheMissingError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = "LocalReportCacheMissingError";
16
+ }
17
+ }
18
+ /**
19
+ * Build a coverage report from the project's local test-harness cache.
20
+ *
21
+ * Throws {@link LocalReportCacheMissingError} when the cache is missing
22
+ * entirely (the caller hasn't run `dotreq harness prepare` yet). Soft
23
+ * issues — stale cache, missing or empty tracking file — are returned
24
+ * as `warnings` so the caller can decide where they belong (stderr for
25
+ * CLI, response body for MCP).
26
+ */
27
+ export function buildLocalReport(opts) {
28
+ const requirementsDir = findRequirementsDir(opts.projectRoot);
29
+ if (!requirementsDir) {
30
+ throw new LocalReportCacheMissingError('No .requirements/ directory found. Run "dotrequirements init" to initialize your project.');
31
+ }
32
+ const cacheDir = path.join(requirementsDir, ".cache");
33
+ const lookupPath = path.join(cacheDir, "lookup.json");
34
+ if (!fs.existsSync(lookupPath)) {
35
+ throw new LocalReportCacheMissingError('No lookup cache found. Run "dotrequirements harness prepare" first to build the cache.');
36
+ }
37
+ const warnings = [];
38
+ const lookup = JSON.parse(fs.readFileSync(lookupPath, "utf-8"));
39
+ try {
40
+ const cacheStat = fs.statSync(lookupPath);
41
+ if (isCacheStale(requirementsDir, cacheStat.mtimeMs)) {
42
+ warnings.push('Cache may be stale — requirements have changed since last prepare. Run "dotrequirements harness prepare" to rebuild.');
43
+ }
44
+ }
45
+ catch {
46
+ // ignore stat errors
47
+ }
48
+ const trackingPath = path.join(cacheDir, "tracking.jsonl");
49
+ const trackingExists = fs.existsSync(trackingPath);
50
+ const entries = readTrackingEntries(requirementsDir);
51
+ if (!trackingExists) {
52
+ warnings.push('No tracking data found. Run your tests first, or run "dotrequirements harness prepare" to start a new tracking session.');
53
+ }
54
+ else if (entries.length === 0) {
55
+ warnings.push("Tracking file is empty — no requirements were tracked during tests.");
56
+ }
57
+ const coverageEntries = buildCoverageEntries(lookup, entries, opts.requirementFilter);
58
+ const tested = coverageEntries.filter((c) => c.tested).length;
59
+ const total = coverageEntries.length;
60
+ const coveragePercent = total > 0 ? parseFloat(((tested / total) * 100).toFixed(1)) : 0;
61
+ return {
62
+ entries: coverageEntries,
63
+ summary: {
64
+ total,
65
+ tested,
66
+ untested: total - tested,
67
+ coveragePercent,
68
+ },
69
+ warnings,
70
+ };
71
+ }
72
+ function buildCoverageEntries(lookup, entries, filterKey) {
73
+ const locationsByKey = new Map();
74
+ for (const entry of entries) {
75
+ if (!locationsByKey.has(entry.requirementKey)) {
76
+ locationsByKey.set(entry.requirementKey, new Set());
77
+ }
78
+ locationsByKey.get(entry.requirementKey).add(entry.callerLocation);
79
+ }
80
+ const results = [];
81
+ for (const [key, req] of Object.entries(lookup.requirements)) {
82
+ if (req.isAlias)
83
+ continue;
84
+ if (filterKey && key !== filterKey && !key.startsWith(`${filterKey}.`)) {
85
+ continue;
86
+ }
87
+ const locations = new Set();
88
+ const keyLocations = locationsByKey.get(key);
89
+ if (keyLocations) {
90
+ keyLocations.forEach((l) => {
91
+ locations.add(l);
92
+ });
93
+ }
94
+ for (const [aliasKey, aliasReq] of Object.entries(lookup.requirements)) {
95
+ if (aliasReq.isAlias && aliasReq.id === req.id) {
96
+ const aliasLocations = locationsByKey.get(aliasKey);
97
+ if (aliasLocations) {
98
+ aliasLocations.forEach((l) => {
99
+ locations.add(l);
100
+ });
101
+ }
102
+ }
103
+ }
104
+ results.push({
105
+ key,
106
+ label: req.label || null,
107
+ content: req.content,
108
+ tested: locations.size > 0,
109
+ locations: Array.from(locations),
110
+ });
111
+ }
112
+ return results;
113
+ }
114
+ //# sourceMappingURL=coverage.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * A `requirement(...)` call site found in source code.
3
+ */
4
+ export interface TestReference {
5
+ file: string;
6
+ line: number;
7
+ column: number;
8
+ requirementId: string;
9
+ context: string;
10
+ }
11
+ /**
12
+ * Find all test files that reference a specific requirement ID
13
+ *
14
+ * Uses execFile with array arguments to avoid command injection vulnerabilities.
15
+ */
16
+ export declare function findTestsForRequirement(workspaceRoot: string, requirementId: string): Promise<TestReference[]>;
17
+ /**
18
+ * Find all requirement references in a specific file using AST parsing
19
+ */
20
+ export declare function findRequirementsInFile(filePath: string): Promise<TestReference[]>;
21
+ /**
22
+ * Find all requirement references in the entire workspace
23
+ *
24
+ * Uses execFile with array arguments to avoid command injection vulnerabilities.
25
+ */
26
+ export declare function findAllTestReferences(workspaceRoot: string): Promise<TestReference[]>;
27
+ /**
28
+ * Get all unique requirement IDs referenced in tests
29
+ * Normalizes child IDs (e.g., AUTH-VALID-LOGIN.0) to root IDs (e.g., AUTH-VALID-LOGIN)
30
+ * Uses AST parsing instead of grep for reliability across environments
31
+ */
32
+ export declare function getReferencedRequirementIds(workspaceRoot: string): Promise<Set<string>>;
33
+ //# sourceMappingURL=grep.d.ts.map
@@ -0,0 +1,306 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ /**
5
+ * Check if ripgrep is available
6
+ */
7
+ async function hasRipgrep() {
8
+ try {
9
+ await execFileAsync("rg", ["--version"]);
10
+ return true;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+ /**
17
+ * Find all test files that reference a specific requirement ID
18
+ *
19
+ * Uses execFile with array arguments to avoid command injection vulnerabilities.
20
+ */
21
+ export async function findTestsForRequirement(workspaceRoot, requirementId) {
22
+ const useRg = await hasRipgrep();
23
+ // Escape special regex characters in the ID
24
+ const escapedId = requirementId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25
+ // Pattern to match requirement('ID'), requirement("ID"), or requirement(`ID`)
26
+ const pattern = `requirement\\s*\\(\\s*['"\`]${escapedId}`;
27
+ try {
28
+ let output;
29
+ if (useRg) {
30
+ // Use ripgrep for better performance
31
+ // Using execFile with array args to avoid shell injection
32
+ const { stdout } = await execFileAsync("rg", [
33
+ "-n",
34
+ "--column",
35
+ pattern,
36
+ "--type",
37
+ "ts",
38
+ "--type",
39
+ "js",
40
+ "--type",
41
+ "tsx",
42
+ "--type",
43
+ "jsx",
44
+ workspaceRoot,
45
+ ], { maxBuffer: 10 * 1024 * 1024 }).catch(() => ({ stdout: "" })); // ripgrep returns non-zero on no matches
46
+ output = stdout;
47
+ }
48
+ else {
49
+ // Fall back to grep using execFile
50
+ const { stdout } = await execFileAsync("grep", [
51
+ "-rn",
52
+ "--include=*.ts",
53
+ "--include=*.tsx",
54
+ "--include=*.js",
55
+ "--include=*.jsx",
56
+ "-E",
57
+ pattern,
58
+ workspaceRoot,
59
+ ], { maxBuffer: 10 * 1024 * 1024 }).catch(() => ({ stdout: "" })); // grep returns non-zero on no matches
60
+ output = stdout;
61
+ }
62
+ return parseGrepOutput(output, requirementId, useRg);
63
+ }
64
+ catch {
65
+ // Grep returns exit code 1 when no matches found
66
+ return [];
67
+ }
68
+ }
69
+ /**
70
+ * Find all requirement references in a specific file using AST parsing
71
+ */
72
+ export async function findRequirementsInFile(filePath) {
73
+ const { parse } = await import("@babel/parser");
74
+ const traverse = await import("@babel/traverse");
75
+ const t = await import("@babel/types");
76
+ const fs = await import("node:fs");
77
+ const pathModule = await import("node:path");
78
+ const results = [];
79
+ // Resolve relative paths from current working directory
80
+ let resolvedPath = pathModule.isAbsolute(filePath)
81
+ ? filePath
82
+ : pathModule.resolve(process.cwd(), filePath);
83
+ // If file doesn't exist and path is relative, try resolving from CLI package root
84
+ if (!pathModule.isAbsolute(filePath) && !fs.existsSync(resolvedPath)) {
85
+ const cliPackageRoot = pathModule.resolve(__dirname, "../..");
86
+ const alternativePath = pathModule.resolve(cliPackageRoot, filePath);
87
+ if (fs.existsSync(alternativePath)) {
88
+ resolvedPath = alternativePath;
89
+ }
90
+ }
91
+ try {
92
+ const code = fs.readFileSync(resolvedPath, "utf-8");
93
+ const ast = parse(code, {
94
+ sourceType: "module",
95
+ plugins: ["typescript", "jsx"],
96
+ errorRecovery: true,
97
+ });
98
+ // Handle both ES module and CommonJS exports for traverse
99
+ // biome-ignore lint/suspicious/noExplicitAny: @babel/traverse ships a CJS default-export that's a callable, with .default property nested 1-2 levels. The interop dance requires escaping the function-vs-namespace type wall.
100
+ let traverseFunc = traverse;
101
+ if (typeof traverseFunc !== "function") {
102
+ traverseFunc = traverseFunc.default;
103
+ }
104
+ if (typeof traverseFunc !== "function") {
105
+ traverseFunc = traverseFunc.default;
106
+ }
107
+ traverseFunc(ast, {
108
+ // biome-ignore lint/suspicious/noExplicitAny: babel-traverse visitor `path` is a deeply-generic NodePath whose precise type depends on what handler you're inside; `any` is the documented practice for plugin code.
109
+ CallExpression(path) {
110
+ const callee = path.node.callee;
111
+ // Check if this is a requirement() call
112
+ if (t.isIdentifier(callee) && callee.name === "requirement") {
113
+ const args = path.node.arguments;
114
+ const loc = path.node.loc;
115
+ // Extract all string literal arguments (supports multi-requirement calls)
116
+ for (const arg of args) {
117
+ if (t.isStringLiteral(arg)) {
118
+ const requirementId = arg.value;
119
+ if (loc) {
120
+ results.push({
121
+ file: resolvedPath,
122
+ line: loc.start.line,
123
+ column: loc.start.column + 1, // 1-indexed
124
+ requirementId,
125
+ context: code.split("\n")[loc.start.line - 1] || "",
126
+ });
127
+ }
128
+ }
129
+ }
130
+ }
131
+ },
132
+ });
133
+ return results;
134
+ }
135
+ catch {
136
+ return [];
137
+ }
138
+ }
139
+ /**
140
+ * Find all requirement references in the entire workspace
141
+ *
142
+ * Uses execFile with array arguments to avoid command injection vulnerabilities.
143
+ */
144
+ export async function findAllTestReferences(workspaceRoot) {
145
+ const useRg = await hasRipgrep();
146
+ // Pattern to match any requirement() call
147
+ const pattern = `requirement\\s*\\(\\s*['"\`][^'"\`]+`;
148
+ try {
149
+ let output;
150
+ if (useRg) {
151
+ // Using execFile with array args to avoid shell injection
152
+ const { stdout } = await execFileAsync("rg", [
153
+ "-n",
154
+ "--column",
155
+ pattern,
156
+ "--type",
157
+ "ts",
158
+ "--type",
159
+ "js",
160
+ "--type",
161
+ "tsx",
162
+ "--type",
163
+ "jsx",
164
+ workspaceRoot,
165
+ ], { maxBuffer: 10 * 1024 * 1024 }).catch(() => ({ stdout: "" })); // ripgrep returns non-zero on no matches
166
+ output = stdout;
167
+ }
168
+ else {
169
+ const { stdout } = await execFileAsync("grep", [
170
+ "-rn",
171
+ "--include=*.ts",
172
+ "--include=*.tsx",
173
+ "--include=*.js",
174
+ "--include=*.jsx",
175
+ "-E",
176
+ pattern,
177
+ workspaceRoot,
178
+ ], { maxBuffer: 10 * 1024 * 1024 }).catch(() => ({ stdout: "" })); // grep returns non-zero on no matches
179
+ output = stdout;
180
+ }
181
+ return parseGrepOutput(output, "", useRg);
182
+ }
183
+ catch {
184
+ return [];
185
+ }
186
+ }
187
+ /**
188
+ * Parse grep/ripgrep output into TestReference objects
189
+ */
190
+ function parseGrepOutput(output, expectedId, isRipgrep, singleFilePath) {
191
+ const results = [];
192
+ const lines = output.trim().split("\n").filter(Boolean);
193
+ for (const line of lines) {
194
+ let match;
195
+ let file;
196
+ let lineNum;
197
+ let column;
198
+ let context;
199
+ if (isRipgrep) {
200
+ // ripgrep format: file:line:column:content
201
+ match = line.match(/^(.+?):(\d+):(\d+):(.*)$/);
202
+ if (!match)
203
+ continue;
204
+ file = match[1];
205
+ lineNum = parseInt(match[2], 10);
206
+ column = parseInt(match[3], 10);
207
+ context = match[4];
208
+ }
209
+ else {
210
+ // grep format: file:line:content (no column)
211
+ if (singleFilePath) {
212
+ match = line.match(/^(\d+):(.*)$/);
213
+ if (!match)
214
+ continue;
215
+ file = singleFilePath;
216
+ lineNum = parseInt(match[1], 10);
217
+ context = match[2];
218
+ column = 1;
219
+ }
220
+ else {
221
+ match = line.match(/^(.+?):(\d+):(.*)$/);
222
+ if (!match)
223
+ continue;
224
+ file = match[1];
225
+ lineNum = parseInt(match[2], 10);
226
+ context = match[3];
227
+ column = 1;
228
+ }
229
+ }
230
+ // Extract the requirement ID from the context
231
+ const idMatch = context.match(/requirement\s*\(\s*['"`]([^'"`]+)/);
232
+ const requirementId = idMatch ? idMatch[1] : expectedId;
233
+ results.push({
234
+ file,
235
+ line: lineNum,
236
+ column,
237
+ requirementId,
238
+ context: context.trim(),
239
+ });
240
+ }
241
+ return results;
242
+ }
243
+ /**
244
+ * Get all unique requirement IDs referenced in tests
245
+ * Normalizes child IDs (e.g., AUTH-VALID-LOGIN.0) to root IDs (e.g., AUTH-VALID-LOGIN)
246
+ * Uses AST parsing instead of grep for reliability across environments
247
+ */
248
+ export async function getReferencedRequirementIds(workspaceRoot) {
249
+ const { glob } = await import("glob");
250
+ const { parse } = await import("@babel/parser");
251
+ const traverse = await import("@babel/traverse");
252
+ const t = await import("@babel/types");
253
+ const fs = await import("node:fs");
254
+ // Find all test files using glob (same pattern as get_requirement tool)
255
+ const testFiles = await glob("**/*.{test,spec}.{js,jsx,ts,tsx}", {
256
+ cwd: workspaceRoot,
257
+ absolute: true,
258
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
259
+ });
260
+ const allRequirementIds = [];
261
+ // For each test file, parse AST and extract requirement IDs
262
+ for (const file of testFiles) {
263
+ try {
264
+ const code = fs.readFileSync(file, "utf-8");
265
+ const ast = parse(code, {
266
+ sourceType: "module",
267
+ plugins: ["typescript", "jsx"],
268
+ errorRecovery: true,
269
+ });
270
+ // Handle both ES module and CommonJS exports for traverse
271
+ // @babel/traverse can be imported as: traverse.default.default in compiled code
272
+ // biome-ignore lint/suspicious/noExplicitAny: see earlier comment on babel/traverse CJS/ESM interop
273
+ let traverseFunc = traverse;
274
+ if (typeof traverseFunc !== "function") {
275
+ traverseFunc = traverseFunc.default;
276
+ }
277
+ if (typeof traverseFunc !== "function") {
278
+ traverseFunc = traverseFunc.default;
279
+ }
280
+ traverseFunc(ast, {
281
+ // biome-ignore lint/suspicious/noExplicitAny: babel-traverse visitor `path` is a deeply-generic NodePath whose precise type depends on what handler you're inside; `any` is the documented practice for plugin code.
282
+ CallExpression(path) {
283
+ const callee = path.node.callee;
284
+ // Check if this is a requirement() call
285
+ if (t.isIdentifier(callee) && callee.name === "requirement") {
286
+ const args = path.node.arguments;
287
+ // Extract all string literal arguments (supports multi-requirement calls)
288
+ for (const arg of args) {
289
+ if (t.isStringLiteral(arg)) {
290
+ allRequirementIds.push(arg.value);
291
+ }
292
+ }
293
+ }
294
+ },
295
+ });
296
+ }
297
+ catch { }
298
+ }
299
+ // Extract root IDs by removing the path suffix (everything after the first dot)
300
+ const rootIds = allRequirementIds.map((id) => {
301
+ const dotIndex = id.indexOf(".");
302
+ return dotIndex > 0 ? id.substring(0, dotIndex) : id;
303
+ });
304
+ return new Set(rootIds);
305
+ }
306
+ //# sourceMappingURL=grep.js.map
@@ -0,0 +1,73 @@
1
+ import { type Metadata, type RequirementNode } from "../schema/index.js";
2
+ /**
3
+ * Flattened requirement for search results
4
+ */
5
+ export interface FlattenedRequirement {
6
+ id: string;
7
+ rootId: string;
8
+ label: string;
9
+ content: string;
10
+ path: string[];
11
+ sourceFile: string;
12
+ documentTitle: string;
13
+ }
14
+ /**
15
+ * Parsed requirements file
16
+ */
17
+ export interface ParsedRequirementsFile {
18
+ metadata: Metadata;
19
+ requirements: RequirementNode[];
20
+ }
21
+ /**
22
+ * Find all *.requirements.md files in the workspace.
23
+ * Supports both .requirements/ directories and colocated files.
24
+ * Excludes example files, test fixtures, and build artifacts.
25
+ */
26
+ export declare function findRequirementsFiles(workspaceRoot: string): Promise<string[]>;
27
+ /**
28
+ * Synchronous variant of {@link findRequirementsFiles}. Used by code paths
29
+ * that can't await (e.g. the test-harness lookup-cache builder).
30
+ */
31
+ export declare function findRequirementsFilesSync(workspaceRoot: string): string[];
32
+ /**
33
+ * Load all requirements from the workspace
34
+ */
35
+ export declare function loadAllRequirements(workspaceRoot: string): Promise<{
36
+ files: ParsedRequirementsFile[];
37
+ flattened: FlattenedRequirement[];
38
+ }>;
39
+ /**
40
+ * Flatten a requirements file into searchable requirements
41
+ */
42
+ export declare function flattenRequirementsFile(file: ParsedRequirementsFile, sourceFile: string): FlattenedRequirement[];
43
+ /**
44
+ * Search requirements by text query
45
+ */
46
+ export declare function searchRequirements(requirements: FlattenedRequirement[], query: string): FlattenedRequirement[];
47
+ /**
48
+ * Get a specific requirement by ID (supports partial matching)
49
+ */
50
+ export declare function getRequirementById(requirements: FlattenedRequirement[], id: string): FlattenedRequirement | undefined;
51
+ /**
52
+ * Get a requirement and all its children
53
+ */
54
+ export declare function getRequirementTree(requirements: FlattenedRequirement[], rootId: string): FlattenedRequirement[];
55
+ /**
56
+ * Format a requirement for display
57
+ */
58
+ export declare function formatRequirement(req: FlattenedRequirement): string;
59
+ /**
60
+ * Filter a requirements file's content to only include specified requirement keys.
61
+ * Parses the file content, filters top-level requirements by key, and rebuilds as
62
+ * markdown requirement blocks (without frontmatter).
63
+ */
64
+ export declare function filterRequirementsByKeys(fileContents: string, keys: string[]): {
65
+ filteredContent: string;
66
+ foundKeys: string[];
67
+ missingKeys: string[];
68
+ };
69
+ /**
70
+ * Format a requirement tree for display
71
+ */
72
+ export declare function formatRequirementTree(requirements: FlattenedRequirement[]): string;
73
+ //# sourceMappingURL=index.d.ts.map