flex-md 1.1.0 → 3.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.
Files changed (84) hide show
  1. package/README.md +75 -29
  2. package/SPEC.md +559 -0
  3. package/dist/__tests__/validate.test.d.ts +1 -0
  4. package/dist/__tests__/validate.test.js +108 -0
  5. package/dist/detect/json/detectIntent.d.ts +2 -0
  6. package/dist/detect/json/detectIntent.js +79 -0
  7. package/dist/detect/json/detectPresence.d.ts +6 -0
  8. package/dist/detect/json/detectPresence.js +191 -0
  9. package/dist/detect/json/index.d.ts +7 -0
  10. package/dist/detect/json/index.js +12 -0
  11. package/dist/detect/json/types.d.ts +43 -0
  12. package/dist/detect/json/types.js +1 -0
  13. package/dist/detection/detector.d.ts +6 -0
  14. package/dist/detection/detector.js +104 -0
  15. package/dist/detection/extractor.d.ts +10 -0
  16. package/dist/detection/extractor.js +54 -0
  17. package/dist/extract/extract.d.ts +5 -0
  18. package/dist/extract/extract.js +50 -0
  19. package/dist/extract/types.d.ts +11 -0
  20. package/dist/extract/types.js +1 -0
  21. package/dist/index.d.ts +13 -3
  22. package/dist/index.js +20 -3
  23. package/dist/issues/build.d.ts +26 -0
  24. package/dist/issues/build.js +62 -0
  25. package/dist/md/lists.d.ts +14 -0
  26. package/dist/md/lists.js +33 -0
  27. package/dist/md/match.d.ts +12 -0
  28. package/dist/md/match.js +44 -0
  29. package/dist/md/outline.d.ts +6 -0
  30. package/dist/md/outline.js +67 -0
  31. package/dist/md/parse.d.ts +29 -0
  32. package/dist/md/parse.js +105 -0
  33. package/dist/md/tables.d.ts +25 -0
  34. package/dist/md/tables.js +72 -0
  35. package/dist/ofs/enricher.d.ts +16 -0
  36. package/dist/ofs/enricher.js +77 -0
  37. package/dist/ofs/extractor.d.ts +9 -0
  38. package/dist/ofs/extractor.js +75 -0
  39. package/dist/ofs/issues.d.ts +14 -0
  40. package/dist/ofs/issues.js +92 -0
  41. package/dist/ofs/issuesEnvelope.d.ts +15 -0
  42. package/dist/ofs/issuesEnvelope.js +71 -0
  43. package/dist/ofs/parser.d.ts +9 -0
  44. package/dist/ofs/parser.js +133 -0
  45. package/dist/ofs/stringify.d.ts +5 -0
  46. package/dist/ofs/stringify.js +32 -0
  47. package/dist/ofs/validator.d.ts +10 -0
  48. package/dist/ofs/validator.js +91 -0
  49. package/dist/outline/builder.d.ts +10 -0
  50. package/dist/outline/builder.js +85 -0
  51. package/dist/outline/renderer.d.ts +6 -0
  52. package/dist/outline/renderer.js +23 -0
  53. package/dist/parser.js +58 -10
  54. package/dist/parsers/lists.d.ts +6 -0
  55. package/dist/parsers/lists.js +36 -0
  56. package/dist/parsers/tables.d.ts +10 -0
  57. package/dist/parsers/tables.js +58 -0
  58. package/dist/pipeline/enforce.d.ts +10 -0
  59. package/dist/pipeline/enforce.js +46 -0
  60. package/dist/pipeline/kind.d.ts +16 -0
  61. package/dist/pipeline/kind.js +24 -0
  62. package/dist/pipeline/repair.d.ts +14 -0
  63. package/dist/pipeline/repair.js +112 -0
  64. package/dist/strictness/container.d.ts +14 -0
  65. package/dist/strictness/container.js +46 -0
  66. package/dist/strictness/processor.d.ts +5 -0
  67. package/dist/strictness/processor.js +29 -0
  68. package/dist/strictness/types.d.ts +77 -0
  69. package/dist/strictness/types.js +106 -0
  70. package/dist/test-pipeline.d.ts +1 -0
  71. package/dist/test-pipeline.js +53 -0
  72. package/dist/test-runner.d.ts +1 -0
  73. package/dist/test-runner.js +331 -0
  74. package/dist/test-strictness.d.ts +1 -0
  75. package/dist/test-strictness.js +213 -0
  76. package/dist/types.d.ts +140 -22
  77. package/dist/validate/policy.d.ts +10 -0
  78. package/dist/validate/policy.js +17 -0
  79. package/dist/validate/types.d.ts +11 -0
  80. package/dist/validate/types.js +1 -0
  81. package/dist/validate/validate.d.ts +2 -0
  82. package/dist/validate/validate.js +308 -0
  83. package/docs/mdflex-compliance.md +216 -0
  84. package/package.json +15 -6
