@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
@@ -1,152 +1,207 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { findProjectRoot } from '../utils/project-settings.js';
4
- import { findRequirementsDir, readTrackingEntries, isCacheStale, } from '../harness/cache.js';
5
- function buildCoverageEntries(lookup, entries, filterKey) {
6
- // Aggregate tracking by key
7
- const locationsByKey = new Map();
8
- for (const entry of entries) {
9
- if (!locationsByKey.has(entry.requirementKey)) {
10
- locationsByKey.set(entry.requirementKey, new Set());
11
- }
12
- locationsByKey.get(entry.requirementKey).add(entry.callerLocation);
13
- }
14
- const results = [];
15
- for (const [key, req] of Object.entries(lookup.requirements)) {
16
- // Skip alias entries to avoid double-counting
17
- if (req.isAlias)
18
- continue;
19
- // Filter if requested
20
- if (filterKey && key !== filterKey && !key.startsWith(filterKey + '.')) {
21
- continue;
22
- }
23
- // Collect locations from both the numeric key and any alias keys that map to the same id
24
- const locations = new Set();
25
- const keyLocations = locationsByKey.get(key);
26
- if (keyLocations)
27
- keyLocations.forEach(l => locations.add(l));
28
- // Also check if tracking data was written using a label path
29
- for (const [aliasKey, aliasReq] of Object.entries(lookup.requirements)) {
30
- if (aliasReq.isAlias && aliasReq.id === req.id) {
31
- const aliasLocations = locationsByKey.get(aliasKey);
32
- if (aliasLocations)
33
- aliasLocations.forEach(l => locations.add(l));
34
- }
1
+ import { getProjectCoverage, getRequirementCoverage, } from "../requirements/cloud-coverage.js";
2
+ import { buildLocalReport, } from "../requirements/coverage.js";
3
+ import { findProjectRoot, getProjectCredentials, } from "../utils/project-settings.js";
4
+ const CONVEX_URL = "https://data.dotrequirements.io";
5
+ export async function reportCommand(options) {
6
+ const source = options.source ?? "local";
7
+ const format = options.format ?? "console";
8
+ if (source === "local" && (options.branch || options.since !== undefined)) {
9
+ throw new Error("--branch and --since require --source cloud. Local reports cover only the most recent test run on this machine.");
10
+ }
11
+ if (source === "local") {
12
+ const projectRoot = findProjectRoot(process.cwd());
13
+ if (!projectRoot) {
14
+ throw new Error('Could not find .requirements/ directory. Run "dotrequirements init" to initialize your project.');
35
15
  }
36
- results.push({
37
- key,
38
- label: req.label || null,
39
- content: req.content,
40
- tested: locations.size > 0,
41
- locations: Array.from(locations),
16
+ const report = buildLocalReport({
17
+ projectRoot,
18
+ requirementFilter: options.requirement,
42
19
  });
20
+ for (const warning of report.warnings) {
21
+ console.error(`Warning: ${warning}\n`);
22
+ }
23
+ console.log(printLocalReport(report, format));
24
+ return;
25
+ }
26
+ // source === "cloud"
27
+ let projectId;
28
+ let projectSecret;
29
+ try {
30
+ const creds = getProjectCredentials(process.cwd());
31
+ projectId = creds.projectId;
32
+ projectSecret = creds.projectSecret;
33
+ }
34
+ catch (error) {
35
+ throw new Error(`Cloud report requires cloud credentials. Run \`dotrequirements link\` to connect this project to the cloud.\n` +
36
+ `(${error instanceof Error ? error.message : String(error)})`);
37
+ }
38
+ if (options.requirement) {
39
+ const record = await getRequirementCoverage(options.requirement, projectId, projectSecret, CONVEX_URL);
40
+ console.log(printCloudRequirement(record, format));
41
+ return;
42
+ }
43
+ const record = await getProjectCoverage(projectId, projectSecret, CONVEX_URL, {
44
+ branch: options.branch,
45
+ sinceTimestamp: options.since,
46
+ });
47
+ console.log(printCloudProject(record, format, {
48
+ branch: options.branch,
49
+ since: options.since,
50
+ }));
51
+ }
52
+ function printLocalReport(report, format) {
53
+ switch (format) {
54
+ case "json":
55
+ return JSON.stringify({
56
+ source: "local",
57
+ summary: report.summary,
58
+ requirements: report.entries,
59
+ }, null, 2);
60
+ case "markdown":
61
+ return formatLocalMarkdown(report);
62
+ default:
63
+ return formatLocalConsole(report);
43
64
  }
44
- return results;
45
65
  }
46
- function formatConsole(coverage) {
47
- const tested = coverage.filter(c => c.tested).length;
48
- const total = coverage.length;
49
- const pct = total > 0 ? ((tested / total) * 100).toFixed(1) : '0.0';
50
- let out = `\n=== Requirements Coverage Report ===\n`;
51
- out += `\nTotal: ${total} | Tested: ${tested} | Untested: ${total - tested} | Coverage: ${pct}%\n`;
52
- const testedEntries = coverage.filter(c => c.tested);
66
+ function formatLocalConsole(report) {
67
+ const { total, tested, untested, coveragePercent } = report.summary;
68
+ let out = `\n=== Requirements Coverage Report (local) ===\n`;
69
+ out += `\nTotal: ${total} | Tested: ${tested} | Untested: ${untested} | Coverage: ${coveragePercent}%\n`;
70
+ const testedEntries = report.entries.filter((c) => c.tested);
53
71
  if (testedEntries.length > 0) {
54
- out += '\n✓ Tested:\n';
72
+ out += "\n✓ Tested:\n";
55
73
  for (const c of testedEntries) {
56
- const preview = c.content.length > 60 ? c.content.substring(0, 60) + '...' : c.content;
57
- out += ` ${c.key}: ${preview}\n`;
74
+ out += ` ${c.key}: ${preview(c.content, 60)}\n`;
58
75
  for (const loc of c.locations) {
59
76
  out += ` ← ${loc}\n`;
60
77
  }
61
78
  }
62
79
  }
63
- const untestedEntries = coverage.filter(c => !c.tested);
80
+ const untestedEntries = report.entries.filter((c) => !c.tested);
64
81
  if (untestedEntries.length > 0) {
65
- out += '\n✗ Untested:\n';
82
+ out += "\n✗ Untested:\n";
66
83
  for (const c of untestedEntries) {
67
- const preview = c.content.length > 60 ? c.content.substring(0, 60) + '...' : c.content;
68
- out += ` ${c.key}: ${preview}\n`;
84
+ out += ` ${c.key}: ${preview(c.content, 60)}\n`;
69
85
  }
70
86
  }
71
- out += '\n====================================\n';
87
+ out += "\n=============================================\n";
72
88
  return out;
73
89
  }
74
- function formatJson(coverage) {
75
- const tested = coverage.filter(c => c.tested).length;
76
- const total = coverage.length;
77
- return JSON.stringify({
78
- summary: {
79
- total,
80
- tested,
81
- untested: total - tested,
82
- coveragePercent: total > 0 ? parseFloat(((tested / total) * 100).toFixed(1)) : 0,
83
- },
84
- requirements: coverage,
85
- }, null, 2);
86
- }
87
- function formatMarkdown(coverage) {
88
- const tested = coverage.filter(c => c.tested).length;
89
- const total = coverage.length;
90
- const pct = total > 0 ? ((tested / total) * 100).toFixed(1) : '0.0';
91
- let out = `# Requirements Coverage Report\n\n`;
92
- out += `**Coverage:** ${tested}/${total} (${pct}%)\n\n`;
90
+ function formatLocalMarkdown(report) {
91
+ const { total, tested, coveragePercent } = report.summary;
92
+ let out = `# Requirements Coverage Report (local)\n\n`;
93
+ out += `**Coverage:** ${tested}/${total} (${coveragePercent}%)\n\n`;
93
94
  out += `| Requirement | Status | Locations |\n`;
94
95
  out += `|---|---|---|\n`;
95
- for (const c of coverage) {
96
- const status = c.tested ? '✓' : '✗';
97
- const locs = c.locations.join(', ') || '-';
98
- const preview = c.content.length > 40 ? c.content.substring(0, 40) + '...' : c.content;
99
- out += `| ${c.key}: ${preview} | ${status} | ${locs} |\n`;
96
+ for (const c of report.entries) {
97
+ const status = c.tested ? "✓" : "✗";
98
+ const locs = c.locations.join(", ") || "-";
99
+ out += `| ${c.key}: ${preview(c.content, 40)} | ${status} | ${locs} |\n`;
100
100
  }
101
101
  return out;
102
102
  }
103
- export async function reportCommand(options) {
104
- const projectRoot = findProjectRoot(process.cwd());
105
- if (!projectRoot) {
106
- throw new Error('Could not find .requirements directory. Run "dotrequirements init" to initialize your project.');
107
- }
108
- const requirementsDir = findRequirementsDir(projectRoot);
109
- const cacheDir = path.join(requirementsDir, '.cache');
110
- const lookupPath = path.join(cacheDir, 'lookup.json');
111
- if (!fs.existsSync(lookupPath)) {
112
- throw new Error('No lookup cache found. Run "dotrequirements prepare" first to build the cache.');
113
- }
114
- // Read lookup cache
115
- const lookupContent = fs.readFileSync(lookupPath, 'utf-8');
116
- const lookup = JSON.parse(lookupContent);
117
- // Warn if cache is stale (requirements changed since last prepare)
118
- try {
119
- const cacheStat = fs.statSync(lookupPath);
120
- if (isCacheStale(requirementsDir, cacheStat.mtimeMs)) {
121
- console.error('Warning: Cache may be stale — requirements have changed since last prepare. Run "dotrequirements prepare" to rebuild.\n');
103
+ function printCloudRequirement(record, format) {
104
+ if (format === "json") {
105
+ return JSON.stringify({ source: "cloud", requirement: record }, null, 2);
106
+ }
107
+ if (!record.lastTestedAt) {
108
+ return `\n${record.requirementKey}: never tested on the cloud-persisted record.\n`;
109
+ }
110
+ const lastTested = new Date(record.lastTestedAt).toISOString();
111
+ const lines = [];
112
+ lines.push(`\n=== Coverage for ${record.requirementKey} (cloud) ===\n`);
113
+ lines.push(`Last tested: ${lastTested}`);
114
+ if (record.branch)
115
+ lines.push(`Branch: ${record.branch}`);
116
+ if (record.testFile) {
117
+ lines.push(`Test: ${record.testFile}${record.testLine ? `:${record.testLine}` : ""}`);
118
+ }
119
+ if (record.allBranches.length > 1) {
120
+ lines.push(`\nAlso tested on:`);
121
+ for (const b of record.allBranches) {
122
+ if (b.branch === record.branch)
123
+ continue;
124
+ lines.push(` ${b.branch} — ${new Date(b.lastTestedAt).toISOString()}${b.testFile ? ` (${b.testFile}${b.testLine ? `:${b.testLine}` : ""})` : ""}`);
122
125
  }
123
126
  }
124
- catch {
125
- // Ignore stat errors
127
+ lines.push("");
128
+ if (format === "markdown") {
129
+ return [
130
+ `# Coverage for ${record.requirementKey} (cloud)\n`,
131
+ `**Last tested:** ${lastTested}`,
132
+ record.branch ? `**Branch:** ${record.branch}` : "",
133
+ record.testFile
134
+ ? `**Test location:** \`${record.testFile}${record.testLine ? `:${record.testLine}` : ""}\``
135
+ : "",
136
+ ]
137
+ .filter(Boolean)
138
+ .join("\n");
126
139
  }
127
- // Read tracking entries
128
- const trackingPath = path.join(cacheDir, 'tracking.jsonl');
129
- const trackingExists = fs.existsSync(trackingPath);
130
- const entries = readTrackingEntries(requirementsDir);
131
- if (!trackingExists) {
132
- console.error('Warning: No tracking data found. Run your tests first, or run "dotrequirements prepare" to start a new tracking session.\n');
140
+ return lines.join("\n");
141
+ }
142
+ function printCloudProject(record, format, filters) {
143
+ if (format === "json") {
144
+ return JSON.stringify({ source: "cloud", filters, summary: cloudSummary(record), ...record }, null, 2);
133
145
  }
134
- else if (entries.length === 0) {
135
- console.error('Warning: Tracking file is empty — no requirements were tracked during tests.\n');
146
+ const summary = cloudSummary(record);
147
+ const filterNote = formatCloudFilters(filters);
148
+ if (format === "markdown") {
149
+ let out = `# Requirements Coverage Report (cloud)\n\n`;
150
+ if (filterNote)
151
+ out += `${filterNote}\n\n`;
152
+ out += `**Coverage:** ${summary.tested}/${summary.total} (${summary.coveragePercent}%)\n\n`;
153
+ if (record.tested.length > 0) {
154
+ out += `## Tested\n\n`;
155
+ for (const t of record.tested) {
156
+ out += `- **${t.requirementKey}** [${t.branch}] — ${new Date(t.lastTestedAt).toISOString()}\n`;
157
+ }
158
+ out += "\n";
159
+ }
160
+ if (record.untested.length > 0) {
161
+ out += `## Untested\n\n`;
162
+ for (const key of record.untested) {
163
+ out += `- ${key}\n`;
164
+ }
165
+ }
166
+ return out;
136
167
  }
137
- const coverage = buildCoverageEntries(lookup, entries, options.requirement);
138
- const format = options.format || 'console';
139
- switch (format) {
140
- case 'json':
141
- console.log(formatJson(coverage));
142
- break;
143
- case 'markdown':
144
- console.log(formatMarkdown(coverage));
145
- break;
146
- case 'console':
147
- default:
148
- console.log(formatConsole(coverage));
149
- break;
168
+ let out = `\n=== Project Coverage Report (cloud) ===\n`;
169
+ if (filterNote)
170
+ out += `${filterNote}\n`;
171
+ out += `\nTotal: ${summary.total} | Tested: ${summary.tested} | Untested: ${summary.untested} | Coverage: ${summary.coveragePercent}%\n`;
172
+ if (record.tested.length > 0) {
173
+ out += "\n✓ Tested:\n";
174
+ for (const t of record.tested) {
175
+ const date = new Date(t.lastTestedAt).toISOString();
176
+ out += ` ${t.requirementKey} [${t.branch}] — ${date}\n`;
177
+ }
178
+ }
179
+ if (record.untested.length > 0) {
180
+ out += "\n✗ Untested:\n";
181
+ for (const key of record.untested) {
182
+ out += ` ${key}\n`;
183
+ }
184
+ }
185
+ out += "\n=======================================\n";
186
+ return out;
187
+ }
188
+ function cloudSummary(record) {
189
+ const tested = record.tested.length;
190
+ const untested = record.untested.length;
191
+ const total = tested + untested;
192
+ const coveragePercent = total > 0 ? parseFloat(((tested / total) * 100).toFixed(1)) : 0;
193
+ return { total, tested, untested, coveragePercent };
194
+ }
195
+ function formatCloudFilters(filters) {
196
+ const parts = [];
197
+ if (filters.branch)
198
+ parts.push(`branch=${filters.branch}`);
199
+ if (filters.since !== undefined) {
200
+ parts.push(`since=${new Date(filters.since).toISOString()}`);
150
201
  }
202
+ return parts.length > 0 ? `Filters: ${parts.join(", ")}` : "";
203
+ }
204
+ function preview(s, max) {
205
+ return s.length > max ? `${s.substring(0, max)}...` : s;
151
206
  }
152
207
  //# sourceMappingURL=report.js.map
@@ -0,0 +1,2 @@
1
+ export declare function requirementsForCommand(testFile: string): Promise<void>;
2
+ //# sourceMappingURL=requirements-for.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { findRequirementsInFile } from "../requirements/grep.js";
4
+ import { getRequirementById, loadAllRequirements, } from "../requirements/index.js";
5
+ export async function requirementsForCommand(testFile) {
6
+ const workspaceRoot = process.cwd();
7
+ const fullPath = resolve(workspaceRoot, testFile);
8
+ // CLI-FIND-MAP-1.1: missing file → error + non-zero exit
9
+ if (!existsSync(fullPath)) {
10
+ throw new Error(`Test file not found: ${testFile}`);
11
+ }
12
+ const { flattened } = await loadAllRequirements(workspaceRoot);
13
+ const refs = await findRequirementsInFile(testFile);
14
+ // CLI-FIND-MAP-1.2: no references → explicit message
15
+ if (refs.length === 0) {
16
+ console.log(`No requirement() references found in "${testFile}".`);
17
+ return;
18
+ }
19
+ // CLI-FIND-MAP-1.0: line, ID, content for each reference
20
+ console.log(`Requirements referenced in "${testFile}":\n`);
21
+ for (const ref of refs) {
22
+ const req = getRequirementById(flattened, ref.requirementId);
23
+ const content = req
24
+ ? `${req.label}: ${req.content}`
25
+ : "(requirement not found)";
26
+ console.log(` Line ${ref.line}: ${ref.requirementId} — ${content}`);
27
+ }
28
+ }
29
+ //# sourceMappingURL=requirements-for.js.map
@@ -0,0 +1,2 @@
1
+ export declare function reviewTestCommand(testFilePath: string): Promise<void>;
2
+ //# sourceMappingURL=review-test.d.ts.map
@@ -0,0 +1,75 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { DEFAULT_API_BASE_URL, fetchReviewTestFeedback, } from "../requirements/cloud-ai.js";
4
+ import { formatRequirementTree, getRequirementTree, loadAllRequirements, } from "../requirements/index.js";
5
+ import { getProjectCredentials } from "../utils/project-settings.js";
6
+ export async function reviewTestCommand(testFilePath) {
7
+ const workspaceRoot = process.cwd();
8
+ const fullPath = resolve(workspaceRoot, testFilePath);
9
+ // CLI-REVIEW-1.5: missing file → error + non-zero exit
10
+ if (!existsSync(fullPath)) {
11
+ throw new Error(`File not found: ${testFilePath}`);
12
+ }
13
+ // CLI-REVIEW-1.6: must be a test file
14
+ if (!/\.(test|spec)\.(js|jsx|ts|tsx)$/.test(testFilePath)) {
15
+ throw new Error(`File must be a test file: *.{test,spec}.{js,jsx,ts,tsx} — got ${testFilePath}`);
16
+ }
17
+ const testFileContents = readFileSync(fullPath, "utf-8");
18
+ // CLI-REVIEW-1.0: collect every requirement(...) call site
19
+ const requirementIdMatches = testFileContents.matchAll(/requirement\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g);
20
+ const requirementIds = new Set();
21
+ for (const match of requirementIdMatches) {
22
+ requirementIds.add(match[1]);
23
+ }
24
+ // CLI-REVIEW-1.1: resolve references against the workspace and bundle trees
25
+ const { flattened } = await loadAllRequirements(workspaceRoot);
26
+ const rootToTestedIds = new Map();
27
+ for (const reqId of requirementIds) {
28
+ const req = flattened.find((r) => r.id === reqId || r.rootId === reqId);
29
+ if (req) {
30
+ if (!rootToTestedIds.has(req.rootId)) {
31
+ rootToTestedIds.set(req.rootId, new Set());
32
+ }
33
+ rootToTestedIds.get(req.rootId).add(reqId);
34
+ }
35
+ }
36
+ const requirements = [];
37
+ for (const [rootId, testedIds] of rootToTestedIds) {
38
+ const tree = getRequirementTree(flattened, rootId);
39
+ requirements.push({
40
+ id: rootId,
41
+ content: formatRequirementTree(tree),
42
+ testedIds: Array.from(testedIds).sort(),
43
+ });
44
+ }
45
+ // CLI-REVIEW-1.6: cloud credentials required
46
+ let projectId;
47
+ let projectSecret;
48
+ try {
49
+ const creds = getProjectCredentials(workspaceRoot);
50
+ projectId = creds.projectId;
51
+ projectSecret = creds.projectSecret;
52
+ }
53
+ catch (error) {
54
+ throw new Error(`Test review requires cloud credentials. Run \`dotrequirements link\` to connect this project to the cloud.\n` +
55
+ `(${error instanceof Error ? error.message : String(error)})`);
56
+ }
57
+ // CLI-REVIEW-1.2 / .3 / .4 / .7 / .8: dispatch to the hosted endpoint
58
+ const apiBaseUrl = process.env.DOTREQUIREMENTS_API_URL ?? DEFAULT_API_BASE_URL;
59
+ let feedback;
60
+ try {
61
+ feedback = await fetchReviewTestFeedback({
62
+ apiBaseUrl,
63
+ projectId,
64
+ projectSecret,
65
+ testFileContents,
66
+ requirements,
67
+ });
68
+ }
69
+ catch (error) {
70
+ throw new Error(`Test review failed: ${error instanceof Error ? error.message : String(error)}`);
71
+ }
72
+ console.log(`Test Review Results for ${testFilePath}\n`);
73
+ console.log(feedback);
74
+ }
75
+ //# sourceMappingURL=review-test.js.map
@@ -0,0 +1,6 @@
1
+ interface SearchOptions {
2
+ regex?: boolean;
3
+ }
4
+ export declare function searchCommand(query: string, options: SearchOptions): Promise<void>;
5
+ export {};
6
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1,39 @@
1
+ import { formatRequirementTree, getRequirementTree, loadAllRequirements, searchRequirements, } from "../requirements/index.js";
2
+ export async function searchCommand(query, options) {
3
+ const { flattened } = await loadAllRequirements(process.cwd());
4
+ let results;
5
+ // CLI-FIND-SEARCH-1.1: --regex interprets query as case-insensitive regex
6
+ if (options.regex) {
7
+ let regex;
8
+ try {
9
+ regex = new RegExp(query, "i");
10
+ }
11
+ catch (error) {
12
+ // CLI-FIND-SEARCH-1.2: invalid regex → error + non-zero exit
13
+ throw new Error(`Invalid regular expression: ${error instanceof Error ? error.message : String(error)}`);
14
+ }
15
+ results = flattened.filter((r) => regex.test(r.id) || regex.test(r.content) || regex.test(r.label));
16
+ }
17
+ else {
18
+ // CLI-FIND-SEARCH-1.0: text search across ID/content/label
19
+ results = searchRequirements(flattened, query);
20
+ }
21
+ // CLI-FIND-SEARCH-1.4: matches on child criteria bubble up to roots
22
+ const matchedRootIds = new Set(results.map((r) => r.rootId));
23
+ const rootResults = flattened.filter((r) => r.path.length === 0 && matchedRootIds.has(r.rootId));
24
+ // CLI-FIND-SEARCH-1.3: no matches → informative line
25
+ if (rootResults.length === 0) {
26
+ console.log(`No requirements found matching "${query}"${options.regex ? " (regex)" : ""}.`);
27
+ return;
28
+ }
29
+ // CLI-FIND-SEARCH-1.5: each result includes ID, source document, full tree
30
+ console.log(`Found ${rootResults.length} requirement(s) matching "${query}"${options.regex ? " (regex)" : ""}:\n`);
31
+ for (const r of rootResults) {
32
+ const tree = getRequirementTree(flattened, r.id);
33
+ console.log(`${r.id}`);
34
+ console.log(` Source: ${r.documentTitle}`);
35
+ console.log(formatRequirementTree(tree));
36
+ console.log();
37
+ }
38
+ }
39
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1,7 @@
1
+ interface StyleCheckOptions {
2
+ keys?: string[];
3
+ model?: string;
4
+ }
5
+ export declare function styleCheckCommand(filePath: string, options: StyleCheckOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=style-check.d.ts.map
@@ -0,0 +1,75 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { DEFAULT_API_BASE_URL, fetchStyleCheckFeedback, } from "../requirements/cloud-ai.js";
4
+ import { filterRequirementsByKeys } from "../requirements/index.js";
5
+ import { getProjectCredentials } from "../utils/project-settings.js";
6
+ export async function styleCheckCommand(filePath, options) {
7
+ const workspaceRoot = process.cwd();
8
+ const fullPath = resolve(workspaceRoot, filePath);
9
+ // CLI-STYLE-1.5: missing file → error + non-zero exit
10
+ if (!existsSync(fullPath)) {
11
+ throw new Error(`File not found: ${filePath}`);
12
+ }
13
+ // CLI-STYLE-1.0 / .1 / .6: file-type detection
14
+ const isRequirementsFile = filePath.endsWith(".requirements.md");
15
+ const isTestFile = /\.(test|spec)\.(js|jsx|ts|tsx)$/.test(filePath);
16
+ if (!isRequirementsFile && !isTestFile) {
17
+ throw new Error(`Unsupported file type: ${filePath}\n` +
18
+ `File must be a requirements file (*.requirements.md) or a test file (*.{test,spec}.{js,jsx,ts,tsx}).`);
19
+ }
20
+ const fileType = isRequirementsFile ? "requirements" : "test";
21
+ const fileContents = readFileSync(fullPath, "utf-8");
22
+ // CLI-STYLE-1.2 / .3: --keys handling
23
+ let fileContentsToCheck = fileContents;
24
+ let scopeNote = "";
25
+ if (options.keys && options.keys.length > 0) {
26
+ if (!isRequirementsFile) {
27
+ throw new Error(`--keys can only be used with requirements files (*.requirements.md), not test files.`);
28
+ }
29
+ const { filteredContent, foundKeys, missingKeys } = filterRequirementsByKeys(fileContents, options.keys);
30
+ if (foundKeys.length === 0) {
31
+ throw new Error(`None of the specified requirement keys were found in ${filePath}: ${options.keys.join(", ")}`);
32
+ }
33
+ fileContentsToCheck = filteredContent;
34
+ if (missingKeys.length > 0) {
35
+ scopeNote = `\nNote: Some specified keys were not found in the file: ${missingKeys.join(", ")}`;
36
+ }
37
+ }
38
+ // CLI-STYLE-1.7: credentials are required
39
+ let projectId;
40
+ let projectSecret;
41
+ try {
42
+ const creds = getProjectCredentials(workspaceRoot);
43
+ projectId = creds.projectId;
44
+ projectSecret = creds.projectSecret;
45
+ }
46
+ catch (error) {
47
+ throw new Error(`Style check requires cloud credentials. Run \`dotrequirements link\` to connect this project to the cloud.\n` +
48
+ `(${error instanceof Error ? error.message : String(error)})`);
49
+ }
50
+ // CLI-STYLE-1.4 / .8 / .9: dispatch to the hosted endpoint
51
+ const apiBaseUrl = process.env.DOTREQUIREMENTS_API_URL ?? DEFAULT_API_BASE_URL;
52
+ let feedback;
53
+ try {
54
+ feedback = await fetchStyleCheckFeedback({
55
+ apiBaseUrl,
56
+ projectId,
57
+ projectSecret,
58
+ fileContents: fileContentsToCheck,
59
+ fileType,
60
+ model: options.model,
61
+ });
62
+ }
63
+ catch (error) {
64
+ throw new Error(`Style check failed: ${error instanceof Error ? error.message : String(error)}`);
65
+ }
66
+ const scopeLabel = options.keys && options.keys.length > 0
67
+ ? ` (${options.keys.join(", ")})`
68
+ : "";
69
+ console.log(`Style Check Results for ${filePath}${scopeLabel}\n`);
70
+ console.log(feedback);
71
+ if (scopeNote) {
72
+ console.log(scopeNote);
73
+ }
74
+ }
75
+ //# sourceMappingURL=style-check.js.map