@popoverai/dotrequirements 0.26.1 → 0.27.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 (84) hide show
  1. package/README.md +13 -69
  2. package/dist/cli.js +19 -7
  3. package/dist/codebase-to-spec/present.js +4 -5
  4. package/dist/codebase-to-spec/validate.js +3 -2
  5. package/dist/commands/acceptance-test.js +4 -2
  6. package/dist/commands/ai-setup.d.ts +8 -2
  7. package/dist/commands/ai-setup.js +154 -310
  8. package/dist/commands/get.js +6 -2
  9. package/dist/commands/init.js +8 -6
  10. package/dist/commands/link-resolution.d.ts +3 -1
  11. package/dist/commands/link-resolution.js +4 -2
  12. package/dist/commands/mcp.d.ts +8 -2
  13. package/dist/commands/mcp.js +17 -6
  14. package/dist/commands/pull.js +36 -3
  15. package/dist/commands/push.js +54 -16
  16. package/dist/commands/report.js +18 -3
  17. package/dist/commands/review-test.d.ts +5 -1
  18. package/dist/commands/review-test.js +117 -15
  19. package/dist/commands/style-check.d.ts +1 -0
  20. package/dist/commands/style-check.js +137 -13
  21. package/dist/commands/tests-for.js +13 -13
  22. package/dist/commands/validate.js +14 -14
  23. package/dist/convex.d.ts +1 -3
  24. package/dist/convex.js +3 -3
  25. package/dist/harness/cache.d.ts +19 -3
  26. package/dist/harness/cache.js +38 -12
  27. package/dist/harness/finalize.js +33 -1
  28. package/dist/harness/index.js +16 -9
  29. package/dist/harness/requirementsLoader.js +12 -0
  30. package/dist/harness/tracking.d.ts +17 -2
  31. package/dist/harness/tracking.js +83 -9
  32. package/dist/push/core.d.ts +50 -0
  33. package/dist/push/core.js +149 -11
  34. package/dist/push/index.d.ts +1 -1
  35. package/dist/push/index.js +1 -1
  36. package/dist/requirements/cloud-ai.d.ts +21 -8
  37. package/dist/requirements/cloud-ai.js +10 -8
  38. package/dist/requirements/cloud-coverage.d.ts +12 -2
  39. package/dist/requirements/cloud-coverage.js +30 -3
  40. package/dist/requirements/grep.d.ts +7 -2
  41. package/dist/requirements/grep.js +75 -47
  42. package/dist/schema/builder.d.ts +1 -1
  43. package/dist/schema/builder.js +13 -0
  44. package/dist/schema/conversions.d.ts +7 -2
  45. package/dist/schema/conversions.js +13 -4
  46. package/dist/schema/parser-core.d.ts +41 -0
  47. package/dist/schema/parser-core.js +113 -18
  48. package/dist/schema/parser.d.ts +8 -26
  49. package/dist/schema/parser.js +23 -251
  50. package/dist/schema/resolver.js +18 -8
  51. package/dist/templates/context-file-section.md +25 -22
  52. package/dist/utils/context-file.d.ts +7 -3
  53. package/dist/utils/context-file.js +10 -7
  54. package/dist/utils/env.js +17 -1
  55. package/dist/utils/oauth-flow.js +8 -0
  56. package/dist/utils/project-settings.d.ts +5 -0
  57. package/dist/utils/project-settings.js +36 -1
  58. package/package.json +3 -5
  59. package/dist/mcp/convexClient.d.ts +0 -19
  60. package/dist/mcp/convexClient.js +0 -24
  61. package/dist/mcp/handlers/authoring.d.ts +0 -41
  62. package/dist/mcp/handlers/authoring.js +0 -104
  63. package/dist/mcp/handlers/debug.d.ts +0 -16
  64. package/dist/mcp/handlers/debug.js +0 -37
  65. package/dist/mcp/handlers/get.d.ts +0 -24
  66. package/dist/mcp/handlers/get.js +0 -65
  67. package/dist/mcp/handlers/index.d.ts +0 -28
  68. package/dist/mcp/handlers/index.js +0 -19
  69. package/dist/mcp/handlers/list.d.ts +0 -7
  70. package/dist/mcp/handlers/list.js +0 -43
  71. package/dist/mcp/handlers/push.d.ts +0 -26
  72. package/dist/mcp/handlers/push.js +0 -186
  73. package/dist/mcp/handlers/report.d.ts +0 -16
  74. package/dist/mcp/handlers/report.js +0 -134
  75. package/dist/mcp/handlers/review.d.ts +0 -51
  76. package/dist/mcp/handlers/review.js +0 -200
  77. package/dist/mcp/handlers/search.d.ts +0 -30
  78. package/dist/mcp/handlers/search.js +0 -58
  79. package/dist/mcp/handlers/test-mapping.d.ts +0 -39
  80. package/dist/mcp/handlers/test-mapping.js +0 -133
  81. package/dist/mcp/handlers/types.d.ts +0 -75
  82. package/dist/mcp/handlers/types.js +0 -25
  83. package/dist/mcp/index.d.ts +0 -45
  84. package/dist/mcp/index.js +0 -634
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Convert between Convex flat representation and hierarchical RequirementNode trees.
3
3
  */