@@ -0,0 +1,77 @@
1
+ export type StrictnessLevel = 0 | 1 | 2 | 3;
2
+ export type ContainerMode = "none" | "markdown_fence" | "flexmd_fence";
3
+ import { Enforcement } from "../types.js";
4
+ export type { Enforcement };
5
+ /**
6
+ * Semi-strict enforcement policy.
7
+ * Lets you be strict about some aspects (e.g. container) and lenient about others (e.g. optional sections).
8
+ */
9
+ export interface SemiStrictPolicy {
10
+ outsideContainer?: Enforcement;
11
+ missingContainer?: Enforcement;
12
+ multipleContainers?: Enforcement;
13
+ missingOfs?: Enforcement;
14
+ missingRequiredSection?: Enforcement;
15
+ missingOptionalSection?: Enforcement;
16
+ wrongSectionKindRequired?: Enforcement;
17
+ wrongSectionKindOptional?: Enforcement;
18
+ emptySectionWithoutNoneRequired?: Enforcement;
19
+ emptySectionWithoutNoneOptional?: Enforcement;
20
+ missingRequiredTable?: Enforcement;
21
+ missingOptionalTable?: Enforcement;
22
+ orderedTableViolationsRequired?: Enforcement;
23
+ orderedTableViolationsOptional?: Enforcement;
24
+ jsonPayloadMissing?: Enforcement;
25
+ }
26
+ export type GuidanceMode = "auto" | "always" | "never";
27
+ export interface StrictnessOptions {
28
+ level: StrictnessLevel;
29
+ container?: "none" | "markdown_fence" | "flexmd_fence";
30
+ requireSingleContainer?: boolean;
31
+ requireOfs?: boolean;
32
+ requireAllSections?: boolean;
33
+ enforceEmptyNone?: boolean;
34
+ acceptAnyHeadingLevel?: boolean;
35
+ duplicateSectionStrategy?: "merge" | "first";
36
+ requireTypedJson?: boolean;
37
+ jsonSchema?: unknown;
38
+ jsonPayloadHeading?: string;
39
+ /**
40
+ * Controls whether prompt enrichment should mention table rules.
41
+ * - auto: only when required/likely
42
+ * - always: always include table guidance if spec declares tables
43
+ * - never: never include table guidance
44
+ */
45
+ tablesGuidance?: GuidanceMode;
46
+ /**
47
+ * Controls whether to mention list formatting rules in the prompt.
48
+ * - auto: mention only if the spec includes list/ordered-list sections (and L1+)
49
+ * - always: mention whenever such sections exist, regardless of strictness level
50
+ * - never: never mention list rules
51
+ */
52
+ listsGuidance?: GuidanceMode;
53
+ /**
54
+ * Controls whether to mention the "empty sections => None" rule.
55
+ * - auto: mention only when spec.emptySectionValue exists and L1+
56
+ * - always: always mention if emptySectionValue exists
57
+ * - never: never mention it
58
+ */
59
+ emptyNoneGuidance?: GuidanceMode;
60
+ /**
61
+ * Controls whether to mention the container rule ("single fenced block").
62
+ * - auto: mention only at L2+
63
+ * - always: always mention (even at L1)
64
+ * - never: never mention (not recommended if L2+)
65
+ */
66
+ containerGuidance?: GuidanceMode;
67
+ /**
68
+ * Controls whether to mention the typed JSON rule for L3.
69
+ * - auto: mention only at L3 when requireTypedJson=true
70
+ * - always: always mention at L3
71
+ * - never: never mention (not recommended if you require it)
72
+ */
73
+ typedJsonGuidance?: GuidanceMode;
74
+ /** Optional semi-strict policy overrides. */
75
+ policy?: SemiStrictPolicy;
76
+ }
77
+ export declare function strictnessDefaults(level: StrictnessLevel): StrictnessOptions;
@@ -0,0 +1,106 @@
1
+ export function strictnessDefaults(level) {
2
+ const common = {
3
+ tablesGuidance: "auto",
4
+ listsGuidance: "auto",
5
+ emptyNoneGuidance: "auto",
6
+ containerGuidance: "auto",
7
+ typedJsonGuidance: "auto",
8
+ acceptAnyHeadingLevel: true,
9
+ duplicateSectionStrategy: "merge",
10
+ };
11
+ if (level === 0) {
12
+ return {
13
+ ...common,
14
+ level,
15
+ container: "none",
16
+ requireOfs: false,
17
+ requireAllSections: false,
18
+ policy: {
19
+ outsideContainer: "ignore",
20
+ missingContainer: "ignore",
21
+ multipleContainers: "ignore",
22
+ missingRequiredSection: "warn",
23
+ missingOptionalSection: "ignore",
24
+ wrongSectionKindRequired: "warn",
25
+ wrongSectionKindOptional: "ignore",
26
+ emptySectionWithoutNoneRequired: "warn",
27
+ emptySectionWithoutNoneOptional: "ignore",
28
+ missingRequiredTable: "warn",
29
+ missingOptionalTable: "ignore",
30
+ orderedTableViolationsRequired: "warn",
31
+ orderedTableViolationsOptional: "ignore",
32
+ }
33
+ };
34
+ }
35
+ if (level === 1) {
36
+ return {
37
+ ...common,
38
+ level,
39
+ container: "none",
40
+ requireOfs: true,
41
+ requireAllSections: true,
42
+ policy: {
43
+ missingRequiredSection: "error",
44
+ missingOptionalSection: "ignore",
45
+ wrongSectionKindRequired: "error",
46
+ wrongSectionKindOptional: "warn",
47
+ emptySectionWithoutNoneRequired: "error",
48
+ emptySectionWithoutNoneOptional: "warn",
49
+ missingRequiredTable: "error",
50
+ missingOptionalTable: "ignore",
51
+ orderedTableViolationsRequired: "error",
52
+ orderedTableViolationsOptional: "warn",
53
+ }
54
+ };
55
+ }
56
+ if (level === 2) {
57
+ return {
58
+ ...common,
59
+ level,
60
+ container: "markdown_fence",
61
+ requireSingleContainer: true,
62
+ requireOfs: true,
63
+ requireAllSections: true,
64
+ policy: {
65
+ outsideContainer: "error",
66
+ missingContainer: "error",
67
+ multipleContainers: "error",
68
+ missingRequiredSection: "error",
69
+ missingOptionalSection: "ignore",
70
+ wrongSectionKindRequired: "error",
71
+ wrongSectionKindOptional: "warn",
72
+ emptySectionWithoutNoneRequired: "error",
73
+ emptySectionWithoutNoneOptional: "warn",
74
+ missingRequiredTable: "error",
75
+ missingOptionalTable: "ignore",
76
+ orderedTableViolationsRequired: "error",
77
+ orderedTableViolationsOptional: "warn",
78
+ }
79
+ };
80
+ }
81
+ return {
82
+ ...common,
83
+ level: 3,
84
+ container: "markdown_fence",
85
+ requireSingleContainer: true,
86
+ requireOfs: true,
87
+ requireAllSections: true,
88
+ requireTypedJson: true,
89
+ policy: {
90
+ outsideContainer: "error",
91
+ missingContainer: "error",
92
+ multipleContainers: "error",
93
+ missingRequiredSection: "error",
94
+ missingOptionalSection: "ignore",
95
+ wrongSectionKindRequired: "error",
96
+ wrongSectionKindOptional: "warn",
97
+ emptySectionWithoutNoneRequired: "error",
98
+ emptySectionWithoutNoneOptional: "warn",
99
+ missingRequiredTable: "error",
100
+ missingOptionalTable: "ignore",
101
+ orderedTableViolationsRequired: "error",
102
+ orderedTableViolationsOptional: "warn",
103
+ jsonPayloadMissing: "error"
104
+ }
105
+ };
106
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ import { enforceFlexMd, parseOutputFormatSpec, detectResponseKind } from "./index.js";
2
+ function assert(condition, message) {
3
+ if (!condition) {
4
+ throw new Error(`Assertion failed: ${message}`);
5
+ }
6
+ }
7
+ console.log("🧪 Running Pipeline & JSON Detection Tests\n");
8
+ const spec = parseOutputFormatSpec(`
9
+ ## Output format
10
+ - Answer — prose (required)
11
+ - Reasoning — ordered list (optional)
12
+ `);
13
+ // 1. Detection Kind
14
+ console.log("--- Detection Kind ---");
15
+ const mdResp = "## Answer\nThis is an answer.";
16
+ const kind1 = detectResponseKind(mdResp, spec);
17
+ console.log("FlexMD Kind:", kind1.kind);
18
+ assert(kind1.kind === "flexmd", "Should detect flexmd");
19
+ const issuesResp = "## Status\n- status: error\n- code: fail\n- message: fail\n\n## Issues\n- issue: x";
20
+ const kind2 = detectResponseKind(issuesResp, spec);
21
+ console.log("Issues Kind:", kind2.kind);
22
+ assert(kind2.kind === "issues", "Should detect issues");
23
+ const plainResp = "Just some text.";
24
+ const kind3 = detectResponseKind(plainResp, spec);
25
+ console.log("Markdown Kind:", kind3.kind);
26
+ assert(kind3.kind === "markdown", "Should detect markdown");
27
+ // 2. JSON fixing
28
+ console.log("\n--- JSON Fixing ---");
29
+ const jsonResp = "```json\n{\n \"Answer\": \"Parsed from JSON\",\n \"Reasoning\": [\"Step 1\", \"Step 2\"]\n}\n```";
30
+ const resultJson = enforceFlexMd(jsonResp, spec, { level: 1 });
31
+ console.log("JSON Fixed OK:", resultJson.ok);
32
+ console.log("JSON Fixed Content:\n", resultJson.extracted?.sectionsByName["Answer"]?.md);
33
+ assert(resultJson.ok === true, "Enforce should succeed with fixed JSON");
34
+ assert(!!resultJson.extracted?.sectionsByName["Answer"]?.md.includes("Parsed from JSON"), "Should extract from fixed JSON");
35
+ assert(resultJson.extracted?.sectionsByName["Reasoning"]?.list?.items.length === 2, "Should extract list from fixed JSON");
36
+ // 3. Raw JSON object
37
+ console.log("\n--- Raw JSON Object ---");
38
+ const rawJson = "{\"Answer\": \"Raw JSON\"}";
39
+ const resultRaw = enforceFlexMd(rawJson, spec, { level: 1 });
40
+ console.log("Raw JSON Fixed OK:", resultRaw.ok);
41
+ assert(resultRaw.ok === true, "Should fix raw JSON");
42
+ // 4. Missing sections fixing
43
+ console.log("\n--- Missing Sections ---");
44
+ // The fixer (repairToMarkdownLevel) SHOULD add missing required sections.
45
+ const failingResp = "## SomeOtherSection\nContent";
46
+ const resultFixed = enforceFlexMd(failingResp, spec, { level: 1 });
47
+ console.log("Auto-Repaired OK:", resultFixed.ok);
48
+ console.log("Repaired Content Contains 'Answer':", resultFixed.outputText.includes("## Answer"));
49
+ console.log("Repaired Content Contains 'None':", resultFixed.outputText.includes("None"));
50
+ assert(resultFixed.ok === true, "Should be ok if required sections are auto-repaired");
51
+ assert(resultFixed.outputText.includes("## Answer"), "Should have added 'Answer' heading");
52
+ assert(resultFixed.outputText.includes("None"), "Should have filled with 'None'");
53
+ console.log("\nāœ… Pipeline Tests Passed");
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,331 @@
1
+ import {
2
+ // Layer A
3
+ parseFlexMd, stringifyFlexMd, validateFlexMd,
4
+ // Layer B
5
+ parseOutputFormatSpec, stringifyOutputFormatSpec, enrichInstructions, validateMarkdownAgainstOfs as validateOutput, extractFromMarkdown as extractOutput,
6
+ // Outline
7
+ buildOutline, renderOutline,
8
+ // Parsers
9
+ parseNestedList as parseList, parsePipeTable, extractPipeTables as extractAllTables, enforceOrderedTable,
10
+ // Strictness
11
+ strictnessDefaults,
12
+ // Layer C
13
+ detectObjects, parseAny } from "./index.js";
14
+ let passed = 0;
15
+ let failed = 0;
16
+ function test(name, fn) {
17
+ try {
18
+ fn();
19
+ console.log(`āœ… ${name}`);
20
+ passed++;
21
+ }
22
+ catch (e) {
23
+ console.error(`āŒ ${name}`);
24
+ console.error(e);
25
+ failed++;
26
+ }
27
+ }
28
+ function assert(condition, message) {
29
+ if (!condition) {
30
+ throw new Error(`Assertion failed: ${message}`);
31
+ }
32
+ }
33
+ console.log("🧪 Running FlexMD v1.1 Tests\n");
34
+ // ============================================================================
35
+ // Layer A Tests
36
+ // ============================================================================
37
+ console.log("šŸ“¦ Layer A - FlexMD Frames\n");
38
+ test("Parse basic FlexMD frame", () => {
39
+ const md = `[[message role=user id=m1]]
40
+ @tags: auth, login
41
+ Hello world
42
+ `;
43
+ const doc = parseFlexMd(md);
44
+ assert(doc.frames.length === 1, "Should have 1 frame");
45
+ assert(doc.frames[0].type === "message", "Type should be message");
46
+ assert(doc.frames[0].role === "user", "Role should be user");
47
+ assert(Array.isArray(doc.frames[0].meta?.tags), "Tags should be array");
48
+ });
49
+ test("Stringify FlexMD frame", () => {
50
+ const doc = {
51
+ frames: [{
52
+ type: "message",
53
+ role: "user",
54
+ id: "m1",
55
+ meta: { tags: ["auth", "login"] },
56
+ body_md: "Hello world\n"
57
+ }]
58
+ };
59
+ const md = stringifyFlexMd(doc);
60
+ assert(md.includes("[[message"), "Should contain frame header");
61
+ assert(md.includes("@tags:"), "Should contain meta");
62
+ });
63
+ test("Parse with type inference", () => {
64
+ const md = `[[message]]
65
+ @priority: 5
66
+ @enabled: true
67
+ @value: null
68
+ Body text
69
+ `;
70
+ const doc = parseFlexMd(md, { metaTypeMode: "infer" });
71
+ assert(doc.frames[0].meta?.priority === 5, "Should infer number");
72
+ assert(doc.frames[0].meta?.enabled === true, "Should infer boolean");
73
+ assert(doc.frames[0].meta?.value === null, "Should infer null");
74
+ });
75
+ test("Validate FlexMD", () => {
76
+ const valid = `[[message role=user]]
77
+ Hello
78
+ `;
79
+ const result = validateFlexMd(valid);
80
+ assert(result.valid === true, "Should be valid");
81
+ });
82
+ // ============================================================================
83
+ // Layer B Tests
84
+ // ============================================================================
85
+ console.log("\nšŸ“‹ Layer B - Output Format Spec\n");
86
+ test("Parse OFS", () => {
87
+ const md = `## Output format (Markdown)
88
+ Include these sections somewhere (order does not matter):
89
+
90
+ - Short answer — prose
91
+ - Reasoning — ordered list
92
+ - Assumptions — list
93
+ `;
94
+ const spec = parseOutputFormatSpec(md);
95
+ assert(spec !== null, "Spec should not be null");
96
+ assert(spec.sections.length === 3, "Should have 3 sections");
97
+ assert(spec.sections[0].name === "Short answer", "First section name");
98
+ assert(spec.sections[0].kind === "prose", "First section kind");
99
+ assert(spec.sections[1].kind === "ordered_list", "Second section kind");
100
+ });
101
+ test("Stringify OFS", () => {
102
+ const spec = {
103
+ descriptorType: "output_format_spec",
104
+ format: "markdown",
105
+ sectionOrderMatters: false,
106
+ sections: [
107
+ { name: "Answer", kind: "prose" }
108
+ ],
109
+ tablesOptional: true,
110
+ tables: [],
111
+ emptySectionValue: "None"
112
+ };
113
+ const md = stringifyOutputFormatSpec(spec);
114
+ assert(md.includes("Answer — prose"), "Should contain section");
115
+ assert(md.includes("None"), "Should contain empty value");
116
+ });
117
+ test("Enrich instructions", () => {
118
+ const spec = {
119
+ descriptorType: "output_format_spec",
120
+ format: "markdown",
121
+ sectionOrderMatters: false,
122
+ sections: [
123
+ { name: "Steps", kind: "ordered_list" },
124
+ { name: "Notes", kind: "list" }
125
+ ],
126
+ tablesOptional: true,
127
+ tables: [],
128
+ emptySectionValue: "None"
129
+ };
130
+ const instructions = enrichInstructions(spec, strictnessDefaults(1));
131
+ assert(instructions.includes("numbered items"), "Should mention ordered lists");
132
+ assert(instructions.includes("bullets"), "Should mention lists");
133
+ assert(instructions.includes("None"), "Should mention empty value");
134
+ });
135
+ test("Validate output against OFS", () => {
136
+ const spec = {
137
+ descriptorType: "output_format_spec",
138
+ format: "markdown",
139
+ sectionOrderMatters: false,
140
+ sections: [
141
+ { name: "Answer", kind: "prose" }
142
+ ],
143
+ tablesOptional: true,
144
+ tables: []
145
+ };
146
+ const md = `## Answer\nThe answer is 42.`;
147
+ const result = validateOutput(md, spec, strictnessDefaults(1));
148
+ assert(result.ok === true, "Should validate successfully");
149
+ });
150
+ test("Extract output from OFS", () => {
151
+ const spec = {
152
+ descriptorType: "output_format_spec",
153
+ format: "markdown",
154
+ sectionOrderMatters: false,
155
+ sections: [
156
+ { name: "Answer", kind: "prose" },
157
+ { name: "Steps", kind: "ordered_list" }
158
+ ],
159
+ tablesOptional: true,
160
+ tables: []
161
+ };
162
+ const md = `## Answer
163
+ The answer is 42.
164
+
165
+ ## Steps
166
+ 1. First step
167
+ 2. Second step
168
+ `;
169
+ const extracted = extractOutput(md, spec, strictnessDefaults(1));
170
+ assert(extracted.sectionsByName["Answer"].md.includes("42"), "Should extract answer");
171
+ assert(extracted.sectionsByName["Steps"].list?.ordered === true, "Should parse ordered list");
172
+ assert(extracted.sectionsByName["Steps"].list?.items.length === 2, "Should have 2 items");
173
+ });
174
+ // ============================================================================
175
+ // Outline Tests
176
+ // ============================================================================
177
+ console.log("\n🌳 Outline & Tree Building\n");
178
+ test("Build outline from headings", () => {
179
+ const md = `# Title
180
+ Content
181
+
182
+ ## Section 1
183
+ More content
184
+
185
+ ### Subsection 1.1
186
+ Details
187
+
188
+ ## Section 2
189
+ Other content
190
+ `;
191
+ const outline = buildOutline(md);
192
+ assert(outline.nodes.length === 1, "Should have 1 root node");
193
+ assert(outline.nodes[0].title === "Title", "Root title");
194
+ assert(outline.nodes[0].children.length === 2, "Should have 2 children");
195
+ assert(outline.nodes[0].children[0].children.length === 1, "First child has 1 child");
196
+ });
197
+ test("Render outline back to Markdown", () => {
198
+ const outline = {
199
+ type: "md_outline",
200
+ nodes: [{
201
+ title: "Title",
202
+ level: 1,
203
+ key: "title",
204
+ content_md: "Content\n",
205
+ children: [{
206
+ title: "Section",
207
+ level: 2,
208
+ key: "section",
209
+ content_md: "More\n",
210
+ children: []
211
+ }]
212
+ }]
213
+ };
214
+ const md = renderOutline(outline);
215
+ assert(md.includes("# Title"), "Should have h1");
216
+ assert(md.includes("## Section"), "Should have h2");
217
+ assert(!md.includes("key"), "Should not include internal keys");
218
+ });
219
+ // ============================================================================
220
+ // Parser Tests
221
+ // ============================================================================
222
+ console.log("\nšŸ“ List & Table Parsers\n");
223
+ test("Parse unordered list", () => {
224
+ const md = `- Item 1
225
+ - Nested 1.1
226
+ - Item 2
227
+ `;
228
+ const list = parseList(md);
229
+ assert(list !== null, "Should parse list");
230
+ assert(list.ordered === false, "Should be unordered");
231
+ assert(list.items.length === 2, "Should have 2 items");
232
+ assert(list.items[0].children.length === 1, "First item has 1 child");
233
+ });
234
+ test("Parse ordered list", () => {
235
+ const md = `1. First
236
+ 1. Sub-first
237
+ 2. Second
238
+ `;
239
+ const list = parseList(md);
240
+ assert(list !== null, "Should parse list");
241
+ assert(list.ordered === true, "Should be ordered");
242
+ assert(list.items.length === 2, "Should have 2 items");
243
+ assert(list.items[0].index === 1, "First item index");
244
+ });
245
+ test("Parse pipe table", () => {
246
+ const md = `| Name | Age |
247
+ |------|-----|
248
+ | Alice | 30 |
249
+ | Bob | 25 |
250
+ `;
251
+ const table = parsePipeTable(md);
252
+ assert(table !== null, "Should parse table");
253
+ assert(table.columns.length === 2, "Should have 2 columns");
254
+ assert(table.rows.length === 2, "Should have 2 rows");
255
+ });
256
+ test("Parse ordered table", () => {
257
+ const md = `| # | Task | Status |
258
+ |---|------|--------|
259
+ | 1 | Do it | Done |
260
+ | 2 | Check | Pending |
261
+ `;
262
+ const table = parsePipeTable(md);
263
+ assert(table !== null, "Should parse table");
264
+ const issues = enforceOrderedTable(table);
265
+ assert(issues.length === 0, "Should have no ordered table issues");
266
+ assert(table.columns[0] === "#", "First column should be #");
267
+ });
268
+ test("Extract all tables", () => {
269
+ const md = `Some text
270
+
271
+ | A | B |
272
+ |---|---|
273
+ | 1 | 2 |
274
+
275
+ More text
276
+
277
+ | C | D |
278
+ |---|---|
279
+ | 3 | 4 |
280
+ `;
281
+ const tables = extractAllTables(md);
282
+ assert(tables.length === 2, "Should find 2 tables");
283
+ });
284
+ // ============================================================================
285
+ // Layer C Tests
286
+ // ============================================================================
287
+ console.log("\nšŸ” Layer C - Detection & Extraction\n");
288
+ test("Detect flexmd fence", () => {
289
+ const text = `Some text
290
+ \`\`\`flexmd
291
+ [[message]]
292
+ Hello
293
+ \`\`\`
294
+ More text
295
+ `;
296
+ const detected = detectObjects(text);
297
+ assert(detected.length > 0, "Should detect object");
298
+ assert(detected[0].kind === "flexmd_fence", "Should be flexmd fence");
299
+ assert(detected[0].confidence === 1.0, "Should have high confidence");
300
+ });
301
+ test("Detect JSON FlexDocument", () => {
302
+ const text = `\`\`\`json
303
+ {"frames": [{"type": "message", "body_md": "Hello"}]}
304
+ \`\`\`
305
+ `;
306
+ const detected = detectObjects(text);
307
+ assert(detected.length > 0, "Should detect object");
308
+ assert(detected[0].kind === "flexdoc_json_fence", "Should be JSON FlexDoc");
309
+ });
310
+ test("Parse any text", () => {
311
+ const text = `Random text
312
+ \`\`\`flexmd
313
+ [[message role=user]]
314
+ Hello
315
+ \`\`\`
316
+ More text
317
+ `;
318
+ const result = parseAny(text);
319
+ assert(result.flexDocs.length === 1, "Should extract 1 FlexDoc");
320
+ assert(result.flexDocs[0].frames.length === 1, "Should have 1 frame");
321
+ });
322
+ // ============================================================================
323
+ // Summary
324
+ // ============================================================================
325
+ console.log("\n" + "=".repeat(50));
326
+ console.log(`āœ… Passed: ${passed}`);
327
+ console.log(`āŒ Failed: ${failed}`);
328
+ console.log("=" + "=".repeat(50));
329
+ if (failed > 0) {
330
+ throw new Error("Tests failed");
331
+ }
@@ -0,0 +1 @@
1
+ export {};