@popoverai/dotrequirements 0.11.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.
Files changed (127) hide show
  1. package/README.md +478 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.js +82 -0
  4. package/dist/commands/init.d.ts +6 -0
  5. package/dist/commands/init.js +355 -0
  6. package/dist/commands/link.d.ts +15 -0
  7. package/dist/commands/link.js +156 -0
  8. package/dist/commands/login.d.ts +12 -0
  9. package/dist/commands/login.js +117 -0
  10. package/dist/commands/logout.d.ts +5 -0
  11. package/dist/commands/logout.js +17 -0
  12. package/dist/commands/mcp-setup.d.ts +5 -0
  13. package/dist/commands/mcp-setup.js +367 -0
  14. package/dist/commands/mcp.d.ts +6 -0
  15. package/dist/commands/mcp.js +10 -0
  16. package/dist/commands/pull.d.ts +7 -0
  17. package/dist/commands/pull.js +171 -0
  18. package/dist/commands/push.d.ts +11 -0
  19. package/dist/commands/push.js +310 -0
  20. package/dist/commands/test.d.ts +6 -0
  21. package/dist/commands/test.js +78 -0
  22. package/dist/config.d.ts +5 -0
  23. package/dist/config.js +16 -0
  24. package/dist/convex.d.ts +56 -0
  25. package/dist/convex.js +58 -0
  26. package/dist/harness/cache.d.ts +135 -0
  27. package/dist/harness/cache.js +342 -0
  28. package/dist/harness/convexReporting.d.ts +15 -0
  29. package/dist/harness/convexReporting.js +136 -0
  30. package/dist/harness/coverageCache.d.ts +30 -0
  31. package/dist/harness/coverageCache.js +70 -0
  32. package/dist/harness/finalize.d.ts +48 -0
  33. package/dist/harness/finalize.js +299 -0
  34. package/dist/harness/index.d.ts +70 -0
  35. package/dist/harness/index.js +103 -0
  36. package/dist/harness/localReporting.d.ts +6 -0
  37. package/dist/harness/localReporting.js +49 -0
  38. package/dist/harness/prepare.d.ts +41 -0
  39. package/dist/harness/prepare.js +83 -0
  40. package/dist/harness/requirementsLoader.d.ts +45 -0
  41. package/dist/harness/requirementsLoader.js +201 -0
  42. package/dist/harness/tracking.d.ts +49 -0
  43. package/dist/harness/tracking.js +179 -0
  44. package/dist/harness/types.d.ts +12 -0
  45. package/dist/harness/types.js +6 -0
  46. package/dist/mcp/convexClient.d.ts +43 -0
  47. package/dist/mcp/convexClient.js +101 -0
  48. package/dist/mcp/grep.d.ts +24 -0
  49. package/dist/mcp/grep.js +261 -0
  50. package/dist/mcp/index.d.ts +3 -0
  51. package/dist/mcp/index.js +1758 -0
  52. package/dist/mcp/requirements.d.ts +47 -0
  53. package/dist/mcp/requirements.js +141 -0
  54. package/dist/mcp/testCodeExtractor.d.ts +22 -0
  55. package/dist/mcp/testCodeExtractor.js +152 -0
  56. package/dist/mcp/types.d.ts +27 -0
  57. package/dist/mcp/types.js +2 -0
  58. package/dist/schema/browser.d.ts +12 -0
  59. package/dist/schema/browser.js +24 -0
  60. package/dist/schema/builder.d.ts +25 -0
  61. package/dist/schema/builder.js +125 -0
  62. package/dist/schema/conversions.d.ts +69 -0
  63. package/dist/schema/conversions.js +201 -0
  64. package/dist/schema/index.d.ts +14 -0
  65. package/dist/schema/index.js +24 -0
  66. package/dist/schema/parser-core.d.ts +61 -0
  67. package/dist/schema/parser-core.js +247 -0
  68. package/dist/schema/parser.d.ts +44 -0
  69. package/dist/schema/parser.js +295 -0
  70. package/dist/schema/resolver.d.ts +66 -0
  71. package/dist/schema/resolver.js +185 -0
  72. package/dist/schema/schemas.d.ts +312 -0
  73. package/dist/schema/schemas.js +258 -0
  74. package/dist/schema/test-schema.d.ts +5 -0
  75. package/dist/schema/test-schema.js +81 -0
  76. package/dist/templates/antigravity-gemini.md +3 -0
  77. package/dist/templates/antigravity-overview-rule.md +3 -0
  78. package/dist/templates/antigravity-test-rule.md +3 -0
  79. package/dist/templates/behavioral-core.md +25 -0
  80. package/dist/templates/claude-code-overview-skill.md +6 -0
  81. package/dist/templates/claude-code-skill.md +6 -0
  82. package/dist/templates/claude-code-test-skill.md +6 -0
  83. package/dist/templates/codex-agents.md +3 -0
  84. package/dist/templates/codex-overview-agents.md +3 -0
  85. package/dist/templates/codex-test-agents.md +3 -0
  86. package/dist/templates/cursor-overview-rule.mdc +5 -0
  87. package/dist/templates/cursor-rule.mdc +5 -0
  88. package/dist/templates/cursor-test-rule.mdc +5 -0
  89. package/dist/templates/example-requirements.d.ts +8 -0
  90. package/dist/templates/example-requirements.js +88 -0
  91. package/dist/templates/example-requirements.ts +88 -0
  92. package/dist/templates/overview-core.md +27 -0
  93. package/dist/templates/requirements-readme.d.ts +5 -0
  94. package/dist/templates/requirements-readme.js +31 -0
  95. package/dist/templates/requirements-readme.ts +30 -0
  96. package/dist/templates/test-writing-core.md +72 -0
  97. package/dist/utils/brand.d.ts +5 -0
  98. package/dist/utils/brand.js +8 -0
  99. package/dist/utils/browser-launch.d.ts +19 -0
  100. package/dist/utils/browser-launch.js +36 -0
  101. package/dist/utils/detect-existing-project.d.ts +5 -0
  102. package/dist/utils/detect-existing-project.js +34 -0
  103. package/dist/utils/env.d.ts +19 -0
  104. package/dist/utils/env.js +56 -0
  105. package/dist/utils/gitignore.d.ts +7 -0
  106. package/dist/utils/gitignore.js +29 -0
  107. package/dist/utils/local-project.d.ts +31 -0
  108. package/dist/utils/local-project.js +33 -0
  109. package/dist/utils/oauth-callback-server.d.ts +28 -0
  110. package/dist/utils/oauth-callback-server.js +156 -0
  111. package/dist/utils/oauth-flow.d.ts +22 -0
  112. package/dist/utils/oauth-flow.js +120 -0
  113. package/dist/utils/project-discovery.d.ts +57 -0
  114. package/dist/utils/project-discovery.js +146 -0
  115. package/dist/utils/project-name.d.ts +8 -0
  116. package/dist/utils/project-name.js +48 -0
  117. package/dist/utils/project-selector.d.ts +25 -0
  118. package/dist/utils/project-selector.js +69 -0
  119. package/dist/utils/prompts.d.ts +33 -0
  120. package/dist/utils/prompts.js +60 -0
  121. package/dist/utils/templates.d.ts +29 -0
  122. package/dist/utils/templates.js +67 -0
  123. package/dist/utils/token-refresh.d.ts +24 -0
  124. package/dist/utils/token-refresh.js +69 -0
  125. package/dist/utils/token-storage.d.ts +31 -0
  126. package/dist/utils/token-storage.js +57 -0
  127. package/package.json +82 -0
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Convert between Convex flat representation and hierarchical RequirementNode trees.
3
+ */
4
+ /**
5
+ * Construct a requirement key from prefix and index.
6
+ * E.g., ("REQ", 123) -> "REQ-123"
7
+ */
8
+ export function constructKey(prefix, index) {
9
+ return `${prefix}-${index}`;
10
+ }
11
+ /**
12
+ * Parse a requirement key into prefix and index.
13
+ * E.g., "REQ-123" -> { prefix: "REQ", index: 123 }
14
+ * Returns null if key doesn't match expected format.
15
+ */
16
+ export function parseKey(key) {
17
+ const match = key.match(/^([A-Z0-9_]+)-(\d+)$/);
18
+ if (!match)
19
+ return null;
20
+ return { prefix: match[1], index: parseInt(match[2], 10) };
21
+ }
22
+ /**
23
+ * Build a hierarchical tree from flat Convex requirements.
24
+ * Uses position paths to reconstruct parent-child relationships.
25
+ */
26
+ export function convexToRequirements(convexReqs) {
27
+ // Separate roots from children
28
+ const roots = convexReqs.filter(r => !r.rootId);
29
+ // Build a map of all requirements by their position path for each root
30
+ const reqsByRoot = new Map();
31
+ for (const req of convexReqs) {
32
+ if (req.rootId) {
33
+ let rootMap = reqsByRoot.get(req.rootId);
34
+ if (!rootMap) {
35
+ rootMap = new Map();
36
+ reqsByRoot.set(req.rootId, rootMap);
37
+ }
38
+ rootMap.set(req.position || '', req);
39
+ }
40
+ }
41
+ // Build trees for each root
42
+ return roots.map(root => buildTreeFromPositions(root, reqsByRoot.get(root._id)));
43
+ }
44
+ /**
45
+ * Recursively build a RequirementNode tree from position-based flat structure.
46
+ */
47
+ function buildTreeFromPositions(convexReq, positionMap) {
48
+ // Derive the key from prefix and index
49
+ const key = constructKey(convexReq.prefix, convexReq.index);
50
+ const node = {
51
+ id: key,
52
+ label: convexReq.label,
53
+ content: convexReq.content,
54
+ children: [],
55
+ metadata: {
56
+ convexId: convexReq._id,
57
+ rootId: convexReq.rootId,
58
+ position: convexReq.position,
59
+ updatedAt: convexReq.updatedAt,
60
+ externalLinks: convexReq.externalLinks,
61
+ // Store prefix/index for round-trip
62
+ prefix: convexReq.prefix,
63
+ index: convexReq.index,
64
+ documentId: convexReq.documentId,
65
+ },
66
+ };
67
+ if (!positionMap) {
68
+ return node;
69
+ }
70
+ // Find direct children based on position paths
71
+ // If this is a root (no position), find children at positions like ".0", ".1"
72
+ // If this has position ".0", find children at positions like ".0.0", ".0.1"
73
+ const currentPosition = convexReq.position || '';
74
+ const childPositionPattern = currentPosition
75
+ ? new RegExp(`^${currentPosition.replace(/\./g, '\\.')}\\.\\d+$`)
76
+ : /^\.\d+$/;
77
+ const children = [];
78
+ for (const [pos, req] of positionMap.entries()) {
79
+ if (childPositionPattern.test(pos)) {
80
+ children.push(req);
81
+ }
82
+ }
83
+ // Sort children by position
84
+ const sortedChildren = children.sort((a, b) => {
85
+ return comparePositions(a.position || '', b.position || '');
86
+ });
87
+ // Recursively build child nodes
88
+ node.children = sortedChildren.map(child => buildTreeFromPositions(child, positionMap));
89
+ return node;
90
+ }
91
+ /**
92
+ * Compare position strings for sorting.
93
+ * Positions are like ".0", ".1", ".0.0", ".0.1"
94
+ */
95
+ function comparePositions(a, b) {
96
+ const aParts = a.split('.').filter(Boolean).map(Number);
97
+ const bParts = b.split('.').filter(Boolean).map(Number);
98
+ for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
99
+ const aVal = aParts[i] ?? -1;
100
+ const bVal = bParts[i] ?? -1;
101
+ if (aVal !== bVal)
102
+ return aVal - bVal;
103
+ }
104
+ return 0;
105
+ }
106
+ /**
107
+ * Flatten a hierarchical structure back to Convex flat format.
108
+ * Generates position paths for each node.
109
+ */
110
+ export function requirementsToConvex(nodes, projectId, documentId) {
111
+ const result = [];
112
+ for (const node of nodes) {
113
+ flattenNode(node, projectId, undefined, undefined, result, documentId);
114
+ }
115
+ return result;
116
+ }
117
+ /**
118
+ * Recursively flatten a RequirementNode into Convex format.
119
+ */
120
+ function flattenNode(node, projectId, rootId, position, result, documentId) {
121
+ // Determine if this is a root or child
122
+ const isRoot = !rootId && !position;
123
+ // Generate convex ID from metadata or create placeholder
124
+ const convexId = node.metadata?.convexId || `temp_${node.id}`;
125
+ // Get prefix/index from metadata (if from round-trip) or parse from node.id
126
+ let prefix;
127
+ let index;
128
+ if (node.metadata?.prefix && typeof node.metadata?.index === 'number') {
129
+ // Round-trip case: use stored prefix/index
130
+ prefix = node.metadata.prefix;
131
+ index = node.metadata.index;
132
+ }
133
+ else {
134
+ // Parse from node.id (e.g., "REQ-123" -> { prefix: "REQ", index: 123 })
135
+ const parsed = parseKey(node.id);
136
+ if (parsed) {
137
+ prefix = parsed.prefix;
138
+ index = parsed.index;
139
+ }
140
+ else {
141
+ // Fallback: use node.id as prefix with index 0 (shouldn't happen with valid keys)
142
+ prefix = node.id;
143
+ index = 0;
144
+ }
145
+ }
146
+ const convexReq = {
147
+ _id: convexId,
148
+ prefix,
149
+ index,
150
+ documentId,
151
+ label: node.label,
152
+ content: node.content,
153
+ projectId,
154
+ rootId: isRoot ? undefined : rootId,
155
+ position: isRoot ? undefined : position,
156
+ metadata: node.metadata,
157
+ externalLinks: node.metadata?.externalLinks,
158
+ updatedAt: node.metadata?.updatedAt || Date.now(),
159
+ };
160
+ result.push(convexReq);
161
+ // Process children
162
+ node.children.forEach((child, childIdx) => {
163
+ const childPosition = position ? `${position}.${childIdx}` : `.${childIdx}`;
164
+ const childRootId = isRoot ? convexId : rootId;
165
+ flattenNode(child, projectId, childRootId, childPosition, result, documentId);
166
+ });
167
+ }
168
+ /**
169
+ * Build metadata from project info and pull time.
170
+ * DOC-HEADER-11.3: When pulling, include defaultPrefix in the frontmatter.
171
+ */
172
+ export function buildMetadata(projectId, version = 1, document) {
173
+ return {
174
+ projectId,
175
+ pulledAt: new Date().toISOString(),
176
+ version,
177
+ document,
178
+ };
179
+ }
180
+ /**
181
+ * Extract unique requirement keys from Convex data.
182
+ * Useful for detecting duplicates or tracking which requirements exist.
183
+ */
184
+ export function extractRequirementKeys(convexReqs) {
185
+ return new Set(convexReqs.map(r => constructKey(r.prefix, r.index)));
186
+ }
187
+ /**
188
+ * Group Convex requirements by their root.
189
+ * Returns a map of rootId → children.
190
+ */
191
+ export function groupByRoot(convexReqs) {
192
+ const groups = new Map();
193
+ for (const req of convexReqs) {
194
+ const key = req.rootId || undefined;
195
+ const group = groups.get(key) || [];
196
+ group.push(req);
197
+ groups.set(key, group);
198
+ }
199
+ return groups;
200
+ }
201
+ //# sourceMappingURL=conversions.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Markdown Requirements Schema
3
+ *
4
+ * This module provides types, validation, parsing, and building utilities
5
+ * for the Markdown requirements format.
6
+ */
7
+ export { MetadataSchema, RequirementNodeSchema, RequirementsFileSchema, ParsedCriterionSchema, RequirementPrefixSchema, RequirementKeySchema, REQUIREMENT_PREFIX_PATTERN, REQUIREMENT_KEY_PATTERN, ValidationError, validateMetadata, validateRequirementNode, validateRequirementsFile, validatePrefix, validateKey, validateForPush, CONVEX_ID_PATTERN, normalizePrefix, parseRequirementKey, buildRequirementKey, } from './schemas.js';
8
+ export type { Metadata, RequirementNode, RequirementsFile, ParsedCriterion, RequirementPrefix, RequirementKey, PushValidationResult, } from './schemas.js';
9
+ export { parseCriterionLine, parseRequirementBlock, parseRequirementsFile, parseRequirementsFromFile, flattenRequirementTree, findRequirementById, getAllRequirements, } from './parser.js';
10
+ export { buildRequirementsMarkdown, buildRequirementMarkdown, buildRequirementsFile, } from './builder.js';
11
+ export { parseRequirementPath, parsePathSegment, findChildrenByLabel, resolvePathSegment, resolveRequirementPath, resolveToNumericPath, getAllLabelPaths, checkPathAmbiguity, } from './resolver.js';
12
+ export type { ConvexRequirement, } from './conversions.js';
13
+ export { convexToRequirements, requirementsToConvex, buildMetadata, extractRequirementKeys, groupByRoot, constructKey, parseKey, } from './conversions.js';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Markdown Requirements Schema
3
+ *
4
+ * This module provides types, validation, parsing, and building utilities
5
+ * for the Markdown requirements format.
6
+ */
7
+ // Schemas and types
8
+ export {
9
+ // Zod schemas
10
+ MetadataSchema, RequirementNodeSchema, RequirementsFileSchema, ParsedCriterionSchema, RequirementPrefixSchema, RequirementKeySchema,
11
+ // Patterns (for external validation)
12
+ REQUIREMENT_PREFIX_PATTERN, REQUIREMENT_KEY_PATTERN,
13
+ // Validation
14
+ ValidationError, validateMetadata, validateRequirementNode, validateRequirementsFile, validatePrefix, validateKey, validateForPush, CONVEX_ID_PATTERN,
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';
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Core parsing functions for dotrequirements format.
3
+ *
4
+ * This module contains pure parsing functions with no Node.js dependencies,
5
+ * making it safe to import in Convex runtime or browser environments.
6
+ */
7
+ import { RequirementNode, ParsedCriterion } from './schemas.js';
8
+ /**
9
+ * Default delimiter for requirements.
10
+ * Can be overridden for organization-specific preferences.
11
+ */
12
+ export declare const DEFAULT_DELIMITER = "\u2192";
13
+ export declare const DELIMITER_PATTERN = "(?:\u2192|->)";
14
+ /**
15
+ * Parse a criterion line in "position. Label → content" format.
16
+ * Example: "0. Given → user has valid credentials"
17
+ * Also supports optional label: "0. → user has valid credentials"
18
+ */
19
+ export declare function parseCriterionLine(line: string, delimiter?: string): ParsedCriterion | null;
20
+ /**
21
+ * Parse the first line of a dotrequirements block (the root requirement).
22
+ * Format: "KEY: content" or "Label → content" or "→ content"
23
+ */
24
+ export declare function parseRootLine(line: string, delimiter?: string): {
25
+ key?: string;
26
+ label: string;
27
+ content: string;
28
+ };
29
+ /**
30
+ * Parse a dotrequirements fenced block into a RequirementNode tree.
31
+ * First line is the requirement content (with optional arrow format).
32
+ * Subsequent lines are criteria with position paths.
33
+ */
34
+ export declare function parseRequirementBlock(key: string, blockContent: string): RequirementNode;
35
+ /**
36
+ * Extract requirement blocks from Markdown body (no frontmatter).
37
+ * Returns an array of { key, blockContent } for each dotrequirements block found.
38
+ */
39
+ export declare function extractRequirementBlocks(body: string): Array<{
40
+ key: string;
41
+ blockContent: string;
42
+ }>;
43
+ /**
44
+ * Parse requirement blocks from markdown content (without frontmatter).
45
+ * Use this for web editor content that doesn't have YAML frontmatter.
46
+ */
47
+ export declare function parseRequirementBlocksFromMarkdown(markdownContent: string): RequirementNode[];
48
+ /**
49
+ * Flatten a requirement tree into a list of all nodes.
50
+ * Useful for searching or displaying all requirements.
51
+ */
52
+ export declare function flattenRequirementTree(node: RequirementNode): RequirementNode[];
53
+ /**
54
+ * Find a requirement by its ID in a tree.
55
+ */
56
+ export declare function findRequirementById(nodes: RequirementNode[], id: string): RequirementNode | undefined;
57
+ /**
58
+ * Get all requirements from multiple files as a flat list.
59
+ */
60
+ export declare function getAllRequirements(requirements: RequirementNode[]): RequirementNode[];
61
+ //# sourceMappingURL=parser-core.d.ts.map
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Core parsing functions for dotrequirements format.
3
+ *
4
+ * This module contains pure parsing functions with no Node.js dependencies,
5
+ * making it safe to import in Convex runtime or browser environments.
6
+ */
7
+ import { ValidationError, } from './schemas.js';
8
+ /**
9
+ * Default delimiter for requirements.
10
+ * Can be overridden for organization-specific preferences.
11
+ */
12
+ export const DEFAULT_DELIMITER = '→';
13
+ export const DELIMITER_PATTERN = '(?:→|->)'; // Non-capturing group for both Unicode and ASCII
14
+ /**
15
+ * Parse a criterion line in "position. Label → content" format.
16
+ * Example: "0. Given → user has valid credentials"
17
+ * Also supports optional label: "0. → user has valid credentials"
18
+ */
19
+ export function parseCriterionLine(line, delimiter = DELIMITER_PATTERN) {
20
+ // Match: position + period + space + optional(label + space) + delimiter + space + content
21
+ // Position can be: 0, 1.0, 2.3.1, etc.
22
+ // Label can contain any characters except the delimiter
23
+ const pattern = new RegExp(`^\\s*(\\d+(?:\\.\\d+)*)\\.\\s*(?:(.+?)\\s+)?${delimiter}\\s*(.+)$`);
24
+ const match = line.match(pattern);
25
+ if (!match) {
26
+ return null;
27
+ }
28
+ return {
29
+ position: match[1],
30
+ label: match[2] ? match[2].trim() : '', // Preserve original case, empty if no label
31
+ content: match[3].trim(),
32
+ };
33
+ }
34
+ /**
35
+ * Parse the first line of a dotrequirements block (the root requirement).
36
+ * Format: "KEY: content" or "Label → content" or "→ content"
37
+ */
38
+ export function parseRootLine(line, delimiter = DELIMITER_PATTERN) {
39
+ const trimmed = line.trim();
40
+ // Try to match: KEY + colon + content (explicit key format)
41
+ // KEY must be followed by colon (not arrow) to distinguish from "Label → content"
42
+ const keyMatch = trimmed.match(/^([\w-]+):\s*(.+)$/);
43
+ if (keyMatch) {
44
+ return {
45
+ key: keyMatch[1],
46
+ label: '', // Keys use empty string for unlabeled requirements
47
+ content: keyMatch[2].trim(),
48
+ };
49
+ }
50
+ // Match: optional(label + space) + delimiter + space + content
51
+ const pattern = new RegExp(`^(?:(.+?)\\s+)?${delimiter}\\s*(.+)$`);
52
+ const match = trimmed.match(pattern);
53
+ if (!match) {
54
+ // If no delimiter found, treat entire line as content with empty label
55
+ return {
56
+ label: '',
57
+ content: trimmed,
58
+ };
59
+ }
60
+ return {
61
+ label: match[1] ? match[1].trim() : '',
62
+ content: match[2].trim(),
63
+ };
64
+ }
65
+ /**
66
+ * Parse a dotrequirements fenced block into a RequirementNode tree.
67
+ * First line is the requirement content (with optional arrow format).
68
+ * Subsequent lines are criteria with position paths.
69
+ */
70
+ export function parseRequirementBlock(key, blockContent) {
71
+ const lines = blockContent.trim().split('\n');
72
+ if (lines.length === 0) {
73
+ throw new ValidationError('Empty requirement block', key);
74
+ }
75
+ // First line is the requirement content
76
+ const rootParsed = parseRootLine(lines[0]);
77
+ if (!rootParsed.content) {
78
+ throw new ValidationError('Requirement content (first line) cannot be empty', key);
79
+ }
80
+ const root = {
81
+ id: key,
82
+ // Root requirements always use "requirementHeader" label for indexing/querying
83
+ // The markdown format doesn't preserve root labels, but we standardize on this value
84
+ label: 'requirementHeader',
85
+ content: rootParsed.content,
86
+ children: [],
87
+ };
88
+ // Parse criteria lines
89
+ const criteriaByPosition = new Map();
90
+ for (let i = 1; i < lines.length; i++) {
91
+ const line = lines[i];
92
+ // Skip empty lines and comments
93
+ if (!line.trim() || line.trim().startsWith('#')) {
94
+ continue;
95
+ }
96
+ const criterion = parseCriterionLine(line);
97
+ if (!criterion) {
98
+ // Check if this looks like another requirement key (KEY: content format)
99
+ const trimmedLine = line.trim();
100
+ if (/^[\w-]+:\s*.+/.test(trimmedLine)) {
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
+ }
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
+ }
105
+ criteriaByPosition.set(criterion.position, criterion);
106
+ }
107
+ // Build tree from flat position paths
108
+ buildTreeFromPositions(root, key, criteriaByPosition);
109
+ return root;
110
+ }
111
+ /**
112
+ * Build a tree structure from flat position paths.
113
+ * Positions like "0", "1", "1.0", "1.1", "1.1.0" define the hierarchy.
114
+ */
115
+ function buildTreeFromPositions(root, rootKey, criteriaByPosition) {
116
+ // Get all top-level children (single digit positions: "0", "1", "2")
117
+ const topLevelPositions = Array.from(criteriaByPosition.keys())
118
+ .filter(pos => !pos.includes('.'))
119
+ .sort((a, b) => parseInt(a) - parseInt(b));
120
+ for (const position of topLevelPositions) {
121
+ const criterion = criteriaByPosition.get(position);
122
+ const childNode = {
123
+ id: `${rootKey}.${position}`,
124
+ label: criterion.label,
125
+ content: criterion.content,
126
+ children: [],
127
+ };
128
+ // Recursively build children
129
+ buildChildrenFromPositions(childNode, position, rootKey, criteriaByPosition);
130
+ root.children.push(childNode);
131
+ }
132
+ }
133
+ /**
134
+ * Recursively build children for a given position.
135
+ */
136
+ function buildChildrenFromPositions(parent, parentPosition, rootKey, criteriaByPosition) {
137
+ // Find direct children (e.g., if parent is "1", find "1.0", "1.1", etc.)
138
+ const childPositions = Array.from(criteriaByPosition.keys())
139
+ .filter(pos => {
140
+ const parts = pos.split('.');
141
+ const parentParts = parentPosition.split('.');
142
+ // Must be exactly one level deeper
143
+ if (parts.length !== parentParts.length + 1) {
144
+ return false;
145
+ }
146
+ // All parent parts must match
147
+ for (let i = 0; i < parentParts.length; i++) {
148
+ if (parts[i] !== parentParts[i]) {
149
+ return false;
150
+ }
151
+ }
152
+ return true;
153
+ })
154
+ .sort((a, b) => {
155
+ const aLast = parseInt(a.split('.').pop());
156
+ const bLast = parseInt(b.split('.').pop());
157
+ return aLast - bLast;
158
+ });
159
+ for (const position of childPositions) {
160
+ const criterion = criteriaByPosition.get(position);
161
+ const childNode = {
162
+ id: `${rootKey}.${position}`,
163
+ label: criterion.label,
164
+ content: criterion.content,
165
+ children: [],
166
+ };
167
+ // Recursively build grandchildren
168
+ buildChildrenFromPositions(childNode, position, rootKey, criteriaByPosition);
169
+ parent.children.push(childNode);
170
+ }
171
+ }
172
+ /**
173
+ * Extract requirement blocks from Markdown body (no frontmatter).
174
+ * Returns an array of { key, blockContent } for each dotrequirements block found.
175
+ */
176
+ export function extractRequirementBlocks(body) {
177
+ const blocks = [];
178
+ // Find all ```dotrequirements code blocks
179
+ const blockRegex = /```dotrequirements\n([\s\S]*?)```/gm;
180
+ let match;
181
+ while ((match = blockRegex.exec(body)) !== null) {
182
+ const blockContent = match[1];
183
+ // Extract key from first line of block (format: "KEY: content")
184
+ const firstLineMatch = blockContent.match(/^([\w-]+):\s*(.+)/);
185
+ if (!firstLineMatch) {
186
+ throw new ValidationError(`Invalid requirement block format - first line must be "KEY: content"`, blockContent.substring(0, 50));
187
+ }
188
+ const key = firstLineMatch[1];
189
+ blocks.push({ key, blockContent: blockContent.trim() });
190
+ }
191
+ return blocks;
192
+ }
193
+ /**
194
+ * Parse requirement blocks from markdown content (without frontmatter).
195
+ * Use this for web editor content that doesn't have YAML frontmatter.
196
+ */
197
+ export function parseRequirementBlocksFromMarkdown(markdownContent) {
198
+ const blocks = extractRequirementBlocks(markdownContent);
199
+ const requirements = [];
200
+ for (const { key, blockContent } of blocks) {
201
+ try {
202
+ const reqNode = parseRequirementBlock(key, blockContent);
203
+ requirements.push(reqNode);
204
+ }
205
+ catch (error) {
206
+ throw new ValidationError(`Failed to parse requirement ${key}`, key, error);
207
+ }
208
+ }
209
+ return requirements;
210
+ }
211
+ /**
212
+ * Flatten a requirement tree into a list of all nodes.
213
+ * Useful for searching or displaying all requirements.
214
+ */
215
+ export function flattenRequirementTree(node) {
216
+ const result = [node];
217
+ for (const child of node.children) {
218
+ result.push(...flattenRequirementTree(child));
219
+ }
220
+ return result;
221
+ }
222
+ /**
223
+ * Find a requirement by its ID in a tree.
224
+ */
225
+ export function findRequirementById(nodes, id) {
226
+ for (const node of nodes) {
227
+ if (node.id === id) {
228
+ return node;
229
+ }
230
+ const found = findRequirementById(node.children, id);
231
+ if (found) {
232
+ return found;
233
+ }
234
+ }
235
+ return undefined;
236
+ }
237
+ /**
238
+ * Get all requirements from multiple files as a flat list.
239
+ */
240
+ export function getAllRequirements(requirements) {
241
+ const all = [];
242
+ for (const req of requirements) {
243
+ all.push(...flattenRequirementTree(req));
244
+ }
245
+ return all;
246
+ }
247
+ //# sourceMappingURL=parser-core.js.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Parse Markdown requirements files into typed structures.
3
+ */
4
+ import { RequirementNode, RequirementsFile, ParsedCriterion } from './schemas.js';
5
+ /**
6
+ * Parse a criterion line in "position. Label → content" format.
7
+ * Example: "0. Given → user has valid credentials"
8
+ * Also supports optional label: "0. → user has valid credentials"
9
+ */
10
+ export declare function parseCriterionLine(line: string, delimiter?: string): ParsedCriterion | null;
11
+ /**
12
+ * Parse a dotrequirements fenced block into a RequirementNode tree.
13
+ * First line is the requirement content (with optional arrow format).
14
+ * Subsequent lines are criteria with position paths.
15
+ */
16
+ export declare function parseRequirementBlock(key: string, blockContent: string): RequirementNode;
17
+ /**
18
+ * Parse a complete requirements Markdown file.
19
+ */
20
+ export declare function parseRequirementsFile(markdownContent: string): {
21
+ metadata: RequirementsFile['_meta'];
22
+ requirements: RequirementNode[];
23
+ };
24
+ /**
25
+ * Parse requirements file from disk.
26
+ */
27
+ export declare function parseRequirementsFromFile(filePath: string): {
28
+ metadata: RequirementsFile['_meta'];
29
+ requirements: RequirementNode[];
30
+ };
31
+ /**
32
+ * Flatten a requirement tree into a list of all nodes.
33
+ * Useful for searching or displaying all requirements.
34
+ */
35
+ export declare function flattenRequirementTree(node: RequirementNode): RequirementNode[];
36
+ /**
37
+ * Find a requirement by its ID in a tree.
38
+ */
39
+ export declare function findRequirementById(nodes: RequirementNode[], id: string): RequirementNode | undefined;
40
+ /**
41
+ * Get all requirements from multiple files as a flat list.
42
+ */
43
+ export declare function getAllRequirements(requirements: RequirementNode[]): RequirementNode[];
44
+ //# sourceMappingURL=parser.d.ts.map