4
- import type { Metadata, RequirementNode } from "./schemas.js";
4
+ import { type Metadata, type RequirementNode } from "./schemas.js";
5
5
  /**
6
6
  * Convex requirement type (flat structure with position paths).
7
7
  * This mirrors the Convex database schema.
@@ -31,7 +31,12 @@ export declare function constructKey(prefix: string, index: number): string;
31
31
  /**
32
32
  * Parse a requirement key into prefix and index.
33
33
  * E.g., "REQ-123" -> { prefix: "REQ", index: 123 }
34
- * Returns null if key doesn't match expected format.
34
+ * E.g., "SYNC-CLI-CREATE-1" -> { prefix: "SYNC-CLI-CREATE", index: 1 }
35
+ * Returns null if key doesn't match REQUIREMENT_KEY_PATTERN, which is the
36
+ * sole grammar authority here: the split below is mechanical. (Deliberately
37
+ * NOT delegated to parseRequirementKey — it case-normalizes and enforces a
38
+ * 30-char prefix cap this pattern doesn't, and pattern-passing keys must
39
+ * never fall back to the caller's prefix=node.id path.)
35
40
  */
36
41
  export declare function parseKey(key: string): {
37
42
  prefix: string;
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Convert between Convex flat representation and hierarchical RequirementNode trees.
3
3
  */
4
+ import { REQUIREMENT_KEY_PATTERN, } from "./schemas.js";
4
5
  /**
5
6
  * Construct a requirement key from prefix and index.
6
7
  * E.g., ("REQ", 123) -> "REQ-123"
@@ -11,13 +12,21 @@ export function constructKey(prefix, index) {
11
12
  /**
12
13
  * Parse a requirement key into prefix and index.
13
14
  * E.g., "REQ-123" -> { prefix: "REQ", index: 123 }
14
- * Returns null if key doesn't match expected format.
15
+ * E.g., "SYNC-CLI-CREATE-1" -> { prefix: "SYNC-CLI-CREATE", index: 1 }
16
+ * Returns null if key doesn't match REQUIREMENT_KEY_PATTERN, which is the
17
+ * sole grammar authority here: the split below is mechanical. (Deliberately
18
+ * NOT delegated to parseRequirementKey — it case-normalizes and enforces a
19
+ * 30-char prefix cap this pattern doesn't, and pattern-passing keys must
20
+ * never fall back to the caller's prefix=node.id path.)
15
21
  */
16
22
  export function parseKey(key) {
17
- const match = key.match(/^([A-Z0-9_]+)-(\d+)$/);
18
- if (!match)
23
+ if (!REQUIREMENT_KEY_PATTERN.test(key))
19
24
  return null;
20
- return { prefix: match[1], index: parseInt(match[2], 10) };
25
+ const splitAt = key.lastIndexOf("-");
26
+ return {
27
+ prefix: key.slice(0, splitAt),
28
+ index: parseInt(key.slice(splitAt + 1), 10),
29
+ };
21
30
  }
22
31
  /**
23
32
  * Build a hierarchical tree from flat Convex requirements.
@@ -15,6 +15,19 @@ export declare const DELIMITER_PATTERN = "(?:\u2192|->)";
15
15
  * Parse a criterion line in "position. Label → content" format.
16
16
  * Example: "0. Given → user has valid credentials"
17
17
  * Also supports optional label: "0. → user has valid credentials"
18
+ *
19
+ * This is the canonical criterion grammar (SYNC-KEY-1.4). The web editor's
20
+ * markdown-conversion parser mirrors this exact behavior; keep them in sync.
21
+ *
22
+ * Two behaviors are load-bearing:
23
+ * - Split on the FIRST delimiter only, so an arrow inside the content
24
+ * ("maps A -> B") is preserved verbatim rather than being re-consumed as a
25
+ * label boundary. Everything before the first delimiter is the (optional,
26
+ * freeform — custom labels are supported, see MARKDOWN_SCHEMA) label;
27
+ * everything after is content.
28
+ * - An empty criterion ("0. → ") is a valid criterion with content "", not a
29
+ * parse failure. Returning null here would make parseRequirementBlocksFromMarkdown
30
+ * reject the whole block, and would drop the criterion (and its subtree) on load.
18
31
  */
19
32
  export declare function parseCriterionLine(line: string, delimiter?: string): ParsedCriterion | null;
20
33
  /**
@@ -40,6 +53,34 @@ export declare function extractRequirementBlocks(body: string): Array<{
40
53
  key: string;
41
54
  blockContent: string;
42
55
  }>;
56
+ /**
57
+ * Split raw file content into its leading YAML frontmatter and body — both
58
+ * derived from ONE fence match, so the two halves are complementary by
59
+ * construction and can never disagree about where the fence closes.
60
+ *
61
+ * Normalizes CRLF first (SYNC-FORMAT-1): frontmatter isolation must accept
62
+ * the same line-ending styles the parser does. Callers must use this (or the
63
+ * wrappers below) instead of hand-rolled regexes so every consumer agrees on
64
+ * what "has frontmatter" means.
65
+ *
66
+ * Returns undefined when the content has no leading frontmatter fence.
67
+ */
68
+ export declare function splitFrontmatter(content: string): {
69
+ yaml: string;
70
+ body: string;
71
+ } | undefined;
72
+ /**
73
+ * The frontmatter half of splitFrontmatter: the inner YAML text, or
74
+ * undefined when the file has no leading fence.
75
+ */
76
+ export declare function extractFrontmatterBlock(content: string): string | undefined;
77
+ /**
78
+ * The body half of splitFrontmatter: the (CRLF-normalized) content with the
79
+ * leading frontmatter fence removed, or the whole normalized content when
80
+ * there is no fence. Blank lines directly after the fence are consumed so
81
+ * the body starts at its first real line.
82
+ */
83
+ export declare function stripFrontmatterBlock(content: string): string;
43
84
  /**
44
85
  * Parse requirement blocks from markdown content (without frontmatter).
45
86
  * Use this for web editor content that doesn't have YAML frontmatter.
@@ -11,24 +11,48 @@ import { ValidationError, validateKey, } from "./schemas.js";
11
11
  */
12
12
  export const DEFAULT_DELIMITER = "→";
13
13
  export const DELIMITER_PATTERN = "(?:→|->)"; // Non-capturing group for both Unicode and ASCII
14
+ // Compiled once for the common (default-delimiter) path so parseCriterionLine
15
+ // doesn't rebuild the same RegExp on every line.
16
+ const DEFAULT_DELIMITER_REGEX = new RegExp(DELIMITER_PATTERN);
14
17
  /**
15
18
  * Parse a criterion line in "position. Label → content" format.
16
19
  * Example: "0. Given → user has valid credentials"
17
20
  * Also supports optional label: "0. → user has valid credentials"
21
+ *
22
+ * This is the canonical criterion grammar (SYNC-KEY-1.4). The web editor's
23
+ * markdown-conversion parser mirrors this exact behavior; keep them in sync.
24
+ *
25
+ * Two behaviors are load-bearing:
26
+ * - Split on the FIRST delimiter only, so an arrow inside the content
27
+ * ("maps A -> B") is preserved verbatim rather than being re-consumed as a
28
+ * label boundary. Everything before the first delimiter is the (optional,
29
+ * freeform — custom labels are supported, see MARKDOWN_SCHEMA) label;
30
+ * everything after is content.
31
+ * - An empty criterion ("0. → ") is a valid criterion with content "", not a
32
+ * parse failure. Returning null here would make parseRequirementBlocksFromMarkdown
33
+ * reject the whole block, and would drop the criterion (and its subtree) on load.
18
34
  */
19
35
  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) {
36
+ // Position prefix: "0.", "1.0.", "2.3.1." — required for a criterion line.
37
+ const positionMatch = line.match(/^\s*(\d+(?:\.\d+)*)\.\s*/);
38
+ if (!positionMatch) {
26
39
  return null;
27
40
  }
41
+ const rest = line.slice(positionMatch[0].length);
42
+ const delimiterMatch = rest.match(delimiter === DELIMITER_PATTERN
43
+ ? DEFAULT_DELIMITER_REGEX
44
+ : new RegExp(delimiter));
45
+ if (!delimiterMatch || delimiterMatch.index === undefined) {
46
+ return null;
47
+ }
48
+ const label = rest.slice(0, delimiterMatch.index).trim();
49
+ const content = rest
50
+ .slice(delimiterMatch.index + delimiterMatch[0].length)
51
+ .trim();
28
52
  return {
29
- position: match[1],
30
- label: match[2] ? match[2].trim() : "", // Preserve original case, empty if no label
31
- content: match[3].trim(),
53
+ position: positionMatch[1],
54
+ label, // Preserve original case; freeform (empty when no label)
55
+ content, // May be "" for an empty criterion
32
56
  };
33
57
  }
