@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
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract test code context using @babel/parser AST traversal.
|
|
3
|
+
*
|
|
4
|
+
* Finds requirement() call references and extracts the enclosing
|
|
5
|
+
* meaningful code block (function, describe, test, etc.)
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import { parse } from "@babel/parser";
|
|
9
|
+
import traverse from "@babel/traverse";
|
|
10
|
+
import * as t from "@babel/types";
|
|
11
|
+
/**
|
|
12
|
+
* Find all test code blocks that reference a specific requirement
|
|
13
|
+
*/
|
|
14
|
+
export function findTestCodeForRequirement(filePath, requirementId) {
|
|
15
|
+
try {
|
|
16
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
17
|
+
const results = [];
|
|
18
|
+
// Parse with TypeScript and JSX support
|
|
19
|
+
const ast = parse(code, {
|
|
20
|
+
sourceType: "module",
|
|
21
|
+
plugins: ["typescript", "jsx"],
|
|
22
|
+
errorRecovery: true,
|
|
23
|
+
});
|
|
24
|
+
// Handle both ES module and CommonJS exports
|
|
25
|
+
const traverseFunc =
|
|
26
|
+
// biome-ignore lint/suspicious/noExplicitAny: @babel/traverse CJS default-export interop (callable function vs namespace import)
|
|
27
|
+
typeof traverse === "function" ? traverse : traverse.default;
|
|
28
|
+
traverseFunc(ast, {
|
|
29
|
+
CallExpression(path) {
|
|
30
|
+
// Check if this is a requirement('ID', ...) call
|
|
31
|
+
const callee = path.node.callee;
|
|
32
|
+
if (t.isIdentifier(callee) && callee.name === "requirement") {
|
|
33
|
+
const args = path.node.arguments;
|
|
34
|
+
// Extract all string literal arguments (supports multi-requirement calls)
|
|
35
|
+
const refIds = [];
|
|
36
|
+
for (const arg of args) {
|
|
37
|
+
if (t.isStringLiteral(arg)) {
|
|
38
|
+
refIds.push(arg.value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Check if any argument matches the requirement we're looking for
|
|
42
|
+
// Match exact ID or if it's a child (e.g., searching for REQ-123 matches REQ-123.0)
|
|
43
|
+
for (const refId of refIds) {
|
|
44
|
+
const isMatch = refId === requirementId || refId.startsWith(`${requirementId}.`);
|
|
45
|
+
if (isMatch) {
|
|
46
|
+
// Find the meaningful enclosing block
|
|
47
|
+
const contextNode = findMeaningfulParent(path);
|
|
48
|
+
if (contextNode?.node.loc) {
|
|
49
|
+
const { start, end } = contextNode.node.loc;
|
|
50
|
+
const nodeStart = contextNode.node.start ?? 0;
|
|
51
|
+
const nodeEnd = contextNode.node.end ?? code.length;
|
|
52
|
+
results.push({
|
|
53
|
+
file: filePath,
|
|
54
|
+
startLine: start.line,
|
|
55
|
+
endLine: end.line,
|
|
56
|
+
code: code.slice(nodeStart, nodeEnd),
|
|
57
|
+
requirementId: refId,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Only add one result per call expression, even if multiple refs match
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error(`Error parsing ${filePath}:`, error);
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Walk up the AST to find the nearest "meaningful" parent node.
|
|
76
|
+
* This could be a function, describe/test/it call, method, etc.
|
|
77
|
+
*/
|
|
78
|
+
function findMeaningfulParent(path) {
|
|
79
|
+
let current = path;
|
|
80
|
+
while (current) {
|
|
81
|
+
const node = current.node;
|
|
82
|
+
// Check for various meaningful boundaries
|
|
83
|
+
if (t.isFunctionDeclaration(node) ||
|
|
84
|
+
t.isFunctionExpression(node) ||
|
|
85
|
+
t.isArrowFunctionExpression(node) ||
|
|
86
|
+
t.isClassMethod(node) ||
|
|
87
|
+
t.isObjectMethod(node)) {
|
|
88
|
+
return current;
|
|
89
|
+
}
|
|
90
|
+
// Check for call expressions (describe, test, it, etc.)
|
|
91
|
+
if (t.isCallExpression(node)) {
|
|
92
|
+
const callee = node.callee;
|
|
93
|
+
// Common test framework function names
|
|
94
|
+
const testFunctionNames = [
|
|
95
|
+
"describe",
|
|
96
|
+
"it",
|
|
97
|
+
"test",
|
|
98
|
+
"suite",
|
|
99
|
+
"context",
|
|
100
|
+
"beforeEach",
|
|
101
|
+
"afterEach",
|
|
102
|
+
"beforeAll",
|
|
103
|
+
"afterAll",
|
|
104
|
+
];
|
|
105
|
+
if (t.isIdentifier(callee) && testFunctionNames.includes(callee.name)) {
|
|
106
|
+
return current;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Check for variable declarations that might contain test functions
|
|
110
|
+
if (t.isVariableDeclarator(node)) {
|
|
111
|
+
return current;
|
|
112
|
+
}
|
|
113
|
+
// Check for export declarations (for Storybook stories, etc.)
|
|
114
|
+
if (t.isExportNamedDeclaration(node) ||
|
|
115
|
+
t.isExportDefaultDeclaration(node)) {
|
|
116
|
+
return current;
|
|
117
|
+
}
|
|
118
|
+
current = current.parentPath;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Find all files that reference a requirement
|
|
124
|
+
*/
|
|
125
|
+
export function findFilesWithRequirement(files, requirementId) {
|
|
126
|
+
const matchingFiles = [];
|
|
127
|
+
for (const file of files) {
|
|
128
|
+
try {
|
|
129
|
+
const code = fs.readFileSync(file, "utf-8");
|
|
130
|
+
// Check for exact match or child references (e.g., AUTH-VALID-LOGIN or AUTH-VALID-LOGIN.0)
|
|
131
|
+
// Simple string search first for performance
|
|
132
|
+
// Patterns match both single and multi-arg calls:
|
|
133
|
+
// requirement('REQ-123')
|
|
134
|
+
// requirement('REQ-123', 'REQ-456')
|
|
135
|
+
// requirement('REQ-456', 'REQ-123') <-- ID anywhere in args
|
|
136
|
+
const hasMatch = code.includes(`'${requirementId}'`) ||
|
|
137
|
+
code.includes(`"${requirementId}"`) ||
|
|
138
|
+
code.includes(`\`${requirementId}\``) ||
|
|
139
|
+
code.includes(`'${requirementId}.`) ||
|
|
140
|
+
code.includes(`"${requirementId}.`) ||
|
|
141
|
+
code.includes(`\`${requirementId}.`);
|
|
142
|
+
if (hasMatch) {
|
|
143
|
+
matchingFiles.push(file);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch { }
|
|
147
|
+
}
|
|
148
|
+
return matchingFiles;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=testCodeExtractor.js.map
|
package/dist/schema/browser.d.ts
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
* This file excludes Node.js-specific functionality (file I/O, fs module).
|
|
4
4
|
* Use this entry point when importing from browser/web environments.
|
|
5
5
|
*/
|
|
6
|
-
export {
|
|
7
|
-
export type {
|
|
8
|
-
export {
|
|
9
|
-
export
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
6
|
+
export { buildRequirementMarkdown, buildRequirementsMarkdown, DEFAULT_DELIMITER, } from "./builder.js";
|
|
7
|
+
export type { ConvexRequirement } from "./conversions.js";
|
|
8
|
+
export { buildMetadata, constructKey, convexToRequirements, extractRequirementKeys, groupByRoot, parseKey, requirementsToConvex, } from "./conversions.js";
|
|
9
|
+
export { DELIMITER_PATTERN, extractRequirementBlocks, findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, parseRequirementBlocksFromMarkdown, parseRootLine, } from "./parser-core.js";
|
|
10
|
+
export type { Metadata, ParsedCriterion, RequirementKey, RequirementNode, RequirementPrefix, RequirementsFile, } from "./schemas.js";
|
|
11
|
+
export { buildRequirementKey, MetadataSchema, normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PATTERN, REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema, ValidationError, validateKey, validateMetadata, validatePrefix, validateRequirementNode, validateRequirementsFile, } from "./schemas.js";
|
|
12
|
+
export type { BuildScenarioOptions, Scenario, ScenarioAssertionSource, ScenarioStep, } from "./scenario.js";
|
|
13
|
+
export { buildScenarioFromRequirements, requirementTreeToScenario, } from "./scenario.js";
|
|
12
14
|
//# sourceMappingURL=browser.d.ts.map
|
package/dist/schema/browser.js
CHANGED
|
@@ -3,22 +3,22 @@
|
|
|
3
3
|
* This file excludes Node.js-specific functionality (file I/O, fs module).
|
|
4
4
|
* Use this entry point when importing from browser/web environments.
|
|
5
5
|
*/
|
|
6
|
+
// Building (uses yaml package - browser-safe)
|
|
7
|
+
export { buildRequirementMarkdown, buildRequirementsMarkdown, DEFAULT_DELIMITER, } from "./builder.js";
|
|
8
|
+
export { buildMetadata, constructKey, convexToRequirements, extractRequirementKeys, groupByRoot, parseKey, requirementsToConvex, } from "./conversions.js";
|
|
9
|
+
// Parser core (pure TypeScript - browser-safe, no fs dependency)
|
|
10
|
+
// These functions parse markdown strings directly without file I/O
|
|
11
|
+
export { DELIMITER_PATTERN, extractRequirementBlocks, findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, parseRequirementBlocksFromMarkdown, parseRootLine, } from "./parser-core.js";
|
|
6
12
|
// Schemas and types (uses zod - browser-safe)
|
|
7
|
-
export {
|
|
13
|
+
export { buildRequirementKey,
|
|
8
14
|
// Zod schemas
|
|
9
|
-
MetadataSchema,
|
|
15
|
+
MetadataSchema,
|
|
16
|
+
// Prefix/key utilities
|
|
17
|
+
normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PATTERN,
|
|
10
18
|
// Patterns (for external validation)
|
|
11
|
-
REQUIREMENT_PREFIX_PATTERN,
|
|
19
|
+
REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema,
|
|
12
20
|
// Validation
|
|
13
|
-
ValidationError, validateMetadata, validateRequirementNode, validateRequirementsFile,
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
// Building (uses yaml package - browser-safe)
|
|
17
|
-
export { DEFAULT_DELIMITER, buildRequirementsMarkdown, buildRequirementMarkdown, } from './builder.js';
|
|
18
|
-
export { convexToRequirements, requirementsToConvex, buildMetadata, extractRequirementKeys, groupByRoot, constructKey, parseKey, } from './conversions.js';
|
|
19
|
-
// Parser core (pure TypeScript - browser-safe, no fs dependency)
|
|
20
|
-
// These functions parse markdown strings directly without file I/O
|
|
21
|
-
export { DELIMITER_PATTERN, parseCriterionLine, parseRootLine, parseRequirementBlock, extractRequirementBlocks, parseRequirementBlocksFromMarkdown, flattenRequirementTree, findRequirementById, getAllRequirements, } from './parser-core.js';
|
|
22
|
-
// NOTE: parser.ts and resolver.ts are excluded because they use Node.js 'fs' module.
|
|
23
|
-
// Use parser-core.ts functions above for browser/Convex environments.
|
|
21
|
+
ValidationError, validateKey, validateMetadata, validatePrefix, validateRequirementNode, validateRequirementsFile, } from "./schemas.js";
|
|
22
|
+
// Scenario building (pure TypeScript - browser-safe, used by Convex Node actions)
|
|
23
|
+
export { buildScenarioFromRequirements, requirementTreeToScenario, } from "./scenario.js";
|
|
24
24
|
//# sourceMappingURL=browser.js.map
|
package/dist/schema/builder.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build Markdown requirements files from structured data.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { Metadata, RequirementNode } from "./schemas.js";
|
|
5
5
|
/**
|
|
6
6
|
* Default delimiter for requirements.
|
|
7
7
|
* Can be overridden for organization-specific preferences.
|
package/dist/schema/builder.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build Markdown requirements files from structured data.
|
|
3
3
|
*/
|
|
4
|
-
import YAML from
|
|
4
|
+
import YAML from "yaml";
|
|
5
5
|
/**
|
|
6
6
|
* Default delimiter for requirements.
|
|
7
7
|
* Can be overridden for organization-specific preferences.
|
|
8
8
|
*/
|
|
9
|
-
export const DEFAULT_DELIMITER =
|
|
9
|
+
export const DEFAULT_DELIMITER = "→";
|
|
10
10
|
/**
|
|
11
11
|
* Build YAML frontmatter from metadata.
|
|
12
12
|
*/
|
|
@@ -17,51 +17,20 @@ function buildFrontmatter(metadata) {
|
|
|
17
17
|
});
|
|
18
18
|
return `---\n${yamlStr.trim()}\n---`;
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Build a dotrequirements block from a requirement node.
|
|
22
|
-
* Formats children with explicit position paths and proper indentation.
|
|
23
|
-
*/
|
|
24
|
-
function buildRequirementBlock(node, delimiter = DEFAULT_DELIMITER) {
|
|
25
|
-
// First line: root requirement with delimiter
|
|
26
|
-
// Include label if it's not empty (empty string means unlabeled)
|
|
27
|
-
const rootLabel = node.label
|
|
28
|
-
? `${node.label.charAt(0).toUpperCase() + node.label.slice(1)} `
|
|
29
|
-
: '';
|
|
30
|
-
let block = `${rootLabel}${delimiter} ${node.content}\n`;
|
|
31
|
-
// Recursively build criteria lines
|
|
32
|
-
function addCriteria(children, parentPosition = '') {
|
|
33
|
-
children.forEach((child, index) => {
|
|
34
|
-
const position = parentPosition === '' ? `${index}` : `${parentPosition}.${index}`;
|
|
35
|
-
const depth = position.split('.').length - 1;
|
|
36
|
-
const indent = ' '.repeat(depth + 1); // 2 spaces per level
|
|
37
|
-
// Include label if present, otherwise just delimiter
|
|
38
|
-
const labelPart = child.label
|
|
39
|
-
? `${child.label.charAt(0).toUpperCase() + child.label.slice(1)} `
|
|
40
|
-
: '';
|
|
41
|
-
block += `${indent}${position}. ${labelPart}${delimiter} ${child.content}\n`;
|
|
42
|
-
// Recursively add grandchildren
|
|
43
|
-
if (child.children && child.children.length > 0) {
|
|
44
|
-
addCriteria(child.children, position);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
addCriteria(node.children);
|
|
49
|
-
return block.trimEnd();
|
|
50
|
-
}
|
|
51
20
|
/**
|
|
52
21
|
* Build the children portion of a requirement block (without the root line).
|
|
53
22
|
*/
|
|
54
23
|
function buildChildrenBlock(children) {
|
|
55
|
-
let block =
|
|
56
|
-
function addChildren(nodes, parentPosition =
|
|
24
|
+
let block = "";
|
|
25
|
+
function addChildren(nodes, parentPosition = "") {
|
|
57
26
|
nodes.forEach((child, index) => {
|
|
58
|
-
const position = parentPosition ===
|
|
59
|
-
const depth = position.split(
|
|
60
|
-
const indent =
|
|
27
|
+
const position = parentPosition === "" ? `${index}` : `${parentPosition}.${index}`;
|
|
28
|
+
const depth = position.split(".").length - 1;
|
|
29
|
+
const indent = " ".repeat(depth + 1); // 2 spaces per level
|
|
61
30
|
// Include label if present, otherwise just delimiter
|
|
62
31
|
const labelPart = child.label
|
|
63
32
|
? `${child.label.charAt(0).toUpperCase() + child.label.slice(1)} `
|
|
64
|
-
:
|
|
33
|
+
: "";
|
|
65
34
|
block += `${indent}${position}. ${labelPart}→ ${child.content}\n`;
|
|
66
35
|
// Recursively add grandchildren
|
|
67
36
|
if (child.children && child.children.length > 0) {
|
|
@@ -79,14 +48,14 @@ function buildRequirementSection(node, title) {
|
|
|
79
48
|
const displayTitle = title || node.content;
|
|
80
49
|
// Heading now just has the title, no key
|
|
81
50
|
let section = `## ${displayTitle}\n\n`;
|
|
82
|
-
section +=
|
|
51
|
+
section += "```dotrequirements\n";
|
|
83
52
|
// First line of block has the key
|
|
84
53
|
section += `${node.id}: ${node.content}\n`;
|
|
85
54
|
// Then add children
|
|
86
55
|
if (node.children && node.children.length > 0) {
|
|
87
56
|
section += buildChildrenBlock(node.children);
|
|
88
57
|
}
|
|
89
|
-
section +=
|
|
58
|
+
section += "\n```\n";
|
|
90
59
|
return section;
|
|
91
60
|
}
|
|
92
61
|
/**
|
|
@@ -94,7 +63,7 @@ function buildRequirementSection(node, title) {
|
|
|
94
63
|
*/
|
|
95
64
|
export function buildRequirementsMarkdown(metadata, requirements, documentTitle) {
|
|
96
65
|
let markdown = buildFrontmatter(metadata);
|
|
97
|
-
markdown +=
|
|
66
|
+
markdown += "\n\n";
|
|
98
67
|
// Add document title if provided
|
|
99
68
|
if (documentTitle) {
|
|
100
69
|
markdown += `# ${documentTitle}\n\n`;
|
|
@@ -102,9 +71,9 @@ export function buildRequirementsMarkdown(metadata, requirements, documentTitle)
|
|
|
102
71
|
// Add each requirement section
|
|
103
72
|
for (const req of requirements) {
|
|
104
73
|
markdown += buildRequirementSection(req);
|
|
105
|
-
markdown +=
|
|
74
|
+
markdown += "\n";
|
|
106
75
|
}
|
|
107
|
-
return markdown.trimEnd()
|
|
76
|
+
return `${markdown.trimEnd()}\n`;
|
|
108
77
|
}
|
|
109
78
|
/**
|
|
110
79
|
* Build Markdown for a single requirement (for testing/debugging).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Convert between Convex flat representation and hierarchical RequirementNode trees.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { Metadata, RequirementNode } from "./schemas.js";
|
|
5
5
|
/**
|
|
6
6
|
* Convex requirement type (flat structure with position paths).
|
|
7
7
|
* This mirrors the Convex database schema.
|
|
@@ -16,7 +16,7 @@ export interface ConvexRequirement {
|
|
|
16
16
|
projectId: string;
|
|
17
17
|
rootId?: string;
|
|
18
18
|
position?: string;
|
|
19
|
-
metadata?:
|
|
19
|
+
metadata?: unknown;
|
|
20
20
|
externalLinks?: {
|
|
21
21
|
jira?: string;
|
|
22
22
|
notion?: string;
|
|
@@ -25,7 +25,7 @@ export function parseKey(key) {
|
|
|
25
25
|
*/
|
|
26
26
|
export function convexToRequirements(convexReqs) {
|
|
27
27
|
// Separate roots from children
|
|
28
|
-
const roots = convexReqs.filter(r => !r.rootId);
|
|
28
|
+
const roots = convexReqs.filter((r) => !r.rootId);
|
|
29
29
|
// Build a map of all requirements by their position path for each root
|
|
30
30
|
const reqsByRoot = new Map();
|
|
31
31
|
for (const req of convexReqs) {
|
|
@@ -35,11 +35,11 @@ export function convexToRequirements(convexReqs) {
|
|
|
35
35
|
rootMap = new Map();
|
|
36
36
|
reqsByRoot.set(req.rootId, rootMap);
|
|
37
37
|
}
|
|
38
|
-
rootMap.set(req.position ||
|
|
38
|
+
rootMap.set(req.position || "", req);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
// Build trees for each root
|
|
42
|
-
return roots.map(root => buildTreeFromPositions(root, reqsByRoot.get(root._id)));
|
|
42
|
+
return roots.map((root) => buildTreeFromPositions(root, reqsByRoot.get(root._id)));
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
45
|
* Recursively build a RequirementNode tree from position-based flat structure.
|
|
@@ -70,9 +70,9 @@ function buildTreeFromPositions(convexReq, positionMap) {
|
|
|
70
70
|
// Find direct children based on position paths
|
|
71
71
|
// If this is a root (no position), find children at positions like ".0", ".1"
|
|
72
72
|
// If this has position ".0", find children at positions like ".0.0", ".0.1"
|
|
73
|
-
const currentPosition = convexReq.position ||
|
|
73
|
+
const currentPosition = convexReq.position || "";
|
|
74
74
|
const childPositionPattern = currentPosition
|
|
75
|
-
? new RegExp(`^${currentPosition.replace(/\./g,
|
|
75
|
+
? new RegExp(`^${currentPosition.replace(/\./g, "\\.")}\\.\\d+$`)
|
|
76
76
|
: /^\.\d+$/;
|
|
77
77
|
const children = [];
|
|
78
78
|
for (const [pos, req] of positionMap.entries()) {
|
|
@@ -82,10 +82,10 @@ function buildTreeFromPositions(convexReq, positionMap) {
|
|
|
82
82
|
}
|
|
83
83
|
// Sort children by position
|
|
84
84
|
const sortedChildren = children.sort((a, b) => {
|
|
85
|
-
return comparePositions(a.position ||
|
|
85
|
+
return comparePositions(a.position || "", b.position || "");
|
|
86
86
|
});
|
|
87
87
|
// Recursively build child nodes
|
|
88
|
-
node.children = sortedChildren.map(child => buildTreeFromPositions(child, positionMap));
|
|
88
|
+
node.children = sortedChildren.map((child) => buildTreeFromPositions(child, positionMap));
|
|
89
89
|
return node;
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
@@ -93,8 +93,8 @@ function buildTreeFromPositions(convexReq, positionMap) {
|
|
|
93
93
|
* Positions are like ".0", ".1", ".0.0", ".0.1"
|
|
94
94
|
*/
|
|
95
95
|
function comparePositions(a, b) {
|
|
96
|
-
const aParts = a.split(
|
|
97
|
-
const bParts = b.split(
|
|
96
|
+
const aParts = a.split(".").filter(Boolean).map(Number);
|
|
97
|
+
const bParts = b.split(".").filter(Boolean).map(Number);
|
|
98
98
|
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
99
99
|
const aVal = aParts[i] ?? -1;
|
|
100
100
|
const bVal = bParts[i] ?? -1;
|
|
@@ -125,7 +125,7 @@ function flattenNode(node, projectId, rootId, position, result, documentId) {
|
|
|
125
125
|
// Get prefix/index from metadata (if from round-trip) or parse from node.id
|
|
126
126
|
let prefix;
|
|
127
127
|
let index;
|
|
128
|
-
if (node.metadata?.prefix && typeof node.metadata?.index ===
|
|
128
|
+
if (node.metadata?.prefix && typeof node.metadata?.index === "number") {
|
|
129
129
|
// Round-trip case: use stored prefix/index
|
|
130
130
|
prefix = node.metadata.prefix;
|
|
131
131
|
index = node.metadata.index;
|
|
@@ -181,7 +181,7 @@ export function buildMetadata(version = 1, document) {
|
|
|
181
181
|
* Useful for detecting duplicates or tracking which requirements exist.
|
|
182
182
|
*/
|
|
183
183
|
export function extractRequirementKeys(convexReqs) {
|
|
184
|
-
return new Set(convexReqs.map(r => constructKey(r.prefix, r.index)));
|
|
184
|
+
return new Set(convexReqs.map((r) => constructKey(r.prefix, r.index)));
|
|
185
185
|
}
|
|
186
186
|
/**
|
|
187
187
|
* Group Convex requirements by their root.
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* This module provides types, validation, parsing, and building utilities
|
|
5
5
|
* for the Markdown requirements format.
|
|
6
6
|
*/
|
|
7
|
-
export {
|
|
8
|
-
export type {
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export type {
|
|
13
|
-
export {
|
|
7
|
+
export { buildRequirementMarkdown, buildRequirementsFile, buildRequirementsMarkdown, } from "./builder.js";
|
|
8
|
+
export type { ConvexRequirement } from "./conversions.js";
|
|
9
|
+
export { buildMetadata, constructKey, convexToRequirements, extractRequirementKeys, groupByRoot, parseKey, requirementsToConvex, } from "./conversions.js";
|
|
10
|
+
export { findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, parseRequirementsFile, parseRequirementsFromFile, } from "./parser.js";
|
|
11
|
+
export { checkPathAmbiguity, findChildrenByLabel, getAllLabelPaths, parsePathSegment, parseRequirementPath, resolvePathSegment, resolveRequirementPath, resolveToNumericPath, } from "./resolver.js";
|
|
12
|
+
export type { BuildScenarioOptions, Scenario, ScenarioAssertionSource, ScenarioStep, } from "./scenario.js";
|
|
13
|
+
export { buildScenarioFromRequirements, requirementTreeToScenario, } from "./scenario.js";
|
|
14
|
+
export type { Metadata, ParsedCriterion, PushValidationResult, RequirementKey, RequirementNode, RequirementPrefix, RequirementsFile, } from "./schemas.js";
|
|
15
|
+
export { buildRequirementKey, CONVEX_ID_PATTERN, MetadataSchema, normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PATTERN, REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema, ValidationError, validateForPush, validateKey, validateMetadata, validatePrefix, validateRequirementNode, validateRequirementsFile, } from "./schemas.js";
|
|
14
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/schema/index.js
CHANGED
|
@@ -4,21 +4,23 @@
|
|
|
4
4
|
* This module provides types, validation, parsing, and building utilities
|
|
5
5
|
* for the Markdown requirements format.
|
|
6
6
|
*/
|
|
7
|
+
// Building
|
|
8
|
+
export { buildRequirementMarkdown, buildRequirementsFile, buildRequirementsMarkdown, } from "./builder.js";
|
|
9
|
+
export { buildMetadata, constructKey, convexToRequirements, extractRequirementKeys, groupByRoot, parseKey, requirementsToConvex, } from "./conversions.js";
|
|
10
|
+
// Parsing
|
|
11
|
+
export { findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, parseRequirementsFile, parseRequirementsFromFile, } from "./parser.js";
|
|
12
|
+
// Path resolution
|
|
13
|
+
export { checkPathAmbiguity, findChildrenByLabel, getAllLabelPaths, parsePathSegment, parseRequirementPath, resolvePathSegment, resolveRequirementPath, resolveToNumericPath, } from "./resolver.js";
|
|
14
|
+
// Scenario building (for browser-automation / runScenario)
|
|
15
|
+
export { buildScenarioFromRequirements, requirementTreeToScenario, } from "./scenario.js";
|
|
7
16
|
// Schemas and types
|
|
8
|
-
export {
|
|
17
|
+
export { buildRequirementKey, CONVEX_ID_PATTERN,
|
|
9
18
|
// Zod schemas
|
|
10
|
-
MetadataSchema,
|
|
19
|
+
MetadataSchema,
|
|
20
|
+
// Prefix/key utilities
|
|
21
|
+
normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PATTERN,
|
|
11
22
|
// Patterns (for external validation)
|
|
12
|
-
REQUIREMENT_PREFIX_PATTERN,
|
|
23
|
+
REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema,
|
|
13
24
|
// Validation
|
|
14
|
-
ValidationError,
|
|
15
|
-
// Prefix/key utilities
|
|
16
|
-
normalizePrefix, parseRequirementKey, buildRequirementKey, } from './schemas.js';
|
|
17
|
-
// Parsing
|
|
18
|
-
export { parseCriterionLine, parseRequirementBlock, parseRequirementsFile, parseRequirementsFromFile, flattenRequirementTree, findRequirementById, getAllRequirements, } from './parser.js';
|
|
19
|
-
// Building
|
|
20
|
-
export { buildRequirementsMarkdown, buildRequirementMarkdown, buildRequirementsFile, } from './builder.js';
|
|
21
|
-
// Path resolution
|
|
22
|
-
export { parseRequirementPath, parsePathSegment, findChildrenByLabel, resolvePathSegment, resolveRequirementPath, resolveToNumericPath, getAllLabelPaths, checkPathAmbiguity, } from './resolver.js';
|
|
23
|
-
export { convexToRequirements, requirementsToConvex, buildMetadata, extractRequirementKeys, groupByRoot, constructKey, parseKey, } from './conversions.js';
|
|
25
|
+
ValidationError, validateForPush, validateKey, validateMetadata, validatePrefix, validateRequirementNode, validateRequirementsFile, } from "./schemas.js";
|
|
24
26
|
//# sourceMappingURL=index.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This module contains pure parsing functions with no Node.js dependencies,
|
|
5
5
|
* making it safe to import in Convex runtime or browser environments.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { type ParsedCriterion, type RequirementNode } from "./schemas.js";
|
|
8
8
|
/**
|
|
9
9
|
* Default delimiter for requirements.
|
|
10
10
|
* Can be overridden for organization-specific preferences.
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* This module contains pure parsing functions with no Node.js dependencies,
|
|
5
5
|
* making it safe to import in Convex runtime or browser environments.
|
|
6
6
|
*/
|
|
7
|
-
import { ValidationError, } from
|
|
7
|
+
import { ValidationError, } from "./schemas.js";
|
|
8
8
|
/**
|
|
9
9
|
* Default delimiter for requirements.
|
|
10
10
|
* Can be overridden for organization-specific preferences.
|
|
11
11
|
*/
|
|
12
|
-
export const DEFAULT_DELIMITER =
|
|
13
|
-
export const DELIMITER_PATTERN =
|
|
12
|
+
export const DEFAULT_DELIMITER = "→";
|
|
13
|
+
export const DELIMITER_PATTERN = "(?:→|->)"; // Non-capturing group for both Unicode and ASCII
|
|
14
14
|
/**
|
|
15
15
|
* Parse a criterion line in "position. Label → content" format.
|
|
16
16
|
* Example: "0. Given → user has valid credentials"
|
|
@@ -27,7 +27,7 @@ export function parseCriterionLine(line, delimiter = DELIMITER_PATTERN) {
|
|
|
27
27
|
}
|
|
28
28
|
return {
|
|
29
29
|
position: match[1],
|
|
30
|
-
label: match[2] ? match[2].trim() :
|
|
30
|
+
label: match[2] ? match[2].trim() : "", // Preserve original case, empty if no label
|
|
31
31
|
content: match[3].trim(),
|
|
32
32
|
};
|
|
33
33
|
}
|
|
@@ -43,7 +43,7 @@ export function parseRootLine(line, delimiter = DELIMITER_PATTERN) {
|
|
|
43
43
|
if (keyMatch) {
|
|
44
44
|
return {
|
|
45
45
|
key: keyMatch[1],
|
|
46
|
-
label:
|
|
46
|
+
label: "", // Keys use empty string for unlabeled requirements
|
|
47
47
|
content: keyMatch[2].trim(),
|
|
48
48
|
};
|
|
49
49
|
}
|
|
@@ -53,12 +53,12 @@ export function parseRootLine(line, delimiter = DELIMITER_PATTERN) {
|
|
|
53
53
|
if (!match) {
|
|
54
54
|
// If no delimiter found, treat entire line as content with empty label
|
|
55
55
|
return {
|
|
56
|
-
label:
|
|
56
|
+
label: "",
|
|
57
57
|
content: trimmed,
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
return {
|
|
61
|
-
label: match[1] ? match[1].trim() :
|
|
61
|
+
label: match[1] ? match[1].trim() : "",
|
|
62
62
|
content: match[2].trim(),
|
|
63
63
|
};
|
|
64
64
|
}
|
|
@@ -68,20 +68,20 @@ export function parseRootLine(line, delimiter = DELIMITER_PATTERN) {
|
|
|
68
68
|
* Subsequent lines are criteria with position paths.
|
|
69
69
|
*/
|
|
70
70
|
export function parseRequirementBlock(key, blockContent) {
|
|
71
|
-
const lines = blockContent.trim().split(
|
|
71
|
+
const lines = blockContent.trim().split("\n");
|
|
72
72
|
if (lines.length === 0) {
|
|
73
|
-
throw new ValidationError(
|
|
73
|
+
throw new ValidationError("Empty requirement block", key);
|
|
74
74
|
}
|
|
75
75
|
// First line is the requirement content
|
|
76
76
|
const rootParsed = parseRootLine(lines[0]);
|
|
77
77
|
if (!rootParsed.content) {
|
|
78
|
-
throw new ValidationError(
|
|
78
|
+
throw new ValidationError("Requirement content (first line) cannot be empty", key);
|
|
79
79
|
}
|
|
80
80
|
const root = {
|
|
81
81
|
id: key,
|
|
82
82
|
// Root requirements always use "requirementHeader" label for indexing/querying
|
|
83
83
|
// The markdown format doesn't preserve root labels, but we standardize on this value
|
|
84
|
-
label:
|
|
84
|
+
label: "requirementHeader",
|
|
85
85
|
content: rootParsed.content,
|
|
86
86
|
children: [],
|
|
87
87
|
};
|
|
@@ -90,7 +90,7 @@ export function parseRequirementBlock(key, blockContent) {
|
|
|
90
90
|
for (let i = 1; i < lines.length; i++) {
|
|
91
91
|
const line = lines[i];
|
|
92
92
|
// Skip empty lines and comments
|
|
93
|
-
if (!line.trim() || line.trim().startsWith(
|
|
93
|
+
if (!line.trim() || line.trim().startsWith("#")) {
|
|
94
94
|
continue;
|
|
95
95
|
}
|
|
96
96
|
const criterion = parseCriterionLine(line);
|
|
@@ -98,7 +98,7 @@ export function parseRequirementBlock(key, blockContent) {
|
|
|
98
98
|
// Check if this looks like another requirement key (KEY: content format)
|
|
99
99
|
const trimmedLine = line.trim();
|
|
100
100
|
if (/^[\w-]+:\s*.+/.test(trimmedLine)) {
|
|
101
|
-
throw new ValidationError(`Multiple requirements in single block. Found "${trimmedLine.split(
|
|
101
|
+
throw new ValidationError(`Multiple requirements in single block. Found "${trimmedLine.split(":")[0]}" at line ${i + 1}, but each requirement must have its own \`\`\`dotrequirements code block.`, key);
|
|
102
102
|
}
|
|
103
103
|
throw new ValidationError(`Invalid criterion format at line ${i + 1}: "${trimmedLine}". Expected format: "N. Label → content" (e.g., "0. Given → user is logged in")`, key);
|
|
104
104
|
}
|
|
@@ -115,8 +115,8 @@ export function parseRequirementBlock(key, blockContent) {
|
|
|
115
115
|
function buildTreeFromPositions(root, rootKey, criteriaByPosition) {
|
|
116
116
|
// Get all top-level children (single digit positions: "0", "1", "2")
|
|
117
117
|
const topLevelPositions = Array.from(criteriaByPosition.keys())
|
|
118
|
-
.filter(pos => !pos.includes(
|
|
119
|
-
.sort((a, b) => parseInt(a) - parseInt(b));
|
|
118
|
+
.filter((pos) => !pos.includes("."))
|
|
119
|
+
.sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
|
|
120
120
|
for (const position of topLevelPositions) {
|
|
121
121
|
const criterion = criteriaByPosition.get(position);
|
|
122
122
|
const childNode = {
|
|
@@ -136,9 +136,9 @@ function buildTreeFromPositions(root, rootKey, criteriaByPosition) {
|
|
|
136
136
|
function buildChildrenFromPositions(parent, parentPosition, rootKey, criteriaByPosition) {
|
|
137
137
|
// Find direct children (e.g., if parent is "1", find "1.0", "1.1", etc.)
|
|
138
138
|
const childPositions = Array.from(criteriaByPosition.keys())
|
|
139
|
-
.filter(pos => {
|
|
140
|
-
const parts = pos.split(
|
|
141
|
-
const parentParts = parentPosition.split(
|
|
139
|
+
.filter((pos) => {
|
|
140
|
+
const parts = pos.split(".");
|
|
141
|
+
const parentParts = parentPosition.split(".");
|
|
142
142
|
// Must be exactly one level deeper
|
|
143
143
|
if (parts.length !== parentParts.length + 1) {
|
|
144
144
|
return false;
|
|
@@ -152,8 +152,8 @@ function buildChildrenFromPositions(parent, parentPosition, rootKey, criteriaByP
|
|
|
152
152
|
return true;
|
|
153
153
|
})
|
|
154
154
|
.sort((a, b) => {
|
|
155
|
-
const aLast = parseInt(a.split(
|
|
156
|
-
const bLast = parseInt(b.split(
|
|
155
|
+
const aLast = parseInt(a.split(".").pop(), 10);
|
|
156
|
+
const bLast = parseInt(b.split(".").pop(), 10);
|
|
157
157
|
return aLast - bLast;
|
|
158
158
|
});
|
|
159
159
|
for (const position of childPositions) {
|
|
@@ -177,8 +177,8 @@ export function extractRequirementBlocks(body) {
|
|
|
177
177
|
const blocks = [];
|
|
178
178
|
// Find all ```dotrequirements code blocks
|
|
179
179
|
const blockRegex = /```dotrequirements\n([\s\S]*?)```/gm;
|
|
180
|
-
let match;
|
|
181
|
-
while (
|
|
180
|
+
let match = blockRegex.exec(body);
|
|
181
|
+
while (match !== null) {
|
|
182
182
|
const blockContent = match[1];
|
|
183
183
|
// Extract key from first line of block (format: "KEY: content")
|
|
184
184
|
const firstLineMatch = blockContent.match(/^([\w-]+):\s*(.+)/);
|
|
@@ -187,6 +187,7 @@ export function extractRequirementBlocks(body) {
|
|
|
187
187
|
}
|
|
188
188
|
const key = firstLineMatch[1];
|
|
189
189
|
blocks.push({ key, blockContent: blockContent.trim() });
|
|
190
|
+
match = blockRegex.exec(body);
|
|
190
191
|
}
|
|
191
192
|
return blocks;
|
|
192
193
|
}
|