@popoverai/dotrequirements 0.24.0 → 0.24.2

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 (60) hide show
  1. package/README.md +4 -3
  2. package/dist/cli.js +0 -0
  3. package/dist/codebase-to-spec/claude.d.ts +1 -0
  4. package/dist/codebase-to-spec/claude.js +9 -0
  5. package/dist/codebase-to-spec/pack.d.ts +16 -0
  6. package/dist/codebase-to-spec/pack.js +17 -3
  7. package/dist/codebase-to-spec/present.d.ts +8 -1
  8. package/dist/codebase-to-spec/present.js +7 -4
  9. package/dist/codebase-to-spec/progress.d.ts +6 -0
  10. package/dist/codebase-to-spec/progress.js +34 -0
  11. package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +1 -1
  12. package/dist/codebase-to-spec/prompts/outline-reviewer.js +3 -1
  13. package/dist/codebase-to-spec/prompts/planner-initial.d.ts +1 -1
  14. package/dist/codebase-to-spec/prompts/planner-initial.js +4 -0
  15. package/dist/codebase-to-spec/prompts/planner-revise.d.ts +1 -1
  16. package/dist/codebase-to-spec/prompts/planner-revise.js +2 -2
  17. package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +1 -1
  18. package/dist/codebase-to-spec/prompts/spec-reviewer.js +6 -1
  19. package/dist/codebase-to-spec/prompts/specifier.d.ts +1 -1
  20. package/dist/codebase-to-spec/prompts/specifier.js +6 -4
  21. package/dist/codebase-to-spec/prompts/style-check.d.ts +10 -2
  22. package/dist/codebase-to-spec/prompts/style-check.js +76 -46
  23. package/dist/codebase-to-spec/schemas.d.ts +85 -1
  24. package/dist/codebase-to-spec/schemas.js +25 -1
  25. package/dist/codebase-to-spec/specifier.js +6 -0
  26. package/dist/commands/codebase-to-spec/index.js +13 -0
  27. package/dist/commands/codebase-to-spec/pack.d.ts +6 -0
  28. package/dist/commands/codebase-to-spec/pack.js +1 -0
  29. package/dist/commands/codebase-to-spec/present.d.ts +5 -0
  30. package/dist/commands/codebase-to-spec/present.js +6 -1
  31. package/dist/commands/codebase-to-spec/run.js +1 -0
  32. package/package.json +5 -6
  33. package/dist/codebase-to-spec/prompts/planner-apply.d.ts +0 -12
  34. package/dist/codebase-to-spec/prompts/planner-apply.js +0 -32
  35. package/dist/commands/browsertest.d.ts +0 -6
  36. package/dist/commands/browsertest.js +0 -212
  37. package/dist/commands/login.d.ts +0 -12
  38. package/dist/commands/login.js +0 -117
  39. package/dist/commands/logout.d.ts +0 -5
  40. package/dist/commands/logout.js +0 -17
  41. package/dist/commands/mcp-setup.d.ts +0 -5
  42. package/dist/commands/mcp-setup.js +0 -441
  43. package/dist/commands/test.d.ts +0 -6
  44. package/dist/commands/test.js +0 -72
  45. package/dist/mcp/grep.d.ts +0 -24
  46. package/dist/mcp/grep.js +0 -306
  47. package/dist/mcp/handlers/coverage.d.ts +0 -44
  48. package/dist/mcp/handlers/coverage.js +0 -103
  49. package/dist/mcp/requirements.d.ts +0 -57
  50. package/dist/mcp/requirements.js +0 -155
  51. package/dist/mcp/testCodeExtractor.d.ts +0 -22
  52. package/dist/mcp/testCodeExtractor.js +0 -150
  53. package/dist/mcp/types.d.ts +0 -27
  54. package/dist/mcp/types.js +0 -2
  55. package/dist/utils/local-project.d.ts +0 -31
  56. package/dist/utils/local-project.js +0 -33
  57. package/dist/utils/token-refresh.d.ts +0 -24
  58. package/dist/utils/token-refresh.js +0 -69
  59. package/dist/utils/token-storage.d.ts +0 -31
  60. package/dist/utils/token-storage.js +0 -57