34
58
  /**
@@ -95,30 +119,48 @@ export function parseRequirementBlock(key, blockContent) {
95
119
  }
96
120
  const criterion = parseCriterionLine(line);
97
121
  if (!criterion) {
98
- // Check if this looks like another requirement key (KEY: content format)
122
+ // Only a line whose prefix is shaped like a requirement KEY (uppercase
123
+ // prefix + "-<digits>", per REQUIREMENT_KEY_PATTERN) suggests a second
124
+ // requirement in the block; a stray "https://..." must not be
125
+ // misdiagnosed as one.
99
126
  const trimmedLine = line.trim();
100
- if (/^[\w-]+:\s*.+/.test(trimmedLine)) {
127
+ if (/^[A-Z][A-Z-]*-\d+:\s*.+/.test(trimmedLine)) {
101
128
  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
129
  }
103
130
  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
131
  }
132
+ // SYNC-FORMAT-2.1: a duplicate position is structural ambiguity — reject
133
+ // it instead of silently dropping the earlier criterion.
134
+ if (criteriaByPosition.has(criterion.position)) {
135
+ throw new ValidationError(`Duplicate criterion position "${criterion.position}" at line ${i + 1} — each position may appear only once per requirement`, key);
136
+ }
105
137
  criteriaByPosition.set(criterion.position, criterion);
106
138
  }
107
139
  // Build tree from flat position paths
108
- buildTreeFromPositions(root, key, criteriaByPosition);
140
+ const consumed = new Set();
141
+ buildTreeFromPositions(root, key, criteriaByPosition, consumed);
142
+ // SYNC-FORMAT-2.2: any criterion not attached to the tree references a
143
+ // missing parent position — reject it instead of silently dropping it.
144
+ for (const position of criteriaByPosition.keys()) {
145
+ if (!consumed.has(position)) {
146
+ const parentPosition = position.split(".").slice(0, -1).join(".");
147
+ throw new ValidationError(`Orphaned criterion position "${position}" — parent position "${parentPosition}" does not exist`, key);
148
+ }
149
+ }
109
150
  return root;
110
151
  }
111
152
  /**
112
153
  * Build a tree structure from flat position paths.
113
154
  * Positions like "0", "1", "1.0", "1.1", "1.1.0" define the hierarchy.
114
155
  */
