flex-md 3.5.0 → 4.1.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 (74) hide show
  1. package/README.md +423 -39
  2. package/dist/index.cjs +62 -3
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +2 -0
  5. package/dist/md/parse.d.ts +1 -0
  6. package/dist/md/parse.js +12 -0
  7. package/dist/ofs/parser.js +31 -10
  8. package/dist/tokens/auto-fix.d.ts +10 -0
  9. package/dist/tokens/auto-fix.js +56 -0
  10. package/dist/tokens/cognitive-cost.d.ts +10 -0
  11. package/dist/tokens/cognitive-cost.js +205 -0
  12. package/dist/tokens/compliance.d.ts +10 -0
  13. package/dist/tokens/compliance.js +70 -0
  14. package/dist/tokens/confidence.d.ts +6 -0
  15. package/dist/tokens/confidence.js +332 -0
  16. package/dist/tokens/estimator.d.ts +12 -0
  17. package/dist/tokens/estimator.js +138 -0
  18. package/dist/tokens/improvements.d.ts +10 -0
  19. package/dist/tokens/improvements.js +697 -0
  20. package/dist/tokens/index.d.ts +24 -0
  21. package/dist/tokens/index.js +31 -0
  22. package/dist/tokens/parser.d.ts +3 -0
  23. package/dist/tokens/parser.js +97 -0
  24. package/dist/tokens/patterns.d.ts +9 -0
  25. package/dist/tokens/patterns.js +20 -0
  26. package/dist/tokens/smart-report.d.ts +10 -0
  27. package/dist/tokens/smart-report.js +187 -0
  28. package/dist/tokens/spec-estimator.d.ts +7 -0
  29. package/dist/tokens/spec-estimator.js +68 -0
  30. package/dist/tokens/types.d.ts +185 -0
  31. package/dist/tokens/validator.d.ts +16 -0
  32. package/dist/tokens/validator.js +59 -0
  33. package/docs/Recommended New Strategies for AI Request Builder.md +691 -0
  34. package/package.json +7 -3
  35. package/dist/detection/detector.d.ts +0 -6
  36. package/dist/detection/detector.js +0 -104
  37. package/dist/detection/extractor.d.ts +0 -10
  38. package/dist/detection/extractor.js +0 -54
  39. package/dist/issues/build.d.ts +0 -26
  40. package/dist/issues/build.js +0 -62
  41. package/dist/md/lists.d.ts +0 -14
  42. package/dist/md/lists.js +0 -33
  43. package/dist/md/tables.d.ts +0 -25
  44. package/dist/md/tables.js +0 -72
  45. package/dist/ofs/extractor.d.ts +0 -9
  46. package/dist/ofs/extractor.js +0 -75
  47. package/dist/ofs/issues.d.ts +0 -14
  48. package/dist/ofs/issues.js +0 -92
  49. package/dist/ofs/validator.d.ts +0 -10
  50. package/dist/ofs/validator.js +0 -91
  51. package/dist/outline/builder.d.ts +0 -10
  52. package/dist/outline/builder.js +0 -85
  53. package/dist/outline/renderer.d.ts +0 -6
  54. package/dist/outline/renderer.js +0 -23
  55. package/dist/parser.d.ts +0 -2
  56. package/dist/parser.js +0 -199
  57. package/dist/parsers/lists.d.ts +0 -6
  58. package/dist/parsers/lists.js +0 -36
  59. package/dist/parsers/tables.d.ts +0 -10
  60. package/dist/parsers/tables.js +0 -58
  61. package/dist/stringify.d.ts +0 -2
  62. package/dist/stringify.js +0 -110
  63. package/dist/test-pipeline.js +0 -53
  64. package/dist/test-runner.d.ts +0 -1
  65. package/dist/test-runner.js +0 -331
  66. package/dist/test-strictness.d.ts +0 -1
  67. package/dist/test-strictness.js +0 -213
  68. package/dist/util.d.ts +0 -5
  69. package/dist/util.js +0 -64
  70. package/dist/validate/policy.d.ts +0 -10
  71. package/dist/validate/policy.js +0 -17
  72. package/dist/validator.d.ts +0 -2
  73. package/dist/validator.js +0 -80
  74. /package/dist/{test-pipeline.d.ts → tokens/types.js} +0 -0