package/dist/mcp/grep.js DELETED
@@ -1,306 +0,0 @@
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 (error) {
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 (error) {
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 (error) { }
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
@@ -1,44 +0,0 @@
1
- /**
2
- * Coverage handlers for MCP tools
3
- *
4
- * Provides cloud coverage query functionality:
5
- * - get_requirement_coverage: Coverage for a specific requirement
6
- * - get_project_coverage_summary: Project-wide coverage summary
7
- */
8
- import type { HandlerContext, ToolResponse } from "./types.js";
9
- /**
10
- * Arguments for get_requirement_coverage tool
11
- */
12
- export interface GetRequirementCoverageArgs {
13
- requirementKey: string;
14
- projectId?: string;
15
- }
16
- /**
17
- * Arguments for get_project_coverage_summary tool
18
- */
19
- export interface GetProjectCoverageSummaryArgs {
20
- branch?: string;
21
- sinceTimestamp?: number;
22
- projectId?: string;
23
- }
24
- /**
25
- * Handler for get_requirement_coverage tool
26
- *
27
- * Requirements covered:
28
- * - MCP-COVERAGE-1.0: When credentials are valid, the response shows when the requirement was last tested
29
- * - MCP-COVERAGE-1.1: The response includes the most recent branch and test file where coverage was recorded
30
- * - MCP-COVERAGE-1.2: When the requirement has no coverage records, the response indicates this
31
- * - MCP-COVERAGE-1.3: When credentials are missing or invalid, an error explains how to authenticate
32
- */
33
- export declare function handleGetRequirementCoverage(args: GetRequirementCoverageArgs, context: HandlerContext): Promise<ToolResponse>;
34
- /**
35
- * Handler for get_project_coverage_summary tool
36
- *
37
- * Requirements covered:
38
- * - MCP-COVERAGE-2.0: The response summarizes which requirements have been tested and which have not
39
- * - MCP-COVERAGE-2.1: When a branch filter is provided, coverage results include only records from that branch
40
- * - MCP-COVERAGE-2.2: When sinceTimestamp is provided, only coverage recorded after that time is included
41
- * - MCP-COVERAGE-2.3: When credentials are missing or invalid, an error explains how to authenticate
42
- */
43
- export declare function handleGetProjectCoverageSummary(args: GetProjectCoverageSummaryArgs, context: HandlerContext): Promise<ToolResponse>;
44
- //# sourceMappingURL=coverage.d.ts.map
@@ -1,103 +0,0 @@
1
- /**
2
- * Coverage handlers for MCP tools
3
- *
4
- * Provides cloud coverage query functionality:
5
- * - get_requirement_coverage: Coverage for a specific requirement
6
- * - get_project_coverage_summary: Project-wide coverage summary
7
- */
8
- import { CONVEX_URL, getProjectCoverage as queryProjectCoverage, getRequirementCoverage as queryRequirementCoverage, } from "../convexClient.js";
9
- import { errorResponse, textResponse } from "./types.js";
10
- /**
11
- * Handler for get_requirement_coverage tool
12
- *
13
- * Requirements covered:
14
- * - MCP-COVERAGE-1.0: When credentials are valid, the response shows when the requirement was last tested
15
- * - MCP-COVERAGE-1.1: The response includes the most recent branch and test file where coverage was recorded
16
- * - MCP-COVERAGE-1.2: When the requirement has no coverage records, the response indicates this
17
- * - MCP-COVERAGE-1.3: When credentials are missing or invalid, an error explains how to authenticate
18
- */
19
- export async function handleGetRequirementCoverage(args, context) {
20
- const { requirementKey, projectId } = args;
21
- // MCP-COVERAGE-1.3: Get project credentials (throws if not configured)
22
- let project;
23
- try {
24
- project = await context.getProjectFromDiscovery(projectId);
25
- }
26
- catch (error) {
27
- return errorResponse(`Coverage query requires project credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
28
- }
29
- // Query cloud for coverage
30
- const coverage = await queryRequirementCoverage(requirementKey, project.projectId, project.projectSecret, CONVEX_URL);
31
- // MCP-COVERAGE-1.2: No coverage records
32
- if (!coverage.lastTestedAt) {
33
- return textResponse(`**${requirementKey}** has not been tested yet.`);
34
- }
35
- // MCP-COVERAGE-1.0: Show when requirement was last tested
36
- const lastTested = new Date(coverage.lastTestedAt);
37
- // MCP-COVERAGE-1.1: Include branch and test file
38
- const testLocation = coverage.testFile
39
- ? `\n**Test Location:** \`${coverage.testFile}${coverage.testLine ? `:${coverage.testLine}` : ""}\``
40
- : "";
41
- let branchInfo = `\n**Branch:** ${coverage.branch}`;
42
- if (coverage.allBranches.length > 1) {
43
- const otherBranches = coverage.allBranches
44
- .filter((b) => b.branch !== coverage.branch)
45
- .map((b) => `- ${b.branch}: ${new Date(b.lastTestedAt).toISOString()}`)
46
- .join("\n");
47
- branchInfo += `\n\n**Also tested on:**\n${otherBranches}`;
48
- }
49
- return textResponse(`# Coverage for ${requirementKey}\n\n**Last Tested:** ${lastTested.toISOString()}${branchInfo}${testLocation}`);
50
- }
51
- /**
52
- * Handler for get_project_coverage_summary tool
53
- *
54
- * Requirements covered:
55
- * - MCP-COVERAGE-2.0: The response summarizes which requirements have been tested and which have not
56
- * - MCP-COVERAGE-2.1: When a branch filter is provided, coverage results include only records from that branch
57
- * - MCP-COVERAGE-2.2: When sinceTimestamp is provided, only coverage recorded after that time is included
58
- * - MCP-COVERAGE-2.3: When credentials are missing or invalid, an error explains how to authenticate
59
- */
60
- export async function handleGetProjectCoverageSummary(args, context) {
61
- const { branch, sinceTimestamp, projectId } = args;
62
- // MCP-COVERAGE-2.3: Get project credentials (throws if not configured)
63
- let project;
64
- try {
65
- project = await context.getProjectFromDiscovery(projectId);
66
- }
67
- catch (error) {
68
- return errorResponse(`Coverage query requires project credentials. Run \`dotrequirements link\` to connect to cloud.\n\nError: ${error instanceof Error ? error.message : String(error)}`);
69
- }
70
- // Query cloud for project coverage with optional filters
71
- const coverage = await queryProjectCoverage(project.projectId, project.projectSecret, CONVEX_URL, { branch, sinceTimestamp });
72
- // MCP-COVERAGE-2.0: Calculate and display coverage summary
73
- const total = coverage.tested.length + coverage.untested.length;
74
- const percentage = total > 0 ? ((coverage.tested.length / total) * 100).toFixed(1) : "0.0";
75
- // MCP-COVERAGE-2.1 & MCP-COVERAGE-2.2: Show filter info if provided
76
- let filterInfo = "";
77
- if (branch) {
78
- filterInfo += `\n**Branch:** ${branch}`;
79
- }
80
- if (sinceTimestamp) {
81
- filterInfo += `\n**Since:** ${new Date(sinceTimestamp).toISOString()}`;
82
- }
83
- let testedSection = "";
84
- if (coverage.tested.length > 0) {
85
- const testedList = coverage.tested
86
- .map((t) => {
87
- const date = new Date(t.lastTestedAt).toISOString();
88
- const location = t.testFile
89
- ? ` (\`${t.testFile}${t.testLine ? `:${t.testLine}` : ""}\`)`
90
- : "";
91
- return `- **${t.requirementKey}** [${t.branch}]: ${date}${location}`;
92
- })
93
- .join("\n");
94
- testedSection = `\n\n## Tested Requirements (${coverage.tested.length})\n\n${testedList}`;
95
- }
96
- let untestedSection = "";
97
- if (coverage.untested.length > 0) {
98
- const untestedList = coverage.untested.map((key) => `- ${key}`).join("\n");
99
- untestedSection = `\n\n## Untested Requirements (${coverage.untested.length})\n\n${untestedList}`;
100
- }
101
- return textResponse(`# Project Coverage Summary\n\n**Total Requirements:** ${total}\n**Coverage:** ${percentage}%${filterInfo}${testedSection}${untestedSection}`);
102
- }
103
- //# sourceMappingURL=coverage.js.map
@@ -1,57 +0,0 @@
1
- import { type Metadata, type RequirementNode } from "../schema/index.js";
2
- import type { FlattenedRequirement } from "./types.js";
3
- /**
4
- * Parsed requirements file
5
- */
6
- export interface ParsedRequirementsFile {
7
- metadata: Metadata;
8
- requirements: RequirementNode[];
9
- }
10
- /**
11
- * Find all *.requirements.md files in the workspace.
12
- * Supports both .requirements/ directories and colocated files.
13
- * Excludes example files, test fixtures, and build artifacts.
14
- */
15
- export declare function findRequirementsFiles(workspaceRoot: string): Promise<string[]>;
16
- /**
17
- * Load all requirements from the workspace
18
- */
19
- export declare function loadAllRequirements(workspaceRoot: string): Promise<{
20
- files: ParsedRequirementsFile[];
21
- flattened: FlattenedRequirement[];
22
- }>;
23
- /**
24
- * Flatten a requirements file into searchable requirements
25
- */
26
- export declare function flattenRequirementsFile(file: ParsedRequirementsFile, sourceFile: string): FlattenedRequirement[];
27
- /**
28
- * Search requirements by text query
29
- */
30
- export declare function searchRequirements(requirements: FlattenedRequirement[], query: string): FlattenedRequirement[];
31
- /**
32
- * Get a specific requirement by ID (supports partial matching)
33
- */
34
- export declare function getRequirementById(requirements: FlattenedRequirement[], id: string): FlattenedRequirement | undefined;
35
- /**
36
- * Get a requirement and all its children
37
- */
38
- export declare function getRequirementTree(requirements: FlattenedRequirement[], rootId: string): FlattenedRequirement[];
39
- /**
40
- * Format a requirement for display
41
- */
42
- export declare function formatRequirement(req: FlattenedRequirement): string;
43
- /**
44
- * Filter a requirements file's content to only include specified requirement keys.
45
- * Parses the file content, filters top-level requirements by key, and rebuilds as
46
- * markdown requirement blocks (without frontmatter).
47
- */
48
- export declare function filterRequirementsByKeys(fileContents: string, keys: string[]): {
49
- filteredContent: string;
50
- foundKeys: string[];
51
- missingKeys: string[];
52
- };
53
- /**
54
- * Format a requirement tree for display
55
- */
56
- export declare function formatRequirementTree(requirements: FlattenedRequirement[]): string;
57
- //# sourceMappingURL=requirements.d.ts.map
@@ -1,155 +0,0 @@
1
- import * as path from "node:path";
2
- import { glob } from "glob";
3
- import { buildRequirementMarkdown, getAllRequirements, parseRequirementsFile, parseRequirementsFromFile, } from "../schema/index.js";
4
- /**
5
- * Find all *.requirements.md files in the workspace.
6
- * Supports both .requirements/ directories and colocated files.
7
- * Excludes example files, test fixtures, and build artifacts.
8
- */
9
- export async function findRequirementsFiles(workspaceRoot) {
10
- const pattern = "**/*.requirements.md";
11
- const matches = await glob(pattern, {
12
- 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
- ],
25
- dot: true, // Include dotfiles/dotdirs like .requirements/
26
- });
27
- return matches.map((m) => path.join(workspaceRoot, m));
28
- }
29
- /**
30
- * Load all requirements from the workspace
31
- */
32
- export async function loadAllRequirements(workspaceRoot) {
33
- const filePaths = await findRequirementsFiles(workspaceRoot);
34
- const allFiles = [];
35
- const allFlattened = [];
36
- for (const filePath of filePaths) {
37
- try {
38
- const parsed = parseRequirementsFromFile(filePath);
39
- allFiles.push(parsed);
40
- const flattened = flattenRequirementsFile(parsed, filePath);
41
- allFlattened.push(...flattened);
42
- }
43
- catch (error) {
44
- // Skip invalid files
45
- console.error(`Failed to parse ${filePath}:`, error);
46
- }
47
- }
48
- return { files: allFiles, flattened: allFlattened };
49
- }
50
- /**
51
- * Flatten a requirements file into searchable requirements
52
- */
53
- export function flattenRequirementsFile(file, sourceFile) {
54
- const results = [];
55
- // Use shared utility to get all requirements (flattened)
56
- const allReqs = getAllRequirements(file.requirements);
57
- for (const req of allReqs) {
58
- // Extract position from ID (e.g., "REQ123.0.1" -> "0.1")
59
- const idParts = req.id.split(".");
60
- const rootId = idParts[0];
61
- const position = idParts.slice(1).join(".");
62
- results.push({
63
- id: req.id,
64
- rootId: rootId,
65
- label: req.label,
66
- content: req.content,
67
- path: idParts.slice(1), // ["0", "1"] for "REQ123.0.1"
68
- sourceFile: sourceFile,
69
- documentTitle: file.metadata.document?.title || rootId,
70
- });
71
- }
72
- return results;
73
- }
74
- /**
75
- * Search requirements by text query
76
- */
77
- export function searchRequirements(requirements, query) {
78
- const lowerQuery = query.toLowerCase();
79
- return requirements.filter((req) => {
80
- // Search in ID
81
- if (req.id.toLowerCase().includes(lowerQuery))
82
- return true;
83
- // Search in content
84
- if (req.content.toLowerCase().includes(lowerQuery))
85
- return true;
86
- // Search in label
87
- if (req.label.toLowerCase().includes(lowerQuery))
88
- return true;
89
- return false;
90
- });
91
- }
92
- /**
93
- * Get a specific requirement by ID (supports partial matching)
94
- */
95
- export function getRequirementById(requirements, id) {
96
- // Exact match first
97
- const exact = requirements.find((r) => r.id === id);
98
- if (exact)
99
- return exact;
100
- // Try partial match (just the root ID)
101
- return requirements.find((r) => r.rootId === id || r.id.startsWith(`${id}.`));
102
- }
103
- /**
104
- * Get a requirement and all its children
105
- */
106
- export function getRequirementTree(requirements, rootId) {
107
- return requirements.filter((r) => r.rootId === rootId || r.id === rootId);
108
- }
109
- /**
110
- * Format a requirement for display
111
- */
112
- export function formatRequirement(req) {
113
- const indent = " ".repeat(req.path.length);
114
- const label = req.label ? ` (${req.label})` : "";
115
- return `${indent}${req.id}${label}: ${req.content}`;
116
- }
117
- /**
118
- * Filter a requirements file's content to only include specified requirement keys.
119
- * Parses the file content, filters top-level requirements by key, and rebuilds as
120
- * markdown requirement blocks (without frontmatter).
121
- */
122
- export function filterRequirementsByKeys(fileContents, keys) {
123
- 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");
129
- return { filteredContent, foundKeys, missingKeys };
130
- }
131
- /**
132
- * Format a requirement tree for display
133
- */
134
- export function formatRequirementTree(requirements) {
135
- // Sort by path to maintain hierarchy - children appear immediately after parent
136
- const sorted = [...requirements].sort((a, b) => {
137
- // Compare paths element by element for proper tree order
138
- const maxLen = Math.max(a.path.length, b.path.length);
139
- for (let i = 0; i < maxLen; i++) {
140
- const aVal = i < a.path.length ? parseInt(a.path[i], 10) : -1;
141
- const bVal = i < b.path.length ? parseInt(b.path[i], 10) : -1;
142
- // If one path is a prefix of the other, the shorter one comes first
143
- if (aVal === -1)
144
- return -1;
145
- if (bVal === -1)
146
- return 1;
147
- if (aVal !== bVal) {
148
- return aVal - bVal;
149
- }
150
- }
151
- return 0;
152
- });
153
- return sorted.map(formatRequirement).join("\n");
154
- }
155
- //# sourceMappingURL=requirements.js.map
@@ -1,22 +0,0 @@
1
- /**
2
- * Extract test code context using @babel/parser AST traversal.
3
- *
4
- * Finds requirement() call references and extracts the enclosing
5
- * meaningful code block (function, describe, test, etc.)
6
- */
7
- export interface TestCodeReference {
8
- file: string;
9
- startLine: number;
10
- endLine: number;
11
- code: string;
12
- requirementId: string;
13
- }
14
- /**
15
- * Find all test code blocks that reference a specific requirement
16
- */
17
- export declare function findTestCodeForRequirement(filePath: string, requirementId: string): TestCodeReference[];
18
- /**
19
- * Find all files that reference a requirement
20
- */
21
- export declare function findFilesWithRequirement(files: string[], requirementId: string): string[];
22
- //# sourceMappingURL=testCodeExtractor.d.ts.map