@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.
- package/README.md +169 -22
- package/dist/cli.js +121 -60
- package/dist/codebase-to-spec/budget.d.ts +53 -0
- package/dist/codebase-to-spec/budget.js +80 -0
- package/dist/codebase-to-spec/cache.d.ts +49 -0
- package/dist/codebase-to-spec/cache.js +54 -0
- package/dist/codebase-to-spec/claude.d.ts +69 -0
- package/dist/codebase-to-spec/claude.js +126 -0
- package/dist/codebase-to-spec/compose.d.ts +49 -0
- package/dist/codebase-to-spec/compose.js +124 -0
- package/dist/codebase-to-spec/edit-loop.d.ts +54 -0
- package/dist/codebase-to-spec/edit-loop.js +195 -0
- package/dist/codebase-to-spec/editor.d.ts +54 -0
- package/dist/codebase-to-spec/editor.js +74 -0
- package/dist/codebase-to-spec/exit-codes.d.ts +40 -0
- package/dist/codebase-to-spec/exit-codes.js +58 -0
- package/dist/codebase-to-spec/fan-out.d.ts +63 -0
- package/dist/codebase-to-spec/fan-out.js +215 -0
- package/dist/codebase-to-spec/interactive.d.ts +30 -0
- package/dist/codebase-to-spec/interactive.js +48 -0
- package/dist/codebase-to-spec/outline-review-loop.d.ts +51 -0
- package/dist/codebase-to-spec/outline-review-loop.js +187 -0
- package/dist/codebase-to-spec/pack.d.ts +51 -0
- package/dist/codebase-to-spec/pack.js +127 -0
- package/dist/codebase-to-spec/planner.d.ts +41 -0
- package/dist/codebase-to-spec/planner.js +76 -0
- package/dist/codebase-to-spec/present.d.ts +94 -0
- package/dist/codebase-to-spec/present.js +288 -0
- package/dist/codebase-to-spec/progress.d.ts +33 -0
- package/dist/codebase-to-spec/progress.js +28 -0
- package/dist/codebase-to-spec/prompts/editor.d.ts +13 -0
- package/dist/codebase-to-spec/prompts/editor.js +57 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.js +87 -0
- package/dist/codebase-to-spec/prompts/planner-initial.d.ts +11 -0
- package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
- package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
- package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
- package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
- package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
- package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/specifier.js +100 -0
- package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/style-check.js +78 -0
- package/dist/codebase-to-spec/schemas.d.ts +257 -0
- package/dist/codebase-to-spec/schemas.js +183 -0
- package/dist/codebase-to-spec/skill-install.d.ts +57 -0
- package/dist/codebase-to-spec/skill-install.js +79 -0
- package/dist/codebase-to-spec/slice.d.ts +49 -0
- package/dist/codebase-to-spec/slice.js +111 -0
- package/dist/codebase-to-spec/specifier.d.ts +60 -0
- package/dist/codebase-to-spec/specifier.js +79 -0
- package/dist/codebase-to-spec/style-check.d.ts +29 -0
- package/dist/codebase-to-spec/style-check.js +33 -0
- package/dist/codebase-to-spec/summary.d.ts +51 -0
- package/dist/codebase-to-spec/summary.js +183 -0
- package/dist/codebase-to-spec/validate.d.ts +46 -0
- package/dist/codebase-to-spec/validate.js +130 -0
- package/dist/commands/acceptance-test.d.ts +6 -0
- package/dist/commands/{browsertest.js → acceptance-test.js} +36 -29
- package/dist/commands/ai-setup.d.ts +5 -0
- package/dist/commands/ai-setup.js +441 -0
- package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
- package/dist/commands/codebase-to-spec/compose.js +57 -0
- package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
- package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
- package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
- package/dist/commands/codebase-to-spec/fan-out.js +77 -0
- package/dist/commands/codebase-to-spec/index.d.ts +9 -0
- package/dist/commands/codebase-to-spec/index.js +135 -0
- package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
- package/dist/commands/codebase-to-spec/pack.js +76 -0
- package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
- package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
- package/dist/commands/codebase-to-spec/present.d.ts +21 -0
- package/dist/commands/codebase-to-spec/present.js +92 -0
- package/dist/commands/codebase-to-spec/run.d.ts +20 -0
- package/dist/commands/codebase-to-spec/run.js +85 -0
- package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
- package/dist/commands/codebase-to-spec/skill-install.js +51 -0
- package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
- package/dist/commands/codebase-to-spec/specify-area.js +82 -0
- package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
- package/dist/commands/codebase-to-spec/style-check.js +42 -0
- package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
- package/dist/commands/codebase-to-spec/validate.js +38 -0
- package/dist/commands/create-requirement-document.d.ts +2 -0
- package/dist/commands/create-requirement-document.js +41 -0
- package/dist/commands/finalize.js +7 -7
- package/dist/commands/get.d.ts +2 -0
- package/dist/commands/get.js +55 -0
- package/dist/commands/init.js +132 -117
- package/dist/commands/link.js +27 -27
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.js +43 -0
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/prepare.js +4 -4
- package/dist/commands/pull.js +116 -121
- package/dist/commands/push.js +106 -112
- package/dist/commands/report.d.ts +6 -2
- package/dist/commands/report.js +177 -122
- package/dist/commands/requirements-for.d.ts +2 -0
- package/dist/commands/requirements-for.js +29 -0
- package/dist/commands/review-test.d.ts +2 -0
- package/dist/commands/review-test.js +75 -0
- package/dist/commands/search.d.ts +6 -0
- package/dist/commands/search.js +39 -0
- package/dist/commands/style-check.d.ts +7 -0
- package/dist/commands/style-check.js +75 -0
- package/dist/commands/tests-for.d.ts +2 -0
- package/dist/commands/tests-for.js +80 -0
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.js +72 -0
- package/dist/config.js +1 -1
- package/dist/convex.d.ts +34 -22
- package/dist/convex.js +38 -22
- package/dist/harness/cache.d.ts +1 -5
- package/dist/harness/cache.js +49 -59
- package/dist/harness/convexReporting.d.ts +1 -1
- package/dist/harness/convexReporting.js +9 -7
- package/dist/harness/coverageCache.js +3 -3
- package/dist/harness/finalize.js +59 -46
- package/dist/harness/index.d.ts +6 -7
- package/dist/harness/index.js +9 -10
- package/dist/harness/prepare.js +6 -5
- package/dist/harness/requirementsLoader.d.ts +2 -2
- package/dist/harness/requirementsLoader.js +13 -35
- package/dist/harness/tracking.js +18 -18
- package/dist/harness/types.d.ts +1 -1
- package/dist/mcp/convexClient.d.ts +0 -39
- package/dist/mcp/convexClient.js +2 -107
- package/dist/mcp/handlers/authoring.d.ts +1 -1
- package/dist/mcp/handlers/authoring.js +30 -234
- package/dist/mcp/handlers/debug.d.ts +2 -3
- package/dist/mcp/handlers/debug.js +10 -10
- package/dist/mcp/handlers/get.d.ts +1 -1
- package/dist/mcp/handlers/get.js +11 -10
- package/dist/mcp/handlers/index.d.ts +20 -20
- package/dist/mcp/handlers/index.js +10 -10
- package/dist/mcp/handlers/list.d.ts +4 -33
- package/dist/mcp/handlers/list.js +16 -38
- package/dist/mcp/handlers/push.d.ts +1 -1
- package/dist/mcp/handlers/push.js +28 -18
- package/dist/mcp/handlers/report.d.ts +16 -0
- package/dist/mcp/handlers/report.js +134 -0
- package/dist/mcp/handlers/review.d.ts +1 -1
- package/dist/mcp/handlers/review.js +40 -59
- package/dist/mcp/handlers/search.d.ts +1 -1
- package/dist/mcp/handlers/search.js +7 -9
- package/dist/mcp/handlers/test-mapping.d.ts +1 -1
- package/dist/mcp/handlers/test-mapping.js +14 -14
- package/dist/mcp/handlers/types.d.ts +3 -3
- package/dist/mcp/handlers/types.js +2 -2
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +147 -167
- package/dist/push/core.d.ts +2 -2
- package/dist/push/core.js +20 -20
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +2 -2
- package/dist/requirements/cloud-ai.d.ts +57 -0
- package/dist/requirements/cloud-ai.js +104 -0
- package/dist/requirements/cloud-coverage.d.ts +41 -0
- package/dist/requirements/cloud-coverage.js +60 -0
- package/dist/requirements/coverage.d.ts +45 -0
- package/dist/requirements/coverage.js +114 -0
- package/dist/{mcp → requirements}/grep.d.ts +10 -1
- package/dist/{mcp → requirements}/grep.js +89 -44
- package/dist/{mcp/requirements.d.ts → requirements/index.d.ts} +19 -3
- package/dist/{mcp/requirements.js → requirements/index.js} +54 -35
- package/dist/requirements/style-guide.d.ts +67 -0
- package/dist/requirements/style-guide.js +299 -0
- package/dist/{mcp → requirements}/testCodeExtractor.js +24 -26
- package/dist/schema/browser.d.ts +8 -8
- package/dist/schema/browser.js +13 -15
- package/dist/schema/builder.d.ts +1 -1
- package/dist/schema/builder.js +13 -44
- package/dist/schema/conversions.d.ts +2 -2
- package/dist/schema/conversions.js +11 -11
- package/dist/schema/index.d.ts +9 -9
- package/dist/schema/index.js +15 -15
- package/dist/schema/parser-core.d.ts +1 -1
- package/dist/schema/parser-core.js +23 -22
- package/dist/schema/parser.d.ts +3 -3
- package/dist/schema/parser.js +27 -31
- package/dist/schema/resolver.d.ts +1 -1
- package/dist/schema/resolver.js +9 -9
- package/dist/schema/scenario.d.ts +1 -1
- package/dist/schema/scenario.js +1 -1
- package/dist/schema/schemas.d.ts +3 -3
- package/dist/schema/schemas.js +41 -28
- package/dist/schema/test-schema.js +27 -27
- package/dist/templates/context-file-section.md +3 -2
- package/dist/templates/example-requirements.js +1 -1
- package/dist/templates/example-requirements.ts +3 -1
- package/dist/templates/requirements-readme.js +1 -1
- package/dist/templates/requirements-readme.ts +1 -1
- package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
- package/dist/utils/brand.js +3 -3
- package/dist/utils/browser-launch.js +4 -4
- package/dist/utils/context-file.d.ts +1 -1
- package/dist/utils/context-file.js +26 -26
- package/dist/utils/env.js +7 -7
- package/dist/utils/gitignore.js +7 -7
- package/dist/utils/oauth-callback-server.d.ts +1 -1
- package/dist/utils/oauth-callback-server.js +27 -25
- package/dist/utils/oauth-flow.js +32 -29
- package/dist/utils/project-discovery.d.ts +3 -3
- package/dist/utils/project-discovery.js +18 -17
- package/dist/utils/project-name.js +8 -8
- package/dist/utils/project-selector.d.ts +1 -1
- package/dist/utils/project-selector.js +24 -21
- package/dist/utils/project-settings.d.ts +1 -1
- package/dist/utils/project-settings.js +24 -22
- package/dist/utils/templates.js +6 -6
- package/package.json +3 -2
- package/dist/commands/browsertest.d.ts +0 -6
- package/dist/commands/login.d.ts +0 -12
- package/dist/commands/login.js +0 -117
- package/dist/commands/logout.d.ts +0 -5
- package/dist/commands/logout.js +0 -17
- package/dist/commands/mcp-setup.d.ts +0 -5
- package/dist/commands/mcp-setup.js +0 -431
- package/dist/commands/test.d.ts +0 -6
- package/dist/commands/test.js +0 -78
- package/dist/mcp/handlers/coverage.d.ts +0 -44
- package/dist/mcp/handlers/coverage.js +0 -105
- package/dist/mcp/types.d.ts +0 -27
- package/dist/mcp/types.js +0 -2
- package/dist/utils/local-project.d.ts +0 -31
- package/dist/utils/local-project.js +0 -33
- package/dist/utils/token-refresh.d.ts +0 -24
- package/dist/utils/token-refresh.js +0 -69
- package/dist/utils/token-storage.d.ts +0 -31
- package/dist/utils/token-storage.js +0 -57
- /package/dist/{mcp → requirements}/testCodeExtractor.d.ts +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { execFile } from
|
|
2
|
-
import { promisify } from
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
52
|
-
const traverse = await import(
|
|
53
|
-
const t = await import(
|
|
54
|
-
const fs = await import(
|
|
55
|
-
const pathModule = await import(
|
|
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,
|
|
92
|
+
const code = fs.readFileSync(resolvedPath, "utf-8");
|
|
71
93
|
const ast = parse(code, {
|
|
72
|
-
sourceType:
|
|
73
|
-
plugins: [
|
|
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 !==
|
|
101
|
+
if (typeof traverseFunc !== "function") {
|
|
79
102
|
traverseFunc = traverseFunc.default;
|
|
80
103
|
}
|
|
81
|
-
if (typeof traverseFunc !==
|
|
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 ===
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
204
|
-
const { parse } = await import(
|
|
205
|
-
const traverse = await import(
|
|
206
|
-
const t = await import(
|
|
207
|
-
const fs = await import(
|
|
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(
|
|
255
|
+
const testFiles = await glob("**/*.{test,spec}.{js,jsx,ts,tsx}", {
|
|
210
256
|
cwd: workspaceRoot,
|
|
211
257
|
absolute: true,
|
|
212
|
-
ignore: [
|
|
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,
|
|
264
|
+
const code = fs.readFileSync(file, "utf-8");
|
|
219
265
|
const ast = parse(code, {
|
|
220
|
-
sourceType:
|
|
221
|
-
plugins: [
|
|
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 !==
|
|
274
|
+
if (typeof traverseFunc !== "function") {
|
|
228
275
|
traverseFunc = traverseFunc.default;
|
|
229
276
|
}
|
|
230
|
-
if (typeof traverseFunc !==
|
|
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 ===
|
|
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
|
|
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
|
|
2
|
-
|
|
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=
|
|
73
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,30 +1,50 @@
|
|
|
1
|
-
import * as path from
|
|
2
|
-
import { glob } from
|
|
3
|
-
import {
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
172
|
+
return sorted.map(formatRequirement).join("\n");
|
|
154
173
|
}
|
|
155
|
-
//# sourceMappingURL=
|
|
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
|