flex-md 1.0.0 → 2.0.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.
@@ -0,0 +1,54 @@
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
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,18 @@
1
1
  export * from "./types.js";
2
2
  export { parseFlexMd } from "./parser.js";
3
3
  export { stringifyFlexMd } from "./stringify.js";
4
+ export { validateFlexMd } from "./validator.js";
5
+ export { parseOutputFormatSpec } from "./ofs/parser.js";
6
+ export { stringifyOutputFormatSpec } from "./ofs/stringify.js";
7
+ export { enrichInstructions } from "./ofs/enricher.js";
8
+ export { validateOutput } from "./ofs/validator.js";
9
+ export type { OfsValidationResult } from "./ofs/validator.js";
10
+ export { extractOutput } from "./ofs/extractor.js";
11
+ export type { ExtractOptions } from "./ofs/extractor.js";
12
+ export { buildOutline, slugify } from "./outline/builder.js";
13
+ export { renderOutline } from "./outline/renderer.js";
14
+ export { parseList } from "./parsers/lists.js";
15
+ export { parsePipeTable, extractAllTables } from "./parsers/tables.js";
16
+ export { detectObjects } from "./detection/detector.js";
17
+ export { parseAny } from "./detection/extractor.js";
18
+ export type { ParseAnyResult } from "./detection/extractor.js";
package/dist/index.js CHANGED
@@ -1,3 +1,20 @@
1
+ // Layer A - FlexMD Frames
1
2
  export * from "./types.js";
2
3
  export { parseFlexMd } from "./parser.js";
3
4
  export { stringifyFlexMd } from "./stringify.js";
