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