@popoverai/dotrequirements 0.22.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 +173 -23
- package/dist/cli.js +121 -61
- 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.d.ts +0 -1
- package/dist/commands/browsertest.js +51 -26
- 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 +156 -178
- 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 -6
- package/dist/schema/browser.js +14 -14
- 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 -7
- package/dist/schema/index.js +15 -13
- 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 +91 -0
- package/dist/schema/scenario.js +82 -0
- 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 +5 -4
- package/dist/utils/project-settings.js +28 -20
- package/dist/utils/templates.js +6 -6
- package/package.json +2 -1
package/dist/schema/parser.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Parse Markdown requirements files into typed structures.
|
|
3
3
|
*/
|
|
4
|
-
import { RequirementNode, RequirementsFile
|
|
4
|
+
import { type ParsedCriterion, type RequirementNode, type RequirementsFile } from "./schemas.js";
|
|
5
5
|
/**
|
|
6
6
|
* Parse a criterion line in "position. Label → content" format.
|
|
7
7
|
* Example: "0. Given → user has valid credentials"
|
|
@@ -18,14 +18,14 @@ export declare function parseRequirementBlock(key: string, blockContent: string)
|
|
|
18
18
|
* Parse a complete requirements Markdown file.
|
|
19
19
|
*/
|
|
20
20
|
export declare function parseRequirementsFile(markdownContent: string): {
|
|
21
|
-
metadata: RequirementsFile[
|
|
21
|
+
metadata: RequirementsFile["_meta"];
|
|
22
22
|
requirements: RequirementNode[];
|
|
23
23
|
};
|
|
24
24
|
/**
|
|
25
25
|
* Parse requirements file from disk.
|
|
26
26
|
*/
|
|
27
27
|
export declare function parseRequirementsFromFile(filePath: string): {
|
|
28
|
-
metadata: RequirementsFile[
|
|
28
|
+
metadata: RequirementsFile["_meta"];
|
|
29
29
|
requirements: RequirementNode[];
|
|
30
30
|
};
|
|
31
31
|
/**
|
package/dist/schema/parser.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Parse Markdown requirements files into typed structures.
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { ValidationError, validateMetadata, } from
|
|
7
|
-
|
|
8
|
-
* Default delimiter for requirements.
|
|
9
|
-
* Can be overridden for organization-specific preferences.
|
|
10
|
-
*/
|
|
11
|
-
const DEFAULT_DELIMITER = '→';
|
|
12
|
-
const DELIMITER_PATTERN = '(?:→|->)'; // Non-capturing group for both Unicode and ASCII
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import YAML from "yaml";
|
|
6
|
+
import { ValidationError, validateMetadata, } from "./schemas.js";
|
|
7
|
+
const DELIMITER_PATTERN = "(?:→|->)"; // Non-capturing group for both Unicode and ASCII
|
|
13
8
|
/**
|
|
14
9
|
* Parse a criterion line in "position. Label → content" format.
|
|
15
10
|
* Example: "0. Given → user has valid credentials"
|
|
@@ -26,7 +21,7 @@ export function parseCriterionLine(line, delimiter = DELIMITER_PATTERN) {
|
|
|
26
21
|
}
|
|
27
22
|
return {
|
|
28
23
|
position: match[1],
|
|
29
|
-
label: match[2] ? match[2].trim() :
|
|
24
|
+
label: match[2] ? match[2].trim() : "", // Preserve original case, empty if no label
|
|
30
25
|
content: match[3].trim(),
|
|
31
26
|
};
|
|
32
27
|
}
|
|
@@ -41,7 +36,7 @@ function parseRootLine(line, delimiter = DELIMITER_PATTERN) {
|
|
|
41
36
|
const keyMatch = trimmed.match(/^([\w-]+):\s*(.+)$/);
|
|
42
37
|
if (keyMatch) {
|
|
43
38
|
return {
|
|
44
|
-
label:
|
|
39
|
+
label: "", // Keys use empty string for unlabeled requirements
|
|
45
40
|
content: keyMatch[2].trim(),
|
|
46
41
|
};
|
|
47
42
|
}
|
|
@@ -51,12 +46,12 @@ function parseRootLine(line, delimiter = DELIMITER_PATTERN) {
|
|
|
51
46
|
if (!match) {
|
|
52
47
|
// If no delimiter found, treat entire line as content with empty label
|
|
53
48
|
return {
|
|
54
|
-
label:
|
|
49
|
+
label: "",
|
|
55
50
|
content: trimmed,
|
|
56
51
|
};
|
|
57
52
|
}
|
|
58
53
|
return {
|
|
59
|
-
label: match[1] ? match[1].trim() :
|
|
54
|
+
label: match[1] ? match[1].trim() : "",
|
|
60
55
|
content: match[2].trim(),
|
|
61
56
|
};
|
|
62
57
|
}
|
|
@@ -66,20 +61,20 @@ function parseRootLine(line, delimiter = DELIMITER_PATTERN) {
|
|
|
66
61
|
* Subsequent lines are criteria with position paths.
|
|
67
62
|
*/
|
|
68
63
|
export function parseRequirementBlock(key, blockContent) {
|
|
69
|
-
const lines = blockContent.trim().split(
|
|
64
|
+
const lines = blockContent.trim().split("\n");
|
|
70
65
|
if (lines.length === 0) {
|
|
71
|
-
throw new ValidationError(
|
|
66
|
+
throw new ValidationError("Empty requirement block", key);
|
|
72
67
|
}
|
|
73
68
|
// First line is the requirement content
|
|
74
69
|
const rootParsed = parseRootLine(lines[0]);
|
|
75
70
|
if (!rootParsed.content) {
|
|
76
|
-
throw new ValidationError(
|
|
71
|
+
throw new ValidationError("Requirement content (first line) cannot be empty", key);
|
|
77
72
|
}
|
|
78
73
|
const root = {
|
|
79
74
|
id: key,
|
|
80
75
|
// Root requirements always use "requirementHeader" label for indexing/querying
|
|
81
76
|
// The markdown format doesn't preserve root labels, but we standardize on this value
|
|
82
|
-
label:
|
|
77
|
+
label: "requirementHeader",
|
|
83
78
|
content: rootParsed.content,
|
|
84
79
|
children: [],
|
|
85
80
|
};
|
|
@@ -88,7 +83,7 @@ export function parseRequirementBlock(key, blockContent) {
|
|
|
88
83
|
for (let i = 1; i < lines.length; i++) {
|
|
89
84
|
const line = lines[i];
|
|
90
85
|
// Skip empty lines and comments
|
|
91
|
-
if (!line.trim() || line.trim().startsWith(
|
|
86
|
+
if (!line.trim() || line.trim().startsWith("#")) {
|
|
92
87
|
continue;
|
|
93
88
|
}
|
|
94
89
|
const criterion = parseCriterionLine(line);
|
|
@@ -108,8 +103,8 @@ export function parseRequirementBlock(key, blockContent) {
|
|
|
108
103
|
function buildTreeFromPositions(root, rootKey, criteriaByPosition) {
|
|
109
104
|
// Get all top-level children (single digit positions: "0", "1", "2")
|
|
110
105
|
const topLevelPositions = Array.from(criteriaByPosition.keys())
|
|
111
|
-
.filter(pos => !pos.includes(
|
|
112
|
-
.sort((a, b) => parseInt(a) - parseInt(b));
|
|
106
|
+
.filter((pos) => !pos.includes("."))
|
|
107
|
+
.sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
|
|
113
108
|
for (const position of topLevelPositions) {
|
|
114
109
|
const criterion = criteriaByPosition.get(position);
|
|
115
110
|
const childNode = {
|
|
@@ -129,9 +124,9 @@ function buildTreeFromPositions(root, rootKey, criteriaByPosition) {
|
|
|
129
124
|
function buildChildrenFromPositions(parent, parentPosition, rootKey, criteriaByPosition) {
|
|
130
125
|
// Find direct children (e.g., if parent is "1", find "1.0", "1.1", etc.)
|
|
131
126
|
const childPositions = Array.from(criteriaByPosition.keys())
|
|
132
|
-
.filter(pos => {
|
|
133
|
-
const parts = pos.split(
|
|
134
|
-
const parentParts = parentPosition.split(
|
|
127
|
+
.filter((pos) => {
|
|
128
|
+
const parts = pos.split(".");
|
|
129
|
+
const parentParts = parentPosition.split(".");
|
|
135
130
|
// Must be exactly one level deeper
|
|
136
131
|
if (parts.length !== parentParts.length + 1) {
|
|
137
132
|
return false;
|
|
@@ -145,8 +140,8 @@ function buildChildrenFromPositions(parent, parentPosition, rootKey, criteriaByP
|
|
|
145
140
|
return true;
|
|
146
141
|
})
|
|
147
142
|
.sort((a, b) => {
|
|
148
|
-
const aLast = parseInt(a.split(
|
|
149
|
-
const bLast = parseInt(b.split(
|
|
143
|
+
const aLast = parseInt(a.split(".").pop(), 10);
|
|
144
|
+
const bLast = parseInt(b.split(".").pop(), 10);
|
|
150
145
|
return aLast - bLast;
|
|
151
146
|
});
|
|
152
147
|
for (const position of childPositions) {
|
|
@@ -171,7 +166,7 @@ function extractFrontmatter(content) {
|
|
|
171
166
|
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
|
|
172
167
|
const match = content.match(frontmatterRegex);
|
|
173
168
|
if (!match) {
|
|
174
|
-
throw new ValidationError(
|
|
169
|
+
throw new ValidationError("Missing YAML frontmatter (should start with ---)");
|
|
175
170
|
}
|
|
176
171
|
const [, frontmatterYaml, body] = match;
|
|
177
172
|
let frontmatter;
|
|
@@ -179,7 +174,7 @@ function extractFrontmatter(content) {
|
|
|
179
174
|
frontmatter = YAML.parse(frontmatterYaml);
|
|
180
175
|
}
|
|
181
176
|
catch (error) {
|
|
182
|
-
throw new ValidationError(
|
|
177
|
+
throw new ValidationError("Failed to parse YAML frontmatter", undefined, error);
|
|
183
178
|
}
|
|
184
179
|
return { frontmatter, body: body.trim() };
|
|
185
180
|
}
|
|
@@ -194,8 +189,8 @@ function extractRequirementBlocks(body) {
|
|
|
194
189
|
const blocks = new Map();
|
|
195
190
|
// Find all ```dotrequirements code blocks
|
|
196
191
|
const blockRegex = /```dotrequirements\n([\s\S]*?)```/gm;
|
|
197
|
-
let match;
|
|
198
|
-
while (
|
|
192
|
+
let match = blockRegex.exec(body);
|
|
193
|
+
while (match !== null) {
|
|
199
194
|
const blockContent = match[1];
|
|
200
195
|
// Extract key from first line of block (format: "KEY: content")
|
|
201
196
|
const firstLineMatch = blockContent.match(/^([\w-]+):\s*(.+)/);
|
|
@@ -209,6 +204,7 @@ function extractRequirementBlocks(body) {
|
|
|
209
204
|
const headingMatch = textBeforeBlock.match(/^#{2,6}\s+([^\n]+)\s*$/m);
|
|
210
205
|
const title = headingMatch ? headingMatch[1].trim() : key;
|
|
211
206
|
blocks.set(key, { title, blockContent: blockContent.trim() });
|
|
207
|
+
match = blockRegex.exec(body);
|
|
212
208
|
}
|
|
213
209
|
return blocks;
|
|
214
210
|
}
|
|
@@ -223,7 +219,7 @@ export function parseRequirementsFile(markdownContent) {
|
|
|
223
219
|
// Extract requirement blocks
|
|
224
220
|
const blocks = extractRequirementBlocks(body);
|
|
225
221
|
if (blocks.size === 0) {
|
|
226
|
-
throw new ValidationError(
|
|
222
|
+
throw new ValidationError("No requirement blocks found in document");
|
|
227
223
|
}
|
|
228
224
|
// Parse each requirement block
|
|
229
225
|
const requirements = [];
|
|
@@ -246,7 +242,7 @@ export function parseRequirementsFile(markdownContent) {
|
|
|
246
242
|
*/
|
|
247
243
|
export function parseRequirementsFromFile(filePath) {
|
|
248
244
|
try {
|
|
249
|
-
const content = fs.readFileSync(filePath,
|
|
245
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
250
246
|
return parseRequirementsFile(content);
|
|
251
247
|
}
|
|
252
248
|
catch (error) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Resolve label-based requirement paths to numeric paths.
|
|
3
3
|
* Supports: REQ123.given, REQ123.when.then, REQ123.given#1
|
|
4
4
|
*/
|
|
5
|
-
import { RequirementNode } from
|
|
5
|
+
import type { RequirementNode } from "./schemas.js";
|
|
6
6
|
/**
|
|
7
7
|
* Parse a requirement path into segments.
|
|
8
8
|
* E.g., "REQ123.given.then" → ["REQ123", "given", "then"]
|
package/dist/schema/resolver.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* E.g., "REQ123.given#1" → ["REQ123", "given#1"]
|
|
9
9
|
*/
|
|
10
10
|
export function parseRequirementPath(path) {
|
|
11
|
-
return path.split(
|
|
11
|
+
return path.split(".");
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Parse a path segment that may include disambiguation.
|
|
@@ -22,7 +22,7 @@ export function parsePathSegment(segment) {
|
|
|
22
22
|
}
|
|
23
23
|
return {
|
|
24
24
|
label: match[1],
|
|
25
|
-
index: match[2] ? parseInt(match[2]) : 0,
|
|
25
|
+
index: match[2] ? parseInt(match[2], 10) : 0,
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
@@ -31,7 +31,7 @@ export function parsePathSegment(segment) {
|
|
|
31
31
|
* Examples: "Several-words" -> "several words", "AC" -> "ac"
|
|
32
32
|
*/
|
|
33
33
|
function normalizeLabel(label) {
|
|
34
|
-
return label.replace(/-/g,
|
|
34
|
+
return label.replace(/-/g, " ").toLowerCase();
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
37
|
* Find children matching a label.
|
|
@@ -40,7 +40,7 @@ function normalizeLabel(label) {
|
|
|
40
40
|
*/
|
|
41
41
|
export function findChildrenByLabel(node, label) {
|
|
42
42
|
const normalizedLabel = normalizeLabel(label);
|
|
43
|
-
return node.children.filter(child => normalizeLabel(child.label) === normalizedLabel);
|
|
43
|
+
return node.children.filter((child) => normalizeLabel(child.label) === normalizedLabel);
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Resolve a label-based path segment to a numeric index.
|
|
@@ -49,7 +49,7 @@ export function findChildrenByLabel(node, label) {
|
|
|
49
49
|
export function resolvePathSegment(node, segment) {
|
|
50
50
|
// Check if segment is numeric (direct index)
|
|
51
51
|
if (/^\d+$/.test(segment)) {
|
|
52
|
-
const index = parseInt(segment);
|
|
52
|
+
const index = parseInt(segment, 10);
|
|
53
53
|
if (index < node.children.length) {
|
|
54
54
|
return {
|
|
55
55
|
child: node.children[index],
|
|
@@ -94,7 +94,7 @@ export function resolveRequirementPath(requirements, path) {
|
|
|
94
94
|
}
|
|
95
95
|
// First segment should be the root requirement ID
|
|
96
96
|
const rootId = segments[0];
|
|
97
|
-
let currentNode = requirements.find(r => r.id === rootId);
|
|
97
|
+
let currentNode = requirements.find((r) => r.id === rootId);
|
|
98
98
|
if (!currentNode) {
|
|
99
99
|
return null;
|
|
100
100
|
}
|
|
@@ -130,7 +130,7 @@ export function resolveToNumericPath(requirements, path) {
|
|
|
130
130
|
*
|
|
131
131
|
* Returns paths like: ["REQ123", "REQ123.given", "REQ123.when", ...]
|
|
132
132
|
*/
|
|
133
|
-
export function getAllLabelPaths(node, prefix =
|
|
133
|
+
export function getAllLabelPaths(node, prefix = "") {
|
|
134
134
|
const currentPath = prefix ? `${prefix}.${node.label}` : node.id;
|
|
135
135
|
const paths = [currentPath];
|
|
136
136
|
for (const child of node.children) {
|
|
@@ -148,7 +148,7 @@ export function checkPathAmbiguity(requirements, path) {
|
|
|
148
148
|
return 0;
|
|
149
149
|
}
|
|
150
150
|
const rootId = segments[0];
|
|
151
|
-
let currentNode = requirements.find(r => r.id === rootId);
|
|
151
|
+
let currentNode = requirements.find((r) => r.id === rootId);
|
|
152
152
|
if (!currentNode) {
|
|
153
153
|
return 0;
|
|
154
154
|
}
|
|
@@ -160,7 +160,7 @@ export function checkPathAmbiguity(requirements, path) {
|
|
|
160
160
|
const segment = segments[i];
|
|
161
161
|
// Numeric segments are never ambiguous
|
|
162
162
|
if (/^\d+$/.test(segment)) {
|
|
163
|
-
const index = parseInt(segment);
|
|
163
|
+
const index = parseInt(segment, 10);
|
|
164
164
|
if (index >= currentNode.children.length) {
|
|
165
165
|
return 0; // Invalid path
|
|
166
166
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scenario builder — turns a requirement tree into a browser-automation Scenario.
|
|
3
|
+
*
|
|
4
|
+
* Used by both the CLI (via `dotreq acceptance-test`) and the Convex Node action
|
|
5
|
+
* (`testCoverage/browserRun:run`) so the two paths produce identical scenarios
|
|
6
|
+
* for the same input.
|
|
7
|
+
*
|
|
8
|
+
* Naive passthrough: every requirement node with non-empty content becomes
|
|
9
|
+
* exactly one assertion keyed by its numeric-path id (e.g. "LOGIN-1" for the
|
|
10
|
+
* root, "LOGIN-1.0" for the first criterion). No arrange/act steps are
|
|
11
|
+
* emitted; Stagehand's hybrid agent is expected to infer setup from the
|
|
12
|
+
* assertion list. See BROWSER-RUN-3 in
|
|
13
|
+
* .requirements/testing-tab-browser-run.requirements.md.
|
|
14
|
+
*
|
|
15
|
+
* Types here are defined locally to avoid adding `@popoverai/browser-automation`
|
|
16
|
+
* as a runtime dependency of this package. They are structurally compatible
|
|
17
|
+
* with browser-automation's exported `Scenario` / `Step` types and can be
|
|
18
|
+
* passed directly to `runScenario`.
|
|
19
|
+
*/
|
|
20
|
+
import type { RequirementNode } from "./schemas.js";
|
|
21
|
+
/**
|
|
22
|
+
* One step in a scenario. Structurally compatible with
|
|
23
|
+
* `@popoverai/browser-automation`'s `Step` type.
|
|
24
|
+
*/
|
|
25
|
+
export interface ScenarioStep {
|
|
26
|
+
step: "arrange" | "act" | "assert";
|
|
27
|
+
description: string;
|
|
28
|
+
url?: string;
|
|
29
|
+
key?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* One entry in the `Scenario.variables` map. Structurally compatible with
|
|
33
|
+
* `@popoverai/browser-automation`'s `Variable` type — the `value` is what
|
|
34
|
+
* gets substituted into the scenario, and the optional `description` gives
|
|
35
|
+
* the agent extra context about what the substitution represents.
|
|
36
|
+
*/
|
|
37
|
+
export interface ScenarioVariable {
|
|
38
|
+
value: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
}
|
|
41
|
+
export type ScenarioVariables = Record<string, ScenarioVariable>;
|
|
42
|
+
/**
|
|
43
|
+
* A scenario passed to `runScenario`. Structurally compatible with
|
|
44
|
+
* `@popoverai/browser-automation`'s `Scenario` type.
|
|
45
|
+
*/
|
|
46
|
+
export interface Scenario {
|
|
47
|
+
baseUrl: string;
|
|
48
|
+
steps: ScenarioStep[];
|
|
49
|
+
variables?: ScenarioVariables;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* One requirement contributing an assertion to a scenario. The minimum shape
|
|
53
|
+
* the builder needs so callers sourcing requirements from different backends
|
|
54
|
+
* (Markdown files via `RequirementNode`, Convex rows, etc.) can all use it.
|
|
55
|
+
*/
|
|
56
|
+
export interface ScenarioAssertionSource {
|
|
57
|
+
/** Full requirement key including path, e.g. "LOGIN-1" or "LOGIN-1.0.1". */
|
|
58
|
+
id: string;
|
|
59
|
+
/** Requirement text; becomes the assertion's `description`. */
|
|
60
|
+
content: string;
|
|
61
|
+
}
|
|
62
|
+
export interface BuildScenarioOptions {
|
|
63
|
+
/**
|
|
64
|
+
* The requirements contributing assertions, in the order they should be
|
|
65
|
+
* evaluated. Every entry with non-empty `content` becomes one `assert` step.
|
|
66
|
+
*/
|
|
67
|
+
assertions: ScenarioAssertionSource[];
|
|
68
|
+
/** Target URL the scenario runs against; becomes `Scenario.baseUrl`. */
|
|
69
|
+
baseUrl: string;
|
|
70
|
+
/**
|
|
71
|
+
* Optional Stagehand variable substitutions. Used to inject secrets
|
|
72
|
+
* (test-user credentials, bypass tokens) into assertion descriptions
|
|
73
|
+
* without exposing them to the language model in plaintext.
|
|
74
|
+
*/
|
|
75
|
+
variables?: Record<string, string>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Build a `Scenario` from a list of requirement assertion sources.
|
|
79
|
+
*
|
|
80
|
+
* Throws if the resulting scenario would have zero assertions
|
|
81
|
+
* (browser-automation rejects scenarios with no asserts).
|
|
82
|
+
*/
|
|
83
|
+
export declare function buildScenarioFromRequirements(opts: BuildScenarioOptions): Scenario;
|
|
84
|
+
/**
|
|
85
|
+
* Convenience wrapper for callers that have a `RequirementNode` tree (root
|
|
86
|
+
* plus nested children). Flattens the tree to the order produced by
|
|
87
|
+
* `flattenRequirementTree` (pre-order: root first, then each subtree), then
|
|
88
|
+
* delegates to `buildScenarioFromRequirements`.
|
|
89
|
+
*/
|
|
90
|
+
export declare function requirementTreeToScenario(root: RequirementNode, opts: Omit<BuildScenarioOptions, "assertions">): Scenario;
|
|
91
|
+
//# sourceMappingURL=scenario.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scenario builder — turns a requirement tree into a browser-automation Scenario.
|
|
3
|
+
*
|
|
4
|
+
* Used by both the CLI (via `dotreq acceptance-test`) and the Convex Node action
|
|
5
|
+
* (`testCoverage/browserRun:run`) so the two paths produce identical scenarios
|
|
6
|
+
* for the same input.
|
|
7
|
+
*
|
|
8
|
+
* Naive passthrough: every requirement node with non-empty content becomes
|
|
9
|
+
* exactly one assertion keyed by its numeric-path id (e.g. "LOGIN-1" for the
|
|
10
|
+
* root, "LOGIN-1.0" for the first criterion). No arrange/act steps are
|
|
11
|
+
* emitted; Stagehand's hybrid agent is expected to infer setup from the
|
|
12
|
+
* assertion list. See BROWSER-RUN-3 in
|
|
13
|
+
* .requirements/testing-tab-browser-run.requirements.md.
|
|
14
|
+
*
|
|
15
|
+
* Types here are defined locally to avoid adding `@popoverai/browser-automation`
|
|
16
|
+
* as a runtime dependency of this package. They are structurally compatible
|
|
17
|
+
* with browser-automation's exported `Scenario` / `Step` types and can be
|
|
18
|
+
* passed directly to `runScenario`.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Pre-order flatten: root first, then each subtree's nodes in order.
|
|
22
|
+
* Inlined here (rather than imported from `parser.ts`) so this module stays
|
|
23
|
+
* free of Node-only dependencies and can be re-exported from the browser
|
|
24
|
+
* entry point.
|
|
25
|
+
*/
|
|
26
|
+
function flattenTree(node) {
|
|
27
|
+
const result = [node];
|
|
28
|
+
for (const child of node.children) {
|
|
29
|
+
result.push(...flattenTree(child));
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a `Scenario` from a list of requirement assertion sources.
|
|
35
|
+
*
|
|
36
|
+
* Throws if the resulting scenario would have zero assertions
|
|
37
|
+
* (browser-automation rejects scenarios with no asserts).
|
|
38
|
+
*/
|
|
39
|
+
export function buildScenarioFromRequirements(opts) {
|
|
40
|
+
const { assertions, baseUrl, variables } = opts;
|
|
41
|
+
if (!baseUrl || baseUrl.trim().length === 0) {
|
|
42
|
+
throw new Error("buildScenarioFromRequirements: baseUrl is required");
|
|
43
|
+
}
|
|
44
|
+
const steps = [];
|
|
45
|
+
for (const source of assertions) {
|
|
46
|
+
const description = source.content.trim();
|
|
47
|
+
if (description.length === 0)
|
|
48
|
+
continue;
|
|
49
|
+
steps.push({
|
|
50
|
+
step: "assert",
|
|
51
|
+
description,
|
|
52
|
+
key: source.id,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (steps.length === 0) {
|
|
56
|
+
throw new Error("buildScenarioFromRequirements: requirement tree has no nodes with content; cannot build a scenario");
|
|
57
|
+
}
|
|
58
|
+
const scenario = { baseUrl, steps };
|
|
59
|
+
if (variables && Object.keys(variables).length > 0) {
|
|
60
|
+
// Callers pass a flat Record<string, string> for ergonomics; wrap each
|
|
61
|
+
// entry as `{ value }` so the scenario matches runScenario's expected
|
|
62
|
+
// Variables shape. Callers with richer values can set variables on the
|
|
63
|
+
// returned scenario directly before invocation.
|
|
64
|
+
scenario.variables = Object.fromEntries(Object.entries(variables).map(([key, value]) => [key, { value }]));
|
|
65
|
+
}
|
|
66
|
+
return scenario;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Convenience wrapper for callers that have a `RequirementNode` tree (root
|
|
70
|
+
* plus nested children). Flattens the tree to the order produced by
|
|
71
|
+
* `flattenRequirementTree` (pre-order: root first, then each subtree), then
|
|
72
|
+
* delegates to `buildScenarioFromRequirements`.
|
|
73
|
+
*/
|
|
74
|
+
export function requirementTreeToScenario(root, opts) {
|
|
75
|
+
const flat = flattenTree(root);
|
|
76
|
+
const assertions = flat.map((node) => ({
|
|
77
|
+
id: node.id,
|
|
78
|
+
content: node.content,
|
|
79
|
+
}));
|
|
80
|
+
return buildScenarioFromRequirements({ ...opts, assertions });
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=scenario.js.map
|
package/dist/schema/schemas.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Zod schemas for Markdown requirements format.
|
|
3
3
|
* TypeScript types are inferred from these schemas using z.infer<>.
|
|
4
4
|
*/
|
|
5
|
-
import { z } from
|
|
5
|
+
import { z } from "zod";
|
|
6
6
|
/**
|
|
7
7
|
* Requirement prefix format: uppercase letters and hyphens, 1-30 chars.
|
|
8
8
|
* First char must be a letter.
|
|
@@ -100,7 +100,7 @@ export declare const CONVEX_ID_PATTERN: RegExp;
|
|
|
100
100
|
*/
|
|
101
101
|
export type PushValidationResult = {
|
|
102
102
|
valid: true;
|
|
103
|
-
action:
|
|
103
|
+
action: "create" | "update";
|
|
104
104
|
warning?: string;
|
|
105
105
|
} | {
|
|
106
106
|
valid: false;
|
|
@@ -246,7 +246,7 @@ export declare const RequirementsFileSchema: z.ZodObject<{
|
|
|
246
246
|
}>;
|
|
247
247
|
}, z.ZodTypeAny, "passthrough">>;
|
|
248
248
|
export type RequirementsFile = z.infer<typeof RequirementsFileSchema> & {
|
|
249
|
-
[key: string]:
|
|
249
|
+
[key: string]: unknown;
|
|
250
250
|
};
|
|
251
251
|
/**
|
|
252
252
|
* Parsed criterion from Markdown (position. Label: content format)
|