@popoverai/dotrequirements 0.23.0 → 0.24.1

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 (235) hide show
  1. package/README.md +169 -22
  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-initial.d.ts +11 -0
  36. package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
  37. package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
  38. package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
  39. package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
  40. package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
  41. package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
  42. package/dist/codebase-to-spec/prompts/specifier.js +100 -0
  43. package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
  44. package/dist/codebase-to-spec/prompts/style-check.js +78 -0
  45. package/dist/codebase-to-spec/schemas.d.ts +257 -0
  46. package/dist/codebase-to-spec/schemas.js +183 -0
  47. package/dist/codebase-to-spec/skill-install.d.ts +57 -0
  48. package/dist/codebase-to-spec/skill-install.js +79 -0
  49. package/dist/codebase-to-spec/slice.d.ts +49 -0
  50. package/dist/codebase-to-spec/slice.js +111 -0
  51. package/dist/codebase-to-spec/specifier.d.ts +60 -0
  52. package/dist/codebase-to-spec/specifier.js +79 -0
  53. package/dist/codebase-to-spec/style-check.d.ts +29 -0
  54. package/dist/codebase-to-spec/style-check.js +33 -0
  55. package/dist/codebase-to-spec/summary.d.ts +51 -0
  56. package/dist/codebase-to-spec/summary.js +183 -0
  57. package/dist/codebase-to-spec/validate.d.ts +46 -0
  58. package/dist/codebase-to-spec/validate.js +130 -0
  59. package/dist/commands/acceptance-test.d.ts +6 -0
  60. package/dist/commands/{browsertest.js → acceptance-test.js} +36 -29
  61. package/dist/commands/ai-setup.d.ts +5 -0
  62. package/dist/commands/ai-setup.js +441 -0
  63. package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
  64. package/dist/commands/codebase-to-spec/compose.js +57 -0
  65. package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
  66. package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
  67. package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
  68. package/dist/commands/codebase-to-spec/fan-out.js +77 -0
  69. package/dist/commands/codebase-to-spec/index.d.ts +9 -0
  70. package/dist/commands/codebase-to-spec/index.js +135 -0
  71. package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
  72. package/dist/commands/codebase-to-spec/pack.js +76 -0
  73. package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
  74. package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
  75. package/dist/commands/codebase-to-spec/present.d.ts +21 -0
  76. package/dist/commands/codebase-to-spec/present.js +92 -0
  77. package/dist/commands/codebase-to-spec/run.d.ts +20 -0
  78. package/dist/commands/codebase-to-spec/run.js +85 -0
  79. package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
  80. package/dist/commands/codebase-to-spec/skill-install.js +51 -0
  81. package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
  82. package/dist/commands/codebase-to-spec/specify-area.js +82 -0
  83. package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
  84. package/dist/commands/codebase-to-spec/style-check.js +42 -0
  85. package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
  86. package/dist/commands/codebase-to-spec/validate.js +38 -0
  87. package/dist/commands/create-requirement-document.d.ts +2 -0
  88. package/dist/commands/create-requirement-document.js +41 -0
  89. package/dist/commands/finalize.js +7 -7
  90. package/dist/commands/get.d.ts +2 -0
  91. package/dist/commands/get.js +55 -0
  92. package/dist/commands/init.js +132 -117
  93. package/dist/commands/link.js +27 -27
  94. package/dist/commands/list.d.ts +6 -0
  95. package/dist/commands/list.js +43 -0
  96. package/dist/commands/mcp.js +1 -1
  97. package/dist/commands/prepare.js +4 -4
  98. package/dist/commands/pull.js +116 -121
  99. package/dist/commands/push.js +106 -112
  100. package/dist/commands/report.d.ts +6 -2
  101. package/dist/commands/report.js +177 -122
  102. package/dist/commands/requirements-for.d.ts +2 -0
  103. package/dist/commands/requirements-for.js +29 -0
  104. package/dist/commands/review-test.d.ts +2 -0
  105. package/dist/commands/review-test.js +75 -0
  106. package/dist/commands/search.d.ts +6 -0
  107. package/dist/commands/search.js +39 -0
  108. package/dist/commands/style-check.d.ts +7 -0
  109. package/dist/commands/style-check.js +75 -0
  110. package/dist/commands/tests-for.d.ts +2 -0
  111. package/dist/commands/tests-for.js +80 -0
  112. package/dist/commands/validate.d.ts +6 -0
  113. package/dist/commands/validate.js +72 -0
  114. package/dist/config.js +1 -1
  115. package/dist/convex.d.ts +34 -22
  116. package/dist/convex.js +38 -22
  117. package/dist/harness/cache.d.ts +1 -5
  118. package/dist/harness/cache.js +49 -59
  119. package/dist/harness/convexReporting.d.ts +1 -1
  120. package/dist/harness/convexReporting.js +9 -7
  121. package/dist/harness/coverageCache.js +3 -3
  122. package/dist/harness/finalize.js +59 -46
  123. package/dist/harness/index.d.ts +6 -7
  124. package/dist/harness/index.js +9 -10
  125. package/dist/harness/prepare.js +6 -5
  126. package/dist/harness/requirementsLoader.d.ts +2 -2
  127. package/dist/harness/requirementsLoader.js +13 -35
  128. package/dist/harness/tracking.js +18 -18
  129. package/dist/harness/types.d.ts +1 -1
  130. package/dist/mcp/convexClient.d.ts +0 -39
  131. package/dist/mcp/convexClient.js +2 -107
  132. package/dist/mcp/handlers/authoring.d.ts +1 -1
  133. package/dist/mcp/handlers/authoring.js +30 -234
  134. package/dist/mcp/handlers/debug.d.ts +2 -3
  135. package/dist/mcp/handlers/debug.js +10 -10
  136. package/dist/mcp/handlers/get.d.ts +1 -1
  137. package/dist/mcp/handlers/get.js +11 -10
  138. package/dist/mcp/handlers/index.d.ts +20 -20
  139. package/dist/mcp/handlers/index.js +10 -10
  140. package/dist/mcp/handlers/list.d.ts +4 -33
  141. package/dist/mcp/handlers/list.js +16 -38
  142. package/dist/mcp/handlers/push.d.ts +1 -1
  143. package/dist/mcp/handlers/push.js +28 -18
  144. package/dist/mcp/handlers/report.d.ts +16 -0
  145. package/dist/mcp/handlers/report.js +134 -0
  146. package/dist/mcp/handlers/review.d.ts +1 -1
  147. package/dist/mcp/handlers/review.js +40 -59
  148. package/dist/mcp/handlers/search.d.ts +1 -1
  149. package/dist/mcp/handlers/search.js +7 -9
  150. package/dist/mcp/handlers/test-mapping.d.ts +1 -1
  151. package/dist/mcp/handlers/test-mapping.js +14 -14
  152. package/dist/mcp/handlers/types.d.ts +3 -3
  153. package/dist/mcp/handlers/types.js +2 -2
  154. package/dist/mcp/index.d.ts +1 -1
  155. package/dist/mcp/index.js +147 -167
  156. package/dist/push/core.d.ts +2 -2
  157. package/dist/push/core.js +20 -20
  158. package/dist/push/index.d.ts +1 -1
  159. package/dist/push/index.js +2 -2
  160. package/dist/requirements/cloud-ai.d.ts +57 -0
  161. package/dist/requirements/cloud-ai.js +104 -0
  162. package/dist/requirements/cloud-coverage.d.ts +41 -0
  163. package/dist/requirements/cloud-coverage.js +60 -0
  164. package/dist/requirements/coverage.d.ts +45 -0
  165. package/dist/requirements/coverage.js +114 -0
  166. package/dist/{mcp → requirements}/grep.d.ts +10 -1
  167. package/dist/{mcp → requirements}/grep.js +89 -44
  168. package/dist/{mcp/requirements.d.ts → requirements/index.d.ts} +19 -3
  169. package/dist/{mcp/requirements.js → requirements/index.js} +54 -35
  170. package/dist/requirements/style-guide.d.ts +67 -0
  171. package/dist/requirements/style-guide.js +299 -0
  172. package/dist/{mcp → requirements}/testCodeExtractor.js +24 -26
  173. package/dist/schema/browser.d.ts +8 -8
  174. package/dist/schema/browser.js +13 -15
  175. package/dist/schema/builder.d.ts +1 -1
  176. package/dist/schema/builder.js +13 -44
  177. package/dist/schema/conversions.d.ts +2 -2
  178. package/dist/schema/conversions.js +11 -11
  179. package/dist/schema/index.d.ts +9 -9
  180. package/dist/schema/index.js +15 -15
  181. package/dist/schema/parser-core.d.ts +1 -1
  182. package/dist/schema/parser-core.js +23 -22
  183. package/dist/schema/parser.d.ts +3 -3
  184. package/dist/schema/parser.js +27 -31
  185. package/dist/schema/resolver.d.ts +1 -1
  186. package/dist/schema/resolver.js +9 -9
  187. package/dist/schema/scenario.d.ts +1 -1
  188. package/dist/schema/scenario.js +1 -1
  189. package/dist/schema/schemas.d.ts +3 -3
  190. package/dist/schema/schemas.js +41 -28
  191. package/dist/schema/test-schema.js +27 -27
  192. package/dist/templates/context-file-section.md +3 -2
  193. package/dist/templates/example-requirements.js +1 -1
  194. package/dist/templates/example-requirements.ts +3 -1
  195. package/dist/templates/requirements-readme.js +1 -1
  196. package/dist/templates/requirements-readme.ts +1 -1
  197. package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
  198. package/dist/utils/brand.js +3 -3
  199. package/dist/utils/browser-launch.js +4 -4
  200. package/dist/utils/context-file.d.ts +1 -1
  201. package/dist/utils/context-file.js +26 -26
  202. package/dist/utils/env.js +7 -7
  203. package/dist/utils/gitignore.js +7 -7
  204. package/dist/utils/oauth-callback-server.d.ts +1 -1
  205. package/dist/utils/oauth-callback-server.js +27 -25
  206. package/dist/utils/oauth-flow.js +32 -29
  207. package/dist/utils/project-discovery.d.ts +3 -3
  208. package/dist/utils/project-discovery.js +18 -17
  209. package/dist/utils/project-name.js +8 -8
  210. package/dist/utils/project-selector.d.ts +1 -1
  211. package/dist/utils/project-selector.js +24 -21
  212. package/dist/utils/project-settings.d.ts +1 -1
  213. package/dist/utils/project-settings.js +24 -22
  214. package/dist/utils/templates.js +6 -6
  215. package/package.json +3 -2
  216. package/dist/commands/browsertest.d.ts +0 -6
  217. package/dist/commands/login.d.ts +0 -12
  218. package/dist/commands/login.js +0 -117
  219. package/dist/commands/logout.d.ts +0 -5
  220. package/dist/commands/logout.js +0 -17
  221. package/dist/commands/mcp-setup.d.ts +0 -5
  222. package/dist/commands/mcp-setup.js +0 -431
  223. package/dist/commands/test.d.ts +0 -6
  224. package/dist/commands/test.js +0 -78
  225. package/dist/mcp/handlers/coverage.d.ts +0 -44
  226. package/dist/mcp/handlers/coverage.js +0 -105
  227. package/dist/mcp/types.d.ts +0 -27
  228. package/dist/mcp/types.js +0 -2
  229. package/dist/utils/local-project.d.ts +0 -31
  230. package/dist/utils/local-project.js +0 -33
  231. package/dist/utils/token-refresh.d.ts +0 -24
  232. package/dist/utils/token-refresh.js +0 -69
  233. package/dist/utils/token-storage.d.ts +0 -31
  234. package/dist/utils/token-storage.js +0 -57
  235. /package/dist/{mcp → requirements}/testCodeExtractor.d.ts +0 -0