5
+ export { validateFlexMd } from "./validator.js";
6
+ // Layer B - Output Format Spec (OFS)
7
+ export { parseOutputFormatSpec } from "./ofs/parser.js";
8
+ export { stringifyOutputFormatSpec } from "./ofs/stringify.js";
9
+ export { enrichInstructions } from "./ofs/enricher.js";
10
+ export { validateOutput } from "./ofs/validator.js";
11
+ export { extractOutput } from "./ofs/extractor.js";
12
+ // Outline & Tree
13
+ export { buildOutline, slugify } from "./outline/builder.js";
14
+ export { renderOutline } from "./outline/renderer.js";
15
+ // Parsers
16
+ export { parseList } from "./parsers/lists.js";
17
+ export { parsePipeTable, extractAllTables } from "./parsers/tables.js";
18
+ // Layer C - Detection & Extraction
19
+ export { detectObjects } from "./detection/detector.js";
20
+ export { parseAny } from "./detection/extractor.js";
@@ -0,0 +1,6 @@
1
+ import type { OutputFormatSpec } from "../types.js";
2
+ /**
3
+ * Generate minimal, feature-driven LLM guidance from an OutputFormatSpec.
4
+ * Only includes rules for features actually used in the spec.
5
+ */
6
+ export declare function enrichInstructions(spec: OutputFormatSpec): string;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Generate minimal, feature-driven LLM guidance from an OutputFormatSpec.
3
+ * Only includes rules for features actually used in the spec.
4
+ */
5
+ export function enrichInstructions(spec) {
6
+ const lines = [];
7
+ if (spec.emptySectionValue) {
8
+ lines.push(`If a section is empty, write \`${spec.emptySectionValue}\`.`);
9
+ }
10
+ const hasList = spec.sections.some(s => s.kind === "list");
11
+ const hasOrderedList = spec.sections.some(s => s.kind === "ordered_list");
12
+ const hasTable = spec.tables.length > 0;
13
+ const hasOrderedTable = spec.tables.some(t => t.kind === "ordered_table");
14
+ if (hasList) {
15
+ lines.push("List sections must use '-' bullets (nested allowed).");
16
+ }
17
+ if (hasOrderedList) {
18
+ lines.push("Ordered-list sections must use numbered items (nested allowed).");
19
+ }
20
+ if (hasTable) {
21
+ lines.push("Tables must be Markdown pipe tables with the specified columns.");
22
+ }
23
+ if (hasOrderedTable) {
24
+ lines.push("Ordered tables must add a first column named '#' with rows numbered 1..N.");
25
+ }
26
+ if (lines.length === 0)
27
+ return "";
28
+ return "Rules:\n" + lines.map(l => `- ${l}`).join("\n") + "\n";
29
+ }
@@ -0,0 +1,9 @@
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;
@@ -0,0 +1,75 @@
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
+ }
@@ -0,0 +1,21 @@
1
+ import type { OutputFormatSpec } from "../types.js";
2
+ /**
3
+ * Parse an Output Format Spec block from Markdown.
4
+ *
5
+ * Expected format:
6
+ * ## Output format (Markdown)
7
+ * Include these sections somewhere (order does not matter):
8
+ *
9
+ * - Short answer — prose
10
+ * - Long answer — prose
11
+ * - Reasoning — ordered list
12
+ * - Assumptions — list
13
+ *
14
+ * Tables (only if needed):
15
+ * - (property1, property2, property3 — table)
16
+ * - (property1, property2, property3 — ordered table, by property2)
17
+ *
18
+ * Empty sections:
19
+ * - If a section is empty, write `None`.
20
+ */
21
+ export declare function parseOutputFormatSpec(md: string): OutputFormatSpec;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Parse an Output Format Spec block from Markdown.
3
+ *
4
+ * Expected format:
5
+ * ## Output format (Markdown)
6
+ * Include these sections somewhere (order does not matter):
7
+ *
8
+ * - Short answer — prose
9
+ * - Long answer — prose
10
+ * - Reasoning — ordered list
11
+ * - Assumptions — list
12
+ *
13
+ * Tables (only if needed):
14
+ * - (property1, property2, property3 — table)
15
+ * - (property1, property2, property3 — ordered table, by property2)
16
+ *
17
+ * Empty sections:
18
+ * - If a section is empty, write `None`.
19
+ */
20
+ export function parseOutputFormatSpec(md) {
21
+ const sections = [];
22
+ const tables = [];
23
+ let emptySectionValue;
24
+ const lines = md.split("\n");
25
+ for (const line of lines) {
26
+ const trimmed = line.trim();
27
+ // Parse section definitions: "- Section name — kind"
28
+ const sectionMatch = trimmed.match(/^-\s+(.+?)\s+[—–-]\s+(prose|list|ordered list)(.*)$/i);
29
+ if (sectionMatch) {
30
+ const name = sectionMatch[1].trim();
31
+ const kindStr = sectionMatch[2].trim().toLowerCase().replace(/\s+/g, "_");
32
+ const kind = kindStr;
33
+ const hint = sectionMatch[3]?.trim();
34
+ sections.push({ name, kind, hint: hint || undefined });
35
+ continue;
36
+ }
37
+ // Parse table definitions: "- (col1, col2, col3 — table)" or "- (col1, col2 — ordered table, by col1)"
38
+ const tableMatch = trimmed.match(/^-\s+\(([^)]+)\s+[—–-]\s+(table|ordered table)(?:,\s*by\s+([^)]+))?\)$/i);
39
+ if (tableMatch) {
40
+ const columnsStr = tableMatch[1].trim();
41
+ const columns = columnsStr.split(",").map(c => c.trim());
42
+ const kindStr = tableMatch[2].trim().toLowerCase().replace(/\s+/g, "_");
43
+ const kind = kindStr;
44
+ const by = tableMatch[3]?.trim();
45
+ tables.push({ columns, kind, by });
46
+ continue;
47
+ }
48
+ // Parse empty section value: "If a section is empty, write `None`."
49
+ const emptyMatch = trimmed.match(/If a section is empty.*?`([^`]+)`/i);
50
+ if (emptyMatch) {
51
+ emptySectionValue = emptyMatch[1];
52
+ continue;
53
+ }
54
+ }
55
+ return {
56
+ descriptorType: "output_format_spec",
57
+ format: "markdown",
58
+ sectionOrderMatters: false,
59
+ sections,
60
+ tablesOptional: true,
61
+ tables,
62
+ emptySectionValue
63
+ };
64
+ }
@@ -0,0 +1,5 @@
1
+ import type { OutputFormatSpec } from "../types.js";
2
+ /**
3
+ * Convert an OutputFormatSpec to canonical Markdown format.
4
+ */
5
+ export declare function stringifyOutputFormatSpec(spec: OutputFormatSpec): string;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Convert an OutputFormatSpec to canonical Markdown format.
3
+ */
4
+ export function stringifyOutputFormatSpec(spec) {
5
+ const parts = [];
6
+ parts.push("## Output format (Markdown)\n");
7
+ parts.push("Include these sections somewhere (order does not matter):\n\n");
8
+ // Sections
9
+ for (const section of spec.sections) {
10
+ const kindStr = section.kind.replace(/_/g, " ");
11
+ const hint = section.hint ? ` ${section.hint}` : "";
12
+ parts.push(`- ${section.name} — ${kindStr}${hint}\n`);
13
+ }
14
+ // Tables
15
+ if (spec.tables.length > 0) {
16
+ parts.push("\nTables (only if needed):\n");
17
+ for (const table of spec.tables) {
18
+ const kindStr = table.kind.replace(/_/g, " ");
19
+ const cols = table.columns.join(", ");
20
+ const byStr = table.by ? `, by ${table.by}` : "";
21
+ parts.push(`- (${cols} — ${kindStr}${byStr})\n`);
22
+ }
23
+ }
24
+ // Empty section value
25
+ if (spec.emptySectionValue) {
26
+ parts.push("\nEmpty sections:\n");
27
+ parts.push(`- If a section is empty, write \`${spec.emptySectionValue}\`.\n`);
28
+ }
29
+ return parts.join("");
30
+ }
@@ -0,0 +1,10 @@
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;
@@ -0,0 +1,91 @@
1
+ import { buildOutline } from "../outline/builder.js";
2
+ /**
3
+ * Validate Markdown output against an OutputFormatSpec.
4
+ */
5
+ export function validateOutput(md, spec) {
6
+ const outline = buildOutline(md);
7
+ const errors = [];
8
+ const warnings = [];
9
+ // Index nodes by normalized title
10
+ const matches = collectMatches(outline.nodes);
11
+ // Validate each required section
12
+ for (const section of spec.sections) {
13
+ const key = normalizeTitle(section.name);
14
+ const nodes = matches.get(key) ?? [];
15
+ if (nodes.length === 0) {
16
+ errors.push(`missing_section:${section.name}`);
17
+ continue;
18
+ }
19
+ // Choose best node (highest level, i.e., smallest level number)
20
+ const chosen = chooseBestNode(nodes);
21
+ const body = chosen.content_md.trim();
22
+ // Check for empty sections
23
+ if (spec.emptySectionValue) {
24
+ if (body === "" && !normalizeNone(body, spec.emptySectionValue)) {
25
+ errors.push(`empty_section_without_none:${section.name}`);
26
+ }
27
+ // If it's "None", skip further validation
28
+ if (normalizeNone(body, spec.emptySectionValue)) {
29
+ continue;
30
+ }
31
+ }
32
+ // Validate section kind
33
+ if (section.kind === "list") {
34
+ if (!/^\s*-\s+/m.test(body)) {
35
+ errors.push(`section_not_bullets:${section.name}`);
36
+ }
37
+ }
38
+ if (section.kind === "ordered_list") {
39
+ if (!/^\s*\d+\.\s+/m.test(body)) {
40
+ errors.push(`section_not_numbered:${section.name}`);
41
+ }
42
+ }
43
+ }
44
+ // Validate tables (if any)
45
+ // Note: Full table validation would require parsing tables from the markdown
46
+ // For now, we just check if tables exist when required
47
+ // This is a simplified version; full implementation would use extractAllTables
48
+ return {
49
+ ok: errors.length === 0,
50
+ errors,
51
+ warnings
52
+ };
53
+ }
54
+ /**
55
+ * Collect all nodes by normalized title.
56
+ */
57
+ function collectMatches(nodes) {
58
+ const map = new Map();
59
+ function visit(node) {
60
+ const key = normalizeTitle(node.title);
61
+ const existing = map.get(key) ?? [];
62
+ existing.push(node);
63
+ map.set(key, existing);
64
+ node.children.forEach(visit);
65
+ }
66
+ nodes.forEach(visit);
67
+ return map;
68
+ }
69
+ /**
70
+ * Choose the best node from multiple matches.
71
+ * Prefer highest-level heading (smallest level number).
72
+ */
73
+ function chooseBestNode(nodes) {
74
+ if (nodes.length === 1)
75
+ return nodes[0];
76
+ // Sort by level (ascending) and take first
77
+ const sorted = [...nodes].sort((a, b) => a.level - b.level);
78
+ return sorted[0];
79
+ }
80
+ /**
81
+ * Normalize title for comparison: lowercase, remove trailing punctuation.
82
+ */
83
+ function normalizeTitle(t) {
84
+ return t.trim().replace(/[:\-–—]\s*$/, "").trim().toLowerCase();
85
+ }
86
+ /**
87
+ * Check if body is the "None" value.
88
+ */
89
+ function normalizeNone(body, noneValue) {
90
+ return body.trim().toLowerCase() === noneValue.toLowerCase();
91
+ }
@@ -0,0 +1,10 @@
1
+ import type { MdOutline } from "../types.js";
2
+ /**
3
+ * Build a nested outline tree from Markdown headings.
4
+ * Accepts any heading level (#..######) and builds parent/child relationships.
5
+ */
6
+ export declare function buildOutline(md: string): MdOutline;
7
+ /**
8
+ * Convert title to slug: lowercase, replace spaces with _, remove special chars
9
+ */
10
+ export declare function slugify(t: string): string;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Build a nested outline tree from Markdown headings.
3
+ * Accepts any heading level (#..######) and builds parent/child relationships.
4
+ */
5
+ export function buildOutline(md) {
6
+ const lines = md.split("\n");
7
+ const nodes = [];
8
+ const stack = [];
9
+ // Collect heading positions
10
+ const headings = [];
11
+ for (let i = 0; i < lines.length; i++) {
12
+ const m = lines[i].match(/^(#{1,6})\s+(.+)\s*$/);
13
+ if (m) {
14
+ headings.push({
15
+ idx: i,
16
+ level: m[1].length,
17
+ title: cleanTitle(m[2])
18
+ });
19
+ }
20
+ }
21
+ // If no headings, return empty outline
22
+ if (headings.length === 0) {
23
+ return { type: "md_outline", nodes: [] };
24
+ }
25
+ // Build tree using stack-based algorithm
26
+ for (let h = 0; h < headings.length; h++) {
27
+ const cur = headings[h];
28
+ const next = headings[h + 1];
29
+ const contentStart = cur.idx + 1;
30
+ const contentEnd = next ? next.idx : lines.length;
31
+ const content_md = lines.slice(contentStart, contentEnd).join("\n").trimEnd() + "\n";
32
+ const node = {
33
+ title: cur.title,
34
+ level: cur.level,
35
+ key: "", // filled later
36
+ content_md,
37
+ children: []
38
+ };
39
+ // Attach using stack: pop nodes with level >= current level
40
+ while (stack.length && stack[stack.length - 1].level >= node.level) {
41
+ stack.pop();
42
+ }
43
+ if (!stack.length) {
44
+ nodes.push(node);
45
+ }
46
+ else {
47
+ stack[stack.length - 1].children.push(node);
48
+ }
49
+ stack.push(node);
50
+ }
51
+ // Fill keys deterministically (slug + dedup)
52
+ assignKeys(nodes);
53
+ return { type: "md_outline", nodes };
54
+ }
55
+ /**
56
+ * Clean heading title: remove trailing punctuation like :, -, –, —
57
+ */
58
+ function cleanTitle(t) {
59
+ return t.trim().replace(/[:\-–—]\s*$/, "").trim();
60
+ }
61
+ /**
62
+ * Convert title to slug: lowercase, replace spaces with _, remove special chars
63
+ */
64
+ export function slugify(t) {
65
+ return t.toLowerCase()
66
+ .replace(/[:\-–—]+$/g, "")
67
+ .replace(/\s+/g, "_")
68
+ .replace(/[^a-z0-9_]/g, "")
69
+ .replace(/_+/g, "_")
70
+ .replace(/^_+|_+$/g, "");
71
+ }
72
+ /**
73
+ * Assign unique keys to all nodes in the tree.
74
+ * Uses slugified titles with deduplication (e.g., "section", "section__2", "section__3")
75
+ */
76
+ function assignKeys(nodes, seen = new Map()) {
77
+ const visit = (n) => {
78
+ const base = slugify(n.title) || "section";
79
+ const count = (seen.get(base) ?? 0) + 1;
80
+ seen.set(base, count);
81
+ n.key = count === 1 ? base : `${base}__${count}`;
82
+ n.children.forEach(visit);
83
+ };
84
+ nodes.forEach(visit);
85
+ }
@@ -0,0 +1,6 @@
1
+ import type { MdOutline } from "../types.js";
2
+ /**
3
+ * Render an outline tree back to Markdown.
4
+ * Never renders internal keys, ids, or dedup suffixes.
5
+ */
6
+ export declare function renderOutline(outline: MdOutline): string;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Render an outline tree back to Markdown.
3
+ * Never renders internal keys, ids, or dedup suffixes.
4
+ */
5
+ export function renderOutline(outline) {
6
+ const parts = [];
7
+ function renderNode(node) {
8
+ // Render heading
9
+ const hashes = "#".repeat(node.level);
10
+ parts.push(`${hashes} ${node.title}\n`);
11
+ // Render content
12
+ if (node.content_md && node.content_md.trim()) {
13
+ parts.push(node.content_md);
14
+ if (!node.content_md.endsWith("\n")) {
15
+ parts.push("\n");
16
+ }
17
+ }
18
+ // Render children recursively
19
+ node.children.forEach(renderNode);
20
+ }
21
+ outline.nodes.forEach(renderNode);
22
+ return parts.join("");
23
+ }
package/dist/parser.js CHANGED
@@ -25,16 +25,64 @@ function parseHeader(inner) {
25
25
  }
26
26
  return out;
27
27
  }
28
- function parseMetaValue(key, value, arrayKeys) {
28
+ function parseMetaValue(key, value, arrayKeys, options) {
29
29
  const v = value.trim();
30
- if (!arrayKeys.has(key))
31
- return v;
32
- // comma-separated
33
- const parts = v
34
- .split(",")
35
- .map((p) => p.trim())
36
- .filter((p) => p.length > 0);
37
- return parts;
30
+ // Handle array keys first
31
+ if (arrayKeys.has(key)) {
32
+ const parts = v
33
+ .split(",")
34
+ .map((p) => p.trim())
35
+ .filter((p) => p.length > 0);
36
+ return parts;
37
+ }
38
+ // Apply type mode
39
+ const mode = options.metaTypeMode ?? "strings";
40
+ if (mode === "schema" && options.metaSchema?.[key]) {
41
+ return parseWithSchema(v, options.metaSchema[key]);
42
+ }
43
+ if (mode === "infer") {
44
+ return inferType(v);
45
+ }
46
+ // Default: strings mode
47
+ return v;
48
+ }
49
+ /**
50
+ * Parse value according to schema type.
51
+ */
52
+ function parseWithSchema(value, type) {
53
+ switch (type) {
54
+ case "boolean":
55
+ return value.toLowerCase() === "true";
56
+ case "null":
57
+ return null;
58
+ case "number": {
59
+ const num = Number(value);
60
+ return isNaN(num) ? value : num;
61
+ }
62
+ default:
63
+ return value;
64
+ }
65
+ }
66
+ /**
67
+ * Safely infer type from string value.
68
+ */
69
+ function inferType(value) {
70
+ const lower = value.toLowerCase();
71
+ // Boolean
72
+ if (lower === "true")
73
+ return true;
74
+ if (lower === "false")
75
+ return false;
76
+ // Null
77
+ if (lower === "null")
78
+ return null;
79
+ // Number (avoid leading zeros like "0012" unless it's just "0" or "0.xxx")
80
+ if (/^-?\d+(\.\d+)?$/.test(value) && !/^0\d/.test(value)) {
81
+ const num = Number(value);
82
+ if (!isNaN(num))
83
+ return num;
84
+ }
85
+ return value;
38
86
  }
39
87
  function tryParsePayload(lang, raw) {
40
88
  const l = (lang ?? "").toLowerCase();
@@ -128,7 +176,7 @@ export function parseFlexMd(input, options = {}) {
128
176
  const key = mm[1].trim();
129
177
  const value = mm[2] ?? "";
130
178
  cur.meta ??= {};
131
- cur.meta[key] = parseMetaValue(key, value, arrayKeys);
179
+ cur.meta[key] = parseMetaValue(key, value, arrayKeys, options);
132
180
  i++;
133
181
  continue;
134
182
  }
@@ -0,0 +1,6 @@
1
+ import type { ParsedList } from "../types.js";
2
+ /**
3
+ * Parse nested Markdown lists into a tree structure.
4
+ * Supports both unordered (-) and ordered (1.) lists.
5
+ */
6
+ export declare function parseList(md: string): ParsedList | null;