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