115
- function buildTreeFromPositions(root, rootKey, criteriaByPosition) {
156
+ function buildTreeFromPositions(root, rootKey, criteriaByPosition, consumed) {
116
157
  // Get all top-level children (single digit positions: "0", "1", "2")
117
158
  const topLevelPositions = Array.from(criteriaByPosition.keys())
118
159
  .filter((pos) => !pos.includes("."))
119
160
  .sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
120
161
  for (const position of topLevelPositions) {
121
162
  const criterion = criteriaByPosition.get(position);
163
+ consumed.add(position);
122
164
  const childNode = {
123
165
  id: `${rootKey}.${position}`,
124
166
  label: criterion.label,
@@ -126,14 +168,14 @@ function buildTreeFromPositions(root, rootKey, criteriaByPosition) {
126
168
  children: [],
127
169
  };
128
170
  // Recursively build children
129
- buildChildrenFromPositions(childNode, position, rootKey, criteriaByPosition);
171
+ buildChildrenFromPositions(childNode, position, rootKey, criteriaByPosition, consumed);
130
172
  root.children.push(childNode);
131
173
  }
132
174
  }
133
175
  /**
134
176
  * Recursively build children for a given position.
135
177
  */
136
- function buildChildrenFromPositions(parent, parentPosition, rootKey, criteriaByPosition) {
178
+ function buildChildrenFromPositions(parent, parentPosition, rootKey, criteriaByPosition, consumed) {
137
179
  // Find direct children (e.g., if parent is "1", find "1.0", "1.1", etc.)
138
180
  const childPositions = Array.from(criteriaByPosition.keys())
139
181
  .filter((pos) => {
@@ -158,6 +200,7 @@ function buildChildrenFromPositions(parent, parentPosition, rootKey, criteriaByP
158
200
  });
159
201
  for (const position of childPositions) {
160
202
  const criterion = criteriaByPosition.get(position);
203
+ consumed.add(position);
161
204
  const childNode = {
162
205
  id: `${rootKey}.${position}`,
163
206
  label: criterion.label,
@@ -165,7 +208,7 @@ function buildChildrenFromPositions(parent, parentPosition, rootKey, criteriaByP
165
208
  children: [],
166
209
  };
167
210
  // Recursively build grandchildren
168
- buildChildrenFromPositions(childNode, position, rootKey, criteriaByPosition);
211
+ buildChildrenFromPositions(childNode, position, rootKey, criteriaByPosition, consumed);
169
212
  parent.children.push(childNode);
170
213
  }
171
214
  }
@@ -175,6 +218,7 @@ function buildChildrenFromPositions(parent, parentPosition, rootKey, criteriaByP
175
218
  */
176
219
  export function extractRequirementBlocks(body) {
177
220
  const blocks = [];
221
+ const seenKeys = new Set();
178
222
  // Find all ```dotrequirements code blocks
179
223
  const blockRegex = /```dotrequirements\n([\s\S]*?)```/gm;
180
224
  let match = blockRegex.exec(body);
@@ -190,17 +234,65 @@ export function extractRequirementBlocks(body) {
190
234
  // locally instead of erroring server-side. Validation only — the original
191
235
  // key is preserved (normalization happens downstream).
192
236
  validateKey(key);
237
+ // SYNC-FORMAT-2.0: a duplicate key is structural ambiguity — reject it
238
+ // instead of producing two requirements with the same key.
239
+ if (seenKeys.has(key)) {
240
+ throw new ValidationError(`Duplicate requirement key "${key}" — each requirement key may appear in only one block`, key);
241
+ }
242
+ seenKeys.add(key);
193
243
  blocks.push({ key, blockContent: blockContent.trim() });
194
244
  match = blockRegex.exec(body);
195
245
  }
196
246
  return blocks;
197
247
  }
248
+ /**
249
+ * Split raw file content into its leading YAML frontmatter and body — both
250
+ * derived from ONE fence match, so the two halves are complementary by
251
+ * construction and can never disagree about where the fence closes.
252
+ *
253
+ * Normalizes CRLF first (SYNC-FORMAT-1): frontmatter isolation must accept
254
+ * the same line-ending styles the parser does. Callers must use this (or the
255
+ * wrappers below) instead of hand-rolled regexes so every consumer agrees on
256
+ * what "has frontmatter" means.
257
+ *
258
+ * Returns undefined when the content has no leading frontmatter fence.
259
+ */
260
+ export function splitFrontmatter(content) {
261
+ const normalized = content.replace(/\r\n/g, "\n");
262
+ const match = normalized.match(/^---\n([\s\S]*?)\n---(?:\n|$)/);
263
+ if (!match) {
264
+ return undefined;
265
+ }
266
+ return { yaml: match[1], body: normalized.slice(match[0].length) };
267
+ }
268
+ /**
269
+ * The frontmatter half of splitFrontmatter: the inner YAML text, or
270
+ * undefined when the file has no leading fence.
271
+ */
272
+ export function extractFrontmatterBlock(content) {
273
+ return splitFrontmatter(content)?.yaml;
274
+ }
275
+ /**
276
+ * The body half of splitFrontmatter: the (CRLF-normalized) content with the
277
+ * leading frontmatter fence removed, or the whole normalized content when
278
+ * there is no fence. Blank lines directly after the fence are consumed so
279
+ * the body starts at its first real line.
280
+ */
281
+ export function stripFrontmatterBlock(content) {
282
+ const split = splitFrontmatter(content);
283
+ if (split === undefined) {
284
+ return content.replace(/\r\n/g, "\n");
285
+ }
286
+ return split.body.replace(/^\n+/, "");
287
+ }
198
288
  /**
199
289
  * Parse requirement blocks from markdown content (without frontmatter).
200
290
  * Use this for web editor content that doesn't have YAML frontmatter.
201
291
  */
202
292
  export function parseRequirementBlocksFromMarkdown(markdownContent) {
203
- const blocks = extractRequirementBlocks(markdownContent);
293
+ // SYNC-FORMAT-1: normalize Windows (CRLF) line endings so content parses
294
+ // identically regardless of line-ending style.
295
+ const blocks = extractRequirementBlocks(markdownContent.replace(/\r\n/g, "\n"));
204
296
  const requirements = [];
205
297
  for (const { key, blockContent } of blocks) {
206
298
  try {
@@ -208,7 +300,10 @@ export function parseRequirementBlocksFromMarkdown(markdownContent) {
208
300
  requirements.push(reqNode);
209
301
  }
210
302
  catch (error) {
211
- throw new ValidationError(`Failed to parse requirement ${key}`, key, error);
303
+ // Include the inner message so structural errors (duplicate position,
304
+ // orphaned position, ...) stay identifiable at the top level.
305
+ const detail = error instanceof Error && error.message ? `: ${error.message}` : "";
306
+ throw new ValidationError(`Failed to parse requirement ${key}${detail}`, key, error);
212
307
  }
213
308
  }
214
309
  return requirements;
@@ -1,19 +1,14 @@
1
1
  /**
2
2
  * Parse Markdown requirements files into typed structures.
3
+ *
4
+ * File-level concerns only: frontmatter isolation, metadata validation, and
5
+ * disk IO. All block and criterion parsing delegates to the
6
+ * environment-agnostic parser-core module, which the web editor and Convex
7
+ * also consume — one parser, so the CLI and the cloud cannot drift on what a
8
+ * requirements file means (SYNC-FORMAT-2.3).
3
9
  */
4
- import { type ParsedCriterion, type RequirementNode, type RequirementsFile } 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;
10
+ import { type RequirementNode, type RequirementsFile } from "./schemas.js";
11
+ export { findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, } from "./parser-core.js";
17
12
  /**
18
13
  * Parse a complete requirements Markdown file.
19
14
  */
@@ -28,17 +23,4 @@ export declare function parseRequirementsFromFile(filePath: string): {
28
23
  metadata: RequirementsFile["_meta"];
29
24
  requirements: RequirementNode[];
30
25
  };
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
26
  //# sourceMappingURL=parser.d.ts.map