@@ -1,12 +1,12 @@
1
- import { execFile } from 'child_process';
2
- import { promisify } from 'util';
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
3
  const execFileAsync = promisify(execFile);
4
4
  /**
5
5
  * Check if ripgrep is available
6
6
  */
7
7
  async function hasRipgrep() {
8
8
  try {
9
- await execFileAsync('rg', ['--version']);
9
+ await execFileAsync("rg", ["--version"]);
10
10
  return true;
11
11
  }
12
12
  catch {
@@ -21,7 +21,7 @@ async function hasRipgrep() {
21
21
  export async function findTestsForRequirement(workspaceRoot, requirementId) {
22
22
  const useRg = await hasRipgrep();
23
23
  // Escape special regex characters in the ID
24
- const escapedId = requirementId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
24
+ const escapedId = requirementId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25
25
  // Pattern to match requirement('ID'), requirement("ID"), or requirement(`ID`)
26
26
  const pattern = `requirement\\s*\\(\\s*['"\`]${escapedId}`;
27
27
  try {
@@ -29,17 +29,39 @@ export async function findTestsForRequirement(workspaceRoot, requirementId) {
29
29
  if (useRg) {
30
30
  // Use ripgrep for better performance
31
31
  // Using execFile with array args to avoid shell injection
32
- const { stdout } = await execFileAsync('rg', ['-n', '--column', pattern, '--type', 'ts', '--type', 'js', '--type', 'tsx', '--type', 'jsx', workspaceRoot], { maxBuffer: 10 * 1024 * 1024 }).catch(() => ({ stdout: '' })); // ripgrep returns non-zero on no matches
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
33
46
  output = stdout;
34
47
  }
35
48
  else {
36
49
  // Fall back to grep using execFile
37
- const { stdout } = await execFileAsync('grep', ['-rn', '--include=*.ts', '--include=*.tsx', '--include=*.js', '--include=*.jsx', '-E', pattern, workspaceRoot], { maxBuffer: 10 * 1024 * 1024 }).catch(() => ({ stdout: '' })); // grep returns non-zero on no matches
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
38
60
  output = stdout;
39
61
  }
40
62
  return parseGrepOutput(output, requirementId, useRg);
41
63
  }
42
- catch (error) {
64
+ catch {
43
65
  // Grep returns exit code 1 when no matches found
44
66
  return [];
45
67
  }
@@ -48,11 +70,11 @@ export async function findTestsForRequirement(workspaceRoot, requirementId) {
48
70
  * Find all requirement references in a specific file using AST parsing
49
71
  */
50
72
  export async function findRequirementsInFile(filePath) {
51
- const { parse } = await import('@babel/parser');
52
- const traverse = await import('@babel/traverse');
53
- const t = await import('@babel/types');
54
- const fs = await import('fs');
55
- const pathModule = await import('path');
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");
56
78
  const results = [];
57
79
  // Resolve relative paths from current working directory
58
80
  let resolvedPath = pathModule.isAbsolute(filePath)
@@ -60,32 +82,34 @@ export async function findRequirementsInFile(filePath) {
60
82
  : pathModule.resolve(process.cwd(), filePath);
61
83
  // If file doesn't exist and path is relative, try resolving from CLI package root
62
84
  if (!pathModule.isAbsolute(filePath) && !fs.existsSync(resolvedPath)) {
63
- const cliPackageRoot = pathModule.resolve(__dirname, '../..');
85
+ const cliPackageRoot = pathModule.resolve(__dirname, "../..");
64
86
  const alternativePath = pathModule.resolve(cliPackageRoot, filePath);
65
87
  if (fs.existsSync(alternativePath)) {
66
88
  resolvedPath = alternativePath;
67
89
  }
68
90
  }
69
91
  try {
70
- const code = fs.readFileSync(resolvedPath, 'utf-8');
92
+ const code = fs.readFileSync(resolvedPath, "utf-8");
71
93
  const ast = parse(code, {
72
- sourceType: 'module',
73
- plugins: ['typescript', 'jsx'],
94
+ sourceType: "module",
95
+ plugins: ["typescript", "jsx"],
74
96
  errorRecovery: true,
75
97
  });
76
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.
77
100
  let traverseFunc = traverse;
78
- if (typeof traverseFunc !== 'function') {
101
+ if (typeof traverseFunc !== "function") {
79
102
  traverseFunc = traverseFunc.default;
80
103
  }
81
- if (typeof traverseFunc !== 'function') {
104
+ if (typeof traverseFunc !== "function") {
82
105
  traverseFunc = traverseFunc.default;
83
106
  }
84
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.
85
109
  CallExpression(path) {
86
110
  const callee = path.node.callee;
87
111
  // Check if this is a requirement() call
88
- if (t.isIdentifier(callee) && callee.name === 'requirement') {
112
+ if (t.isIdentifier(callee) && callee.name === "requirement") {
89
113
  const args = path.node.arguments;
90
114
  const loc = path.node.loc;
91
115
  // Extract all string literal arguments (supports multi-requirement calls)
@@ -98,7 +122,7 @@ export async function findRequirementsInFile(filePath) {
98
122
  line: loc.start.line,
99
123
  column: loc.start.column + 1, // 1-indexed
100
124
  requirementId,
101
- context: code.split('\n')[loc.start.line - 1] || '',
125
+ context: code.split("\n")[loc.start.line - 1] || "",
102
126
  });
103
127
  }
104
128
  }
@@ -108,7 +132,7 @@ export async function findRequirementsInFile(filePath) {
108
132
  });
109
133
  return results;
110
134
  }
111
- catch (error) {
135
+ catch {
112
136
  return [];
113
137
  }
114
138
  }
@@ -125,14 +149,36 @@ export async function findAllTestReferences(workspaceRoot) {
125
149
  let output;
126
150
  if (useRg) {
127
151
  // Using execFile with array args to avoid shell injection
128
- const { stdout } = await execFileAsync('rg', ['-n', '--column', pattern, '--type', 'ts', '--type', 'js', '--type', 'tsx', '--type', 'jsx', workspaceRoot], { maxBuffer: 10 * 1024 * 1024 }).catch(() => ({ stdout: '' })); // ripgrep returns non-zero on no matches
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
129
166
  output = stdout;
130
167
  }
131
168
  else {
132
- const { stdout } = await execFileAsync('grep', ['-rn', '--include=*.ts', '--include=*.tsx', '--include=*.js', '--include=*.jsx', '-E', pattern, workspaceRoot], { maxBuffer: 10 * 1024 * 1024 }).catch(() => ({ stdout: '' })); // grep returns non-zero on no matches
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
133
179
  output = stdout;
134
180
  }
135
- return parseGrepOutput(output, '', useRg);
181
+ return parseGrepOutput(output, "", useRg);
136
182
  }
137
183
  catch {
138
184
  return [];
@@ -143,7 +189,7 @@ export async function findAllTestReferences(workspaceRoot) {
143
189
  */
144
190
  function parseGrepOutput(output, expectedId, isRipgrep, singleFilePath) {
145
191
  const results = [];
146
- const lines = output.trim().split('\n').filter(Boolean);
192
+ const lines = output.trim().split("\n").filter(Boolean);
147
193
  for (const line of lines) {
148
194
  let match;
149
195
  let file;
@@ -200,41 +246,43 @@ function parseGrepOutput(output, expectedId, isRipgrep, singleFilePath) {
200
246
  * Uses AST parsing instead of grep for reliability across environments
201
247
  */
202
248
  export async function getReferencedRequirementIds(workspaceRoot) {
203
- const { glob } = await import('glob');
204
- const { parse } = await import('@babel/parser');
205
- const traverse = await import('@babel/traverse');
206
- const t = await import('@babel/types');
207
- const fs = await import('fs');
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");
208
254
  // Find all test files using glob (same pattern as get_requirement tool)
209
- const testFiles = await glob('**/*.{test,spec}.{js,jsx,ts,tsx}', {
255
+ const testFiles = await glob("**/*.{test,spec}.{js,jsx,ts,tsx}", {
210
256
  cwd: workspaceRoot,
211
257
  absolute: true,
212
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**'],
258
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
213
259
  });
214
260
  const allRequirementIds = [];
215
261
  // For each test file, parse AST and extract requirement IDs
216
262
  for (const file of testFiles) {
217
263
  try {
218
- const code = fs.readFileSync(file, 'utf-8');
264
+ const code = fs.readFileSync(file, "utf-8");
219
265
  const ast = parse(code, {
220
- sourceType: 'module',
221
- plugins: ['typescript', 'jsx'],
266
+ sourceType: "module",
267
+ plugins: ["typescript", "jsx"],
222
268
  errorRecovery: true,
223
269
  });
224
270
  // Handle both ES module and CommonJS exports for traverse
225
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
226
273
  let traverseFunc = traverse;
227
- if (typeof traverseFunc !== 'function') {
274
+ if (typeof traverseFunc !== "function") {
228
275
  traverseFunc = traverseFunc.default;
229
276
  }
230
- if (typeof traverseFunc !== 'function') {
277
+ if (typeof traverseFunc !== "function") {
231
278
  traverseFunc = traverseFunc.default;
232
279
  }
233
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.
234
282
  CallExpression(path) {
235
283
  const callee = path.node.callee;
236
284
  // Check if this is a requirement() call
237
- if (t.isIdentifier(callee) && callee.name === 'requirement') {
285
+ if (t.isIdentifier(callee) && callee.name === "requirement") {
238
286
  const args = path.node.arguments;
239
287
  // Extract all string literal arguments (supports multi-requirement calls)
240
288
  for (const arg of args) {
@@ -246,14 +294,11 @@ export async function getReferencedRequirementIds(workspaceRoot) {
246
294
  },
247
295
  });
248
296
  }
249
- catch (error) {
250
- // Skip unparseable files
251
- continue;
252
- }
297
+ catch { }
253
298
  }
254
299
  // Extract root IDs by removing the path suffix (everything after the first dot)
255
- const rootIds = allRequirementIds.map(id => {
256
- const dotIndex = id.indexOf('.');
300
+ const rootIds = allRequirementIds.map((id) => {
301
+ const dotIndex = id.indexOf(".");
257
302
  return dotIndex > 0 ? id.substring(0, dotIndex) : id;
258
303
  });
259
304
  return new Set(rootIds);
@@ -1,5 +1,16 @@
1
- import { type RequirementNode, type Metadata } from '../schema/index.js';
2
- import type { FlattenedRequirement } from './types.js';
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
+ }
3
14
  /**
4
15
  * Parsed requirements file
5
16
  */
@@ -13,6 +24,11 @@ export interface ParsedRequirementsFile {
13
24
  * Excludes example files, test fixtures, and build artifacts.
14
25
  */
15
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[];
16
32
  /**
17
33
  * Load all requirements from the workspace
18
34
  */
@@ -54,4 +70,4 @@ export declare function filterRequirementsByKeys(fileContents: string, keys: str
54
70
  * Format a requirement tree for display
55
71
  */
56
72
  export declare function formatRequirementTree(requirements: FlattenedRequirement[]): string;
57
- //# sourceMappingURL=requirements.d.ts.map
73
+ //# sourceMappingURL=index.d.ts.map
@@ -1,30 +1,50 @@
1
- import * as path from 'path';
2
- import { glob } from 'glob';
3
- import { parseRequirementsFromFile, parseRequirementsFile, buildRequirementMarkdown, getAllRequirements, } from '../schema/index.js';
1
+ import * as path from "node:path";
2
+ import { glob, globSync } from "glob";
3
+ import { buildRequirementMarkdown, getAllRequirements, parseRequirementsFile, parseRequirementsFromFile, } from "../schema/index.js";
4
+ /**
5
+ * Glob pattern matching *.requirements.md files.
6
+ */
7
+ const REQUIREMENTS_FILE_PATTERN = "**/*.requirements.md";
8
+ /**
9
+ * Directories ignored when discovering requirements files.
10
+ * Shared between async and sync discovery so the two never drift.
11
+ */
12
+ const REQUIREMENTS_IGNORE_PATTERNS = [
13
+ "**/node_modules/**",
14
+ "**/dist/**",
15
+ "**/.git/**",
16
+ "**/build/**",
17
+ // Exclude example and test fixture directories
18
+ "**/example/**",
19
+ "**/examples/**",
20
+ "**/__tests__/**",
21
+ "**/fixtures/**",
22
+ "**/.fixtures/**",
23
+ ];
4
24
  /**
5
25
  * Find all *.requirements.md files in the workspace.
6
26
  * Supports both .requirements/ directories and colocated files.
7
27
  * Excludes example files, test fixtures, and build artifacts.
8
28
  */
9
29
  export async function findRequirementsFiles(workspaceRoot) {
10
- const pattern = '**/*.requirements.md';
11
- const matches = await glob(pattern, {
30
+ const matches = await glob(REQUIREMENTS_FILE_PATTERN, {
12
31
  cwd: workspaceRoot,
13
- ignore: [
14
- '**/node_modules/**',
15
- '**/dist/**',
16
- '**/.git/**',
17
- '**/build/**',
18
- // Exclude example and test fixture directories
19
- '**/example/**',
20
- '**/examples/**',
21
- '**/__tests__/**',
22
- '**/fixtures/**',
23
- '**/.fixtures/**',
24
- ],
32
+ ignore: REQUIREMENTS_IGNORE_PATTERNS,
25
33
  dot: true, // Include dotfiles/dotdirs like .requirements/
26
34
  });
27
- return matches.map(m => path.join(workspaceRoot, m));
35
+ return matches.map((m) => path.join(workspaceRoot, m));
36
+ }
37
+ /**
38
+ * Synchronous variant of {@link findRequirementsFiles}. Used by code paths
39
+ * that can't await (e.g. the test-harness lookup-cache builder).
40
+ */
41
+ export function findRequirementsFilesSync(workspaceRoot) {
42
+ const matches = globSync(REQUIREMENTS_FILE_PATTERN, {
43
+ cwd: workspaceRoot,
44
+ ignore: REQUIREMENTS_IGNORE_PATTERNS,
45
+ dot: true,
46
+ });
47
+ return matches.map((m) => path.join(workspaceRoot, m));
28
48
  }
29
49
  /**
30
50
  * Load all requirements from the workspace
@@ -56,9 +76,8 @@ export function flattenRequirementsFile(file, sourceFile) {
56
76
  const allReqs = getAllRequirements(file.requirements);
57
77
  for (const req of allReqs) {
58
78
  // Extract position from ID (e.g., "REQ123.0.1" -> "0.1")
59
- const idParts = req.id.split('.');
79
+ const idParts = req.id.split(".");
60
80
  const rootId = idParts[0];
61
- const position = idParts.slice(1).join('.');
62
81
  results.push({
63
82
  id: req.id,
64
83
  rootId: rootId,
@@ -76,7 +95,7 @@ export function flattenRequirementsFile(file, sourceFile) {
76
95
  */
77
96
  export function searchRequirements(requirements, query) {
78
97
  const lowerQuery = query.toLowerCase();
79
- return requirements.filter(req => {
98
+ return requirements.filter((req) => {
80
99
  // Search in ID
81
100
  if (req.id.toLowerCase().includes(lowerQuery))
82
101
  return true;
@@ -94,24 +113,24 @@ export function searchRequirements(requirements, query) {
94
113
  */
95
114
  export function getRequirementById(requirements, id) {
96
115
  // Exact match first
97
- const exact = requirements.find(r => r.id === id);
116
+ const exact = requirements.find((r) => r.id === id);
98
117
  if (exact)
99
118
  return exact;
100
119
  // Try partial match (just the root ID)
101
- return requirements.find(r => r.rootId === id || r.id.startsWith(id + '.'));
120
+ return requirements.find((r) => r.rootId === id || r.id.startsWith(`${id}.`));
102
121
  }
103
122
  /**
104
123
  * Get a requirement and all its children
105
124
  */
106
125
  export function getRequirementTree(requirements, rootId) {
107
- return requirements.filter(r => r.rootId === rootId || r.id === rootId);
126
+ return requirements.filter((r) => r.rootId === rootId || r.id === rootId);
108
127
  }
109
128
  /**
110
129
  * Format a requirement for display
111
130
  */
112
131
  export function formatRequirement(req) {
113
- const indent = ' '.repeat(req.path.length);
114
- const label = req.label ? ` (${req.label})` : '';
132
+ const indent = " ".repeat(req.path.length);
133
+ const label = req.label ? ` (${req.label})` : "";
115
134
  return `${indent}${req.id}${label}: ${req.content}`;
116
135
  }
117
136
  /**
@@ -121,11 +140,11 @@ export function formatRequirement(req) {
121
140
  */
122
141
  export function filterRequirementsByKeys(fileContents, keys) {
123
142
  const { requirements } = parseRequirementsFile(fileContents);
124
- const upperKeys = keys.map(k => k.toUpperCase());
125
- const filtered = requirements.filter(r => upperKeys.includes(r.id.toUpperCase()));
126
- const foundKeys = filtered.map(r => r.id);
127
- const missingKeys = upperKeys.filter(k => !foundKeys.map(f => f.toUpperCase()).includes(k));
128
- const filteredContent = filtered.map(buildRequirementMarkdown).join('\n');
143
+ const upperKeys = keys.map((k) => k.toUpperCase());
144
+ const filtered = requirements.filter((r) => upperKeys.includes(r.id.toUpperCase()));
145
+ const foundKeys = filtered.map((r) => r.id);
146
+ const missingKeys = upperKeys.filter((k) => !foundKeys.map((f) => f.toUpperCase()).includes(k));
147
+ const filteredContent = filtered.map(buildRequirementMarkdown).join("\n");
129
148
  return { filteredContent, foundKeys, missingKeys };
130
149
  }
131
150
  /**
@@ -137,8 +156,8 @@ export function formatRequirementTree(requirements) {
137
156
  // Compare paths element by element for proper tree order
138
157
  const maxLen = Math.max(a.path.length, b.path.length);
139
158
  for (let i = 0; i < maxLen; i++) {
140
- const aVal = i < a.path.length ? parseInt(a.path[i]) : -1;
141
- const bVal = i < b.path.length ? parseInt(b.path[i]) : -1;
159
+ const aVal = i < a.path.length ? parseInt(a.path[i], 10) : -1;
160
+ const bVal = i < b.path.length ? parseInt(b.path[i], 10) : -1;
142
161
  // If one path is a prefix of the other, the shorter one comes first
143
162
  if (aVal === -1)
144
163
  return -1;
@@ -150,6 +169,6 @@ export function formatRequirementTree(requirements) {
150
169
  }
151
170
  return 0;
152
171
  });
153
- return sorted.map(formatRequirement).join('\n');
172
+ return sorted.map(formatRequirement).join("\n");
154
173
  }
155
- //# sourceMappingURL=requirements.js.map
174
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Style-guide template generator. Produces the markdown document that both
3
+ * the MCP `create_requirement_document` tool and the CLI
4
+ * `dotreq create-requirement-document` verb return.
5
+ *
6
+ * The output is a full ready-to-paste-into-a-PR markdown document that
7
+ * describes the requirements format, embeds a worked example, and includes
8
+ * style principles for writing requirements and tests. Discovered label /
9
+ * key patterns from the calling workspace and (optional) project-owner-
10
+ * supplied custom guidance are interpolated into the body.
11
+ *
12
+ * If the project has a `.requirements/STYLE.md`, callers can pass its
13
+ * contents via `localStyleGuide` to use that body in place of the bundled
14
+ * defaults. See {@link readLocalStyleGuide} for the lookup helper.
15
+ */
16
+ import type { FlattenedRequirement } from "./index.js";
17
+ /**
18
+ * Conventional location of a project's STYLE.md, relative to the workspace
19
+ * root. Exported so callers (init, push/pull, tests) can reference one place.
20
+ */
21
+ export declare const STYLE_MD_PATH = ".requirements/STYLE.md";
22
+ export interface GenerateStyleGuideParams {
23
+ /** Flattened view of the workspace's existing requirements, used to
24
+ * surface label patterns and key-prefix patterns in the output. Pass
25
+ * an empty array when no requirements exist yet. */
26
+ requirements: FlattenedRequirement[];
27
+ /** Project-owner-supplied custom guidance (currently sourced from the
28
+ * cloud project's `requirementsStyleContext` field). Pass null/undefined
29
+ * to omit. */
30
+ customStyleGuidance?: string | null;
31
+ /** Suggested target path for the new file. Used only in the preamble
32
+ * and "Next Steps" section. Defaults to a generic example path. */
33
+ filePath?: string;
34
+ /** Project-local STYLE.md contents. When provided and non-empty, this
35
+ * body replaces the bundled default; callers can fetch it via
36
+ * {@link readLocalStyleGuide}. */
37
+ localStyleGuide?: string | null;
38
+ }
39
+ /**
40
+ * Read `.requirements/STYLE.md` if the workspace has one. Returns the file
41
+ * contents on success, `null` if the file is absent. Empty / whitespace-only
42
+ * files are treated as absent so the bundled defaults still apply.
43
+ */
44
+ export declare function readLocalStyleGuide(workspaceRoot: string): string | null;
45
+ /**
46
+ * Build the body of the bundled default style guide. This is the content
47
+ * that lives inside the "# Requirements File Template" preamble — the
48
+ * format syntax, key/label conventions, style principles, and example
49
+ * requirements — with discovered patterns and custom guidance interpolated.
50
+ *
51
+ * Exposed separately from {@link generateStyleGuide} so callers can:
52
+ * - scaffold `.requirements/STYLE.md` with the bundled defaults at init time
53
+ * - swap the body wholesale (via `localStyleGuide`) without losing the
54
+ * wrapping preamble + "Next Steps" footer
55
+ */
56
+ export declare function generateStyleGuideBody(params: Omit<GenerateStyleGuideParams, "filePath" | "localStyleGuide">): string;
57
+ /**
58
+ * Build a complete style-guide document for the calling workspace.
59
+ * Returns a single markdown string suitable for printing to stdout or
60
+ * wrapping in an MCP text response.
61
+ *
62
+ * When `localStyleGuide` is provided and non-empty, that content replaces
63
+ * the bundled default body. The preamble and "Next Steps" footer are
64
+ * always applied.
65
+ */
66
+ export declare function generateStyleGuide(params: GenerateStyleGuideParams): string;
67
+ //# sourceMappingURL=style-guide.d.ts.map