@@ -1,104 +0,0 @@
1
- /**
2
- * Detect FlexMD and other structured objects in arbitrary text.
3
- * Returns all detected objects with confidence scores and byte ranges.
4
- */
5
- export function detectObjects(text) {
6
- const detected = [];
7
- // Tier A: Detect ```flexmd fenced blocks (highest confidence)
8
- detected.push(...detectFlexMdFences(text));
9
- // Tier B: Detect ```json blocks with FlexDocument shape
10
- detected.push(...detectFlexDocJsonFences(text));
11
- // Tier C: Detect raw FlexMD markers (best effort)
12
- detected.push(...detectRawFlexMd(text));
13
- // Sort by start position
14
- detected.sort((a, b) => a.start - b.start);
15
- return detected;
16
- }
17
- /**
18
- * Tier A: Detect ```flexmd fenced blocks.
19
- */
20
- function detectFlexMdFences(text) {
21
- const detected = [];
22
- const regex = /```flexmd\n([\s\S]*?)```/g;
23
- let match;
24
- while ((match = regex.exec(text)) !== null) {
25
- detected.push({
26
- kind: "flexmd_fence",
27
- confidence: 1.0,
28
- start: match.index,
29
- end: match.index + match[0].length,
30
- raw: match[0],
31
- inner: match[1]
32
- });
33
- }
34
- return detected;
35
- }
36
- /**
37
- * Tier B: Detect ```json blocks that match FlexDocument shape.
38
- */
39
- function detectFlexDocJsonFences(text) {
40
- const detected = [];
41
- const regex = /```json\n([\s\S]*?)```/g;
42
- let match;
43
- while ((match = regex.exec(text)) !== null) {
44
- const inner = match[1];
45
- // Try to parse as JSON
46
- try {
47
- const parsed = JSON.parse(inner);
48
- // Check if it has the FlexDocument shape (has "frames" array)
49
- if (parsed && typeof parsed === "object" && Array.isArray(parsed.frames)) {
50
- detected.push({
51
- kind: "flexdoc_json_fence",
52
- confidence: 0.9,
53
- start: match.index,
54
- end: match.index + match[0].length,
55
- raw: match[0],
56
- inner
57
- });
58
- }
59
- }
60
- catch {
61
- // Not valid JSON, skip
62
- }
63
- }
64
- return detected;
65
- }
66
- /**
67
- * Tier C: Detect raw FlexMD markers (best effort).
68
- * Looks for at least 2 strong markers within first 500 chars:
69
- * - [[...]]
70
- * - @key:
71
- * - @payload:name:
72
- */
73
- function detectRawFlexMd(text) {
74
- const detected = [];
75
- // Look for frame markers
76
- const frameRegex = /\[\[([^\]]+)\]\]/g;
77
- const metaRegex = /@[a-zA-Z_][a-zA-Z0-9_]*:/g;
78
- const payloadRegex = /@payload:[a-zA-Z_][a-zA-Z0-9_]*:/g;
79
- let match;
80
- const markers = [];
81
- // Collect all marker positions
82
- while ((match = frameRegex.exec(text)) !== null) {
83
- markers.push(match.index);
84
- }
85
- while ((match = metaRegex.exec(text)) !== null) {
86
- markers.push(match.index);
87
- }
88
- while ((match = payloadRegex.exec(text)) !== null) {
89
- markers.push(match.index);
90
- }
91
- // If we have at least 2 markers, consider it raw FlexMD
92
- if (markers.length >= 2) {
93
- const start = Math.min(...markers);
94
- const end = text.length;
95
- detected.push({
96
- kind: "raw_flexmd",
97
- confidence: 0.7,
98
- start,
99
- end,
100
- raw: text.substring(start, end)
101
- });
102
- }
103
- return detected;
104
- }
@@ -1,10 +0,0 @@
1
- import type { FlexDocument } from "../types.js";
2
- export interface ParseAnyResult {
3
- flexDocs: FlexDocument[];
4
- markdownSnippets: string[];
5
- remainder: string;
6
- }
7
- /**
8
- * Parse any text and extract all FlexMD documents and Markdown snippets.
9
- */
10
- export declare function parseAny(text: string): ParseAnyResult;
@@ -1,54 +0,0 @@
1
- import { detectObjects } from "./detector.js";
2
- import { parseFlexMd } from "../parser.js";
3
- /**
4
- * Parse any text and extract all FlexMD documents and Markdown snippets.
5
- */
6
- export function parseAny(text) {
7
- const detected = detectObjects(text);
8
- const flexDocs = [];
9
- const markdownSnippets = [];
10
- for (const obj of detected) {
11
- if (obj.kind === "flexmd_fence" && obj.inner) {
12
- // Parse fenced FlexMD
13
- try {
14
- const doc = parseFlexMd(obj.inner);
15
- flexDocs.push(doc);
16
- }
17
- catch {
18
- // Parse failed, treat as markdown snippet
19
- markdownSnippets.push(obj.inner);
20
- }
21
- }
22
- else if (obj.kind === "flexdoc_json_fence" && obj.inner) {
23
- // Parse JSON FlexDocument
24
- try {
25
- const doc = JSON.parse(obj.inner);
26
- flexDocs.push(doc);
27
- }
28
- catch {
29
- // Parse failed, skip
30
- }
31
- }
32
- else if (obj.kind === "raw_flexmd") {
33
- // Parse raw FlexMD
34
- try {
35
- const doc = parseFlexMd(obj.raw);
36
- flexDocs.push(doc);
37
- }
38
- catch {
39
- // Parse failed, treat as markdown snippet
40
- markdownSnippets.push(obj.raw);
41
- }
42
- }
43
- }
44
- // Calculate remainder (text not covered by detected objects)
45
- let remainder = text;
46
- for (const obj of detected) {
47
- remainder = remainder.replace(obj.raw, "");
48
- }
49
- return {
50
- flexDocs,
51
- markdownSnippets,
52
- remainder: remainder.trim()
53
- };
54
- }
@@ -1,26 +0,0 @@
1
- import type { StrictnessOptions } from "../strictness/types.js";
2
- export type IssuesStatusCode = "missing_input" | "unclear_instructions" | "invalid_format" | "unsupported" | (string & {});
3
- export interface IssueItem {
4
- issue: string;
5
- path?: string;
6
- expected?: string;
7
- got?: string;
8
- hint?: string;
9
- [k: string]: string | undefined;
10
- }
11
- export interface BuildIssuesEnvelopeInput {
12
- status: "error" | "partial";
13
- code: IssuesStatusCode;
14
- message: string;
15
- issues: IssueItem[];
16
- missingInputs?: string[];
17
- clarificationsNeeded?: string[];
18
- /** If provided, wraps output in a single container fence when strictness requires it. */
19
- strictness?: StrictnessOptions;
20
- /** Override fence language: "markdown" | "flexmd" (defaults from strictness.container) */
21
- fence?: "markdown" | "flexmd";
22
- }
23
- /**
24
- * Build a Markdown-only issues envelope.
25
- */
26
- export declare function buildIssuesEnvelope(input: BuildIssuesEnvelopeInput): string;
@@ -1,62 +0,0 @@
1
- import { strictnessDefaults } from "../strictness/types.js";
2
- /**
3
- * Build a Markdown-only issues envelope.
4
- */
5
- export function buildIssuesEnvelope(input) {
6
- const strict = input.strictness ? { ...strictnessDefaults(input.strictness.level), ...input.strictness } : undefined;
7
- const fence = input.fence ?? resolveFence(strict);
8
- const lines = [];
9
- lines.push(`## Status`);
10
- lines.push(`- status: ${input.status}`);
11
- lines.push(`- code: ${input.code}`);
12
- lines.push(`- message: ${input.message}`);
13
- lines.push(``);
14
- if (input.missingInputs && input.missingInputs.length) {
15
- lines.push(`## Missing inputs`);
16
- for (const mi of input.missingInputs)
17
- lines.push(`- ${mi}`);
18
- lines.push(``);
19
- }
20
- if (input.clarificationsNeeded && input.clarificationsNeeded.length) {
21
- lines.push(`## Clarifications needed`);
22
- for (const q of input.clarificationsNeeded)
23
- lines.push(`- ${q}`);
24
- lines.push(``);
25
- }
26
- lines.push(`## Issues`);
27
- if (!input.issues.length) {
28
- lines.push(`- issue: none`);
29
- }
30
- else {
31
- for (const it of input.issues) {
32
- lines.push(`- issue: ${it.issue}`);
33
- emitField(lines, "path", it.path);
34
- emitField(lines, "expected", it.expected);
35
- emitField(lines, "got", it.got);
36
- emitField(lines, "hint", it.hint);
37
- for (const [k, v] of Object.entries(it)) {
38
- if (k === "issue" || k === "path" || k === "expected" || k === "got" || k === "hint")
39
- continue;
40
- emitField(lines, k, v);
41
- }
42
- }
43
- }
44
- let md = lines.join("\n").trimEnd() + "\n";
45
- if (strict && strict.level >= 2) {
46
- const lang = (fence === "flexmd") ? "flexmd" : "markdown";
47
- md = `\`\`\`${lang}\n${md}\`\`\`\n`;
48
- }
49
- return md;
50
- }
51
- function emitField(lines, key, value) {
52
- if (value == null || value === "")
53
- return;
54
- lines.push(` ${key}: ${value}`);
55
- }
56
- function resolveFence(strict) {
57
- if (!strict)
58
- return "markdown";
59
- if (strict.container === "flexmd_fence")
60
- return "flexmd";
61
- return "markdown";
62
- }
@@ -1,14 +0,0 @@
1
- export interface ListItem {
2
- text: string;
3
- index?: number;
4
- children: ListItem[];
5
- }
6
- export interface ParsedList {
7
- kind: "list";
8
- ordered: boolean;
9
- items: ListItem[];
10
- }
11
- /**
12
- * Parses a flat list segment from Markdown into a nested structure.
13
- */
14
- export declare function parseNestedList(sectionMd: string): ParsedList | null;
package/dist/md/lists.js DELETED
@@ -1,33 +0,0 @@
1
- /**
2
- * Parses a flat list segment from Markdown into a nested structure.
3
- */
4
- export function parseNestedList(sectionMd) {
5
- const lines = sectionMd.split("\n");
6
- const listLines = lines
7
- .map(l => ({ raw: l, indent: (l.match(/^\s*/)?.[0].length ?? 0) }))
8
- .filter(x => /^\s*(-\s+|\d+\.\s+)/.test(x.raw));
9
- if (!listLines.length)
10
- return null;
11
- const ordered = listLines.some(x => /^\s*\d+\.\s+/.test(x.raw));
12
- const root = [];
13
- const stack = [];
14
- for (const { raw, indent } of listLines) {
15
- const mO = raw.match(/^\s*(\d+)\.\s+(.*)$/);
16
- const mU = raw.match(/^\s*-\s+(.*)$/);
17
- const text = (mO?.[2] ?? mU?.[1] ?? "").trim();
18
- const item = { text, children: [] };
19
- if (mO)
20
- item.index = Number(mO[1]);
21
- while (stack.length && stack[stack.length - 1].indent >= indent) {
22
- stack.pop();
23
- }
24
- if (!stack.length) {
25
- root.push(item);
26
- }
27
- else {
28
- stack[stack.length - 1].item.children.push(item);
29
- }
30
- stack.push({ indent, item });
31
- }
32
- return { kind: "list", ordered, items: root };
33
- }
@@ -1,25 +0,0 @@
1
- export interface ParsedTable {
2
- kind: "table" | "ordered_table";
3
- by?: string;
4
- columns: string[];
5
- rows: string[][];
6
- }
7
- /**
8
- * Extracts all pipe table blocks from a markdown string.
9
- */
10
- export declare function extractPipeTables(md: string): string[];
11
- /**
12
- * Parses a single pipe table block into columns and rows.
13
- */
14
- export declare function parsePipeTable(block: string): {
15
- columns: string[];
16
- rows: string[][];
17
- } | null;
18
- export declare function normalizeHeader(s: string): string;
19
- /**
20
- * Enforces ordered table constraints (must have '#' column numbered 1..N).
21
- */
22
- export declare function enforceOrderedTable(table: {
23
- columns: string[];
24
- rows: string[][];
25
- }): string[];
package/dist/md/tables.js DELETED
@@ -1,72 +0,0 @@
1
- /**
2
- * Extracts all pipe table blocks from a markdown string.
3
- */
4
- export function extractPipeTables(md) {
5
- const lines = md.split("\n");
6
- const blocks = [];
7
- let i = 0;
8
- while (i < lines.length) {
9
- const h = lines[i] ?? "";
10
- const s = lines[i + 1] ?? "";
11
- const looksHeader = h.includes("|");
12
- const looksSep = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(s);
13
- if (looksHeader && looksSep) {
14
- const start = i;
15
- i += 2;
16
- while (i < lines.length && (lines[i] ?? "").includes("|") && (lines[i] ?? "").trim() !== "") {
17
- i++;
18
- }
19
- const block = lines.slice(start, i).join("\n");
20
- blocks.push(block);
21
- continue;
22
- }
23
- i++;
24
- }
25
- return blocks;
26
- }
27
- /**
28
- * Parses a single pipe table block into columns and rows.
29
- */
30
- export function parsePipeTable(block) {
31
- const lines = block.split("\n").map(l => l.trim()).filter(Boolean);
32
- if (lines.length < 2)
33
- return null;
34
- const parseRow = (row) => {
35
- let content = row;
36
- if (content.startsWith("|"))
37
- content = content.slice(1);
38
- if (content.endsWith("|"))
39
- content = content.slice(0, -1);
40
- return content.split("|").map(c => c.trim());
41
- };
42
- const columns = parseRow(lines[0]);
43
- const rows = lines.slice(2).map(parseRow);
44
- for (const r of rows) {
45
- while (r.length < columns.length)
46
- r.push("");
47
- }
48
- return { columns, rows };
49
- }
50
- export function normalizeHeader(s) {
51
- return s.trim().toLowerCase();
52
- }
53
- /**
54
- * Enforces ordered table constraints (must have '#' column numbered 1..N).
55
- */
56
- export function enforceOrderedTable(table) {
57
- const issues = [];
58
- if (!table.columns.length || normalizeHeader(table.columns[0]) !== "#") {
59
- issues.push("ordered_table_missing_hash_column");
60
- return issues;
61
- }
62
- // enforce 1..N
63
- for (let i = 0; i < table.rows.length; i++) {
64
- const cell = (table.rows[i]?.[0] ?? "").trim();
65
- const n = Number(cell);
66
- if (!Number.isInteger(n) || n !== i + 1) {
67
- issues.push("ordered_table_bad_index_sequence");
68
- break;
69
- }
70
- }
71
- return issues;
72
- }
@@ -1,9 +0,0 @@
1
- import type { OutputFormatSpec, ExtractedResult } from "../types.js";
2
- export interface ExtractOptions {
3
- /** Parse lists even for prose sections */
4
- parseAllLists?: boolean;
5
- }
6
- /**
7
- * Extract structured data from Markdown based on an OutputFormatSpec.
8
- */
9
- export declare function extractOutput(md: string, spec: OutputFormatSpec, opts?: ExtractOptions): ExtractedResult;
@@ -1,75 +0,0 @@
1
- import { buildOutline } from "../outline/builder.js";
2
- import { parseList } from "../parsers/lists.js";
3
- import { extractAllTables } from "../parsers/tables.js";
4
- /**
5
- * Extract structured data from Markdown based on an OutputFormatSpec.
6
- */
7
- export function extractOutput(md, spec, opts = {}) {
8
- const outline = buildOutline(md);
9
- const tables = extractAllTables(md);
10
- // Index nodes by normalized title
11
- const matches = collectMatches(outline.nodes);
12
- const sectionsByName = {};
13
- // Extract each section
14
- for (const section of spec.sections) {
15
- const key = normalizeTitle(section.name);
16
- const nodes = matches.get(key) ?? [];
17
- if (nodes.length === 0) {
18
- // Section not found - could add to a "missing" array if needed
19
- continue;
20
- }
21
- // Choose best node (highest level)
22
- const chosen = chooseBestNode(nodes);
23
- const body = chosen.content_md.trim();
24
- const extracted = {
25
- nodeKey: chosen.key,
26
- nodeLevel: chosen.level,
27
- md: body
28
- };
29
- // Parse lists if required by section kind or if parseAllLists is enabled
30
- if (section.kind === "list" || section.kind === "ordered_list" || opts.parseAllLists) {
31
- const list = parseList(body);
32
- if (list) {
33
- extracted.list = list;
34
- }
35
- }
36
- sectionsByName[section.name] = extracted;
37
- }
38
- return {
39
- outline,
40
- sectionsByName,
41
- tables
42
- };
43
- }
44
- /**
45
- * Collect all nodes by normalized title.
46
- */
47
- function collectMatches(nodes) {
48
- const map = new Map();
49
- function visit(node) {
50
- const key = normalizeTitle(node.title);
51
- const existing = map.get(key) ?? [];
52
- existing.push(node);
53
- map.set(key, existing);
54
- node.children.forEach(visit);
55
- }
56
- nodes.forEach(visit);
57
- return map;
58
- }
59
- /**
60
- * Choose the best node from multiple matches.
61
- * Prefer highest-level heading (smallest level number).
62
- */
63
- function chooseBestNode(nodes) {
64
- if (nodes.length === 1)
65
- return nodes[0];
66
- // Sort by level (ascending) and take first
67
- const sorted = [...nodes].sort((a, b) => a.level - b.level);
68
- return sorted[0];
69
- }
70
- /**
71
- * Normalize title for comparison: lowercase, remove trailing punctuation.
72
- */
73
- function normalizeTitle(t) {
74
- return t.trim().replace(/[:\-–—]\s*$/, "").trim().toLowerCase();
75
- }
@@ -1,14 +0,0 @@
1
- import type { IssuesEnvelope, FlexMdResponseMode } from "../types.js";
2
- /**
3
- * Detects if the response is an Issues envelope (Mode B).
4
- */
5
- export declare function detectIssuesEnvelope(md: string): boolean;
6
- /**
7
- * Historical alias for processResponseMarkdown.
8
- * @deprecated Use detectIssuesEnvelope
9
- */
10
- export declare function detectResponseMode(md: string): FlexMdResponseMode;
11
- /**
12
- * Parses a Markdown Issues envelope into a structured JSON object.
13
- */
14
- export declare function parseIssuesEnvelope(md: string): IssuesEnvelope | null;
@@ -1,92 +0,0 @@
1
- /**
2
- * Detects if the response is an Issues envelope (Mode B).
3
- */
4
- export function detectIssuesEnvelope(md) {
5
- const hasStatus = /(^|\n)##\s+Status\s*$/im.test(md);
6
- const hasIssuesHeading = /(^|\n)##\s+Issues\s*$/im.test(md);
7
- const hasStatusLine = /(^|\n)-\s*status:\s*(error|partial)\s*$/im.test(md);
8
- return hasStatus && hasIssuesHeading && hasStatusLine;
9
- }
10
- /**
11
- * Historical alias for processResponseMarkdown.
12
- * @deprecated Use detectIssuesEnvelope
13
- */
14
- export function detectResponseMode(md) {
15
- return detectIssuesEnvelope(md) ? "issues" : "answer";
16
- }
17
- /**
18
- * Parses a Markdown Issues envelope into a structured JSON object.
19
- */
20
- export function parseIssuesEnvelope(md) {
21
- if (detectResponseMode(md) !== "issues")
22
- return null;
23
- const statusBlock = sectionBody(md, "Status");
24
- const issuesBlock = sectionBody(md, "Issues");
25
- if (!statusBlock || !issuesBlock)
26
- return null;
27
- const statusKv = parseTopLevelKvBullets(statusBlock);
28
- const status = {
29
- status: statusKv["status"] ?? "error",
30
- code: statusKv["code"] ?? "invalid_format",
31
- message: statusKv["message"] ?? "An error occurred."
32
- };
33
- const missingInputs = parseSimpleList(sectionBody(md, "Missing inputs"));
34
- const clarificationsNeeded = parseSimpleList(sectionBody(md, "Clarifications needed"));
35
- const issues = parseIssueItems(issuesBlock);
36
- return {
37
- mode: "issues",
38
- status,
39
- missingInputs: missingInputs.length ? missingInputs : undefined,
40
- clarificationsNeeded: clarificationsNeeded.length ? clarificationsNeeded : undefined,
41
- issues
42
- };
43
- }
44
- function sectionBody(md, heading) {
45
- const rx = new RegExp(`(^|\\n)##\\s+${escapeRx(heading)}\\s*\\n([\\s\\S]*?)(\\n##\\s+|$)`, "i");
46
- const m = md.match(rx);
47
- return m ? (m[2] ?? "").trim() : null;
48
- }
49
- function parseTopLevelKvBullets(block) {
50
- const out = {};
51
- for (const line of block.split("\n")) {
52
- const m = line.match(/^\s*-\s*([a-zA-Z0-9_-]+)\s*:\s*(.+?)\s*$/);
53
- if (m)
54
- out[m[1]] = m[2];
55
- }
56
- return out;
57
- }
58
- function parseSimpleList(block) {
59
- if (!block)
60
- return [];
61
- const items = [];
62
- for (const line of block.split("\n")) {
63
- const m = line.match(/^\s*-\s+(.+?)\s*$/);
64
- if (m)
65
- items.push(m[1].trim());
66
- }
67
- return items;
68
- }
69
- function parseIssueItems(block) {
70
- const lines = block.split("\n");
71
- const items = [];
72
- let current = null;
73
- for (const line of lines) {
74
- const top = line.match(/^\s*-\s*issue:\s*(.+?)\s*$/i);
75
- if (top) {
76
- if (current)
77
- items.push(current);
78
- current = { issue: top[1].trim() };
79
- continue;
80
- }
81
- const sub = line.match(/^\s{2,}([a-zA-Z0-9_-]+)\s*:\s*(.+?)\s*$/);
82
- if (sub && current) {
83
- current[sub[1]] = sub[2].trim();
84
- }
85
- }
86
- if (current)
87
- items.push(current);
88
- return items;
89
- }
90
- function escapeRx(s) {
91
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
92
- }
@@ -1,10 +0,0 @@
1
- import type { OutputFormatSpec } from "../types.js";
2
- export interface OfsValidationResult {
3
- ok: boolean;
4
- errors: string[];
5
- warnings: string[];
6
- }
7
- /**
8
- * Validate Markdown output against an OutputFormatSpec.
9
- */
10
- export declare function validateOutput(md: string, spec: OutputFormatSpec): OfsValidationResult;