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,213 @@
1
+ import { parseOutputFormatSpec, processResponseMarkdown, strictnessDefaults } from "../src/index.js";
2
+ const specText = `
3
+ ## Output format (Markdown)
4
+ Include these sections somewhere (order does not matter):
5
+
6
+ - Short answer — prose
7
+ - Long answer — prose
8
+ - Reasoning — ordered list
9
+ - Assumptions — list
10
+ - Unknowns — list
11
+
12
+ tables:
13
+ - (property, value — table)
14
+
15
+ empty sections:
16
+ - If a section is empty, write \`None\`.
17
+ `;
18
+ const spec = parseOutputFormatSpec(specText);
19
+ // --- L0 Test ---
20
+ console.log("--- Level 0 Test ---");
21
+ const l0Response = `
22
+ I thought about it.
23
+ ## Short answer
24
+ Yes, it works.
25
+
26
+ ## Unknowns
27
+ - None so far.
28
+ `;
29
+ const l0Result = processResponseMarkdown(l0Response, spec, strictnessDefaults(0));
30
+ console.log("L0 OK:", l0Result.ok);
31
+ console.log("L0 Issues:", l0Result.issues);
32
+ // --- L1 Test ---
33
+ console.log("\n--- Level 1 Test ---");
34
+ const l1Response = `
35
+ ## Short answer
36
+ Yes.
37
+
38
+ ## Long answer
39
+ Full details.
40
+
41
+ ## Reasoning
42
+ 1. Step one.
43
+ 2. Step two.
44
+
45
+ ## Assumptions
46
+ - Assumption A.
47
+
48
+ ## Unknowns
49
+ None
50
+ `;
51
+ const l1Result = processResponseMarkdown(l1Response, spec, strictnessDefaults(1));
52
+ console.log("L1 OK:", l1Result.ok);
53
+ console.log("L1 Issues:", l1Result.issues);
54
+ // --- L2 Test ---
55
+ console.log("\n--- Level 2 Test ---");
56
+ const l2Response = `
57
+ Some chatter before.
58
+ \`\`\`markdown
59
+ ## Short answer
60
+ Yes.
61
+ ## Long answer
62
+ None
63
+ ## Reasoning
64
+ 1. A
65
+ ## Assumptions
66
+ None
67
+ ## Unknowns
68
+ None
69
+ \`\`\`
70
+ Some chatter after.
71
+ `;
72
+ const l2Result = processResponseMarkdown(l2Response, spec, strictnessDefaults(2));
73
+ console.log("L2 OK (should be false if chatter exists):", l2Result.ok);
74
+ console.log("L2 Issues (should include 'content_outside_container'):", l2Result.issues);
75
+ const l2ResponseClean = `\`\`\`markdown
76
+ ## Short answer
77
+ Yes.
78
+ ## Long answer
79
+ None
80
+ ## Reasoning
81
+ 1. A
82
+ ## Assumptions
83
+ None
84
+ ## Unknowns
85
+ None
86
+ \`\`\``;
87
+ const l2ResultClean = processResponseMarkdown(l2ResponseClean, spec, strictnessDefaults(2));
88
+ console.log("L2 Clean OK:", l2ResultClean.ok);
89
+ // --- L3 Test ---
90
+ console.log("\n--- Level 3 Test ---");
91
+ const l3Response = `\`\`\`markdown
92
+ ## Short answer
93
+ Data processed.
94
+ ## Long answer
95
+ None
96
+ ## Reasoning
97
+ 1. Step
98
+ ## Assumptions
99
+ None
100
+ ## Unknowns
101
+ None
102
+
103
+ \`\`\`json
104
+ { "status": "success", "val": 42 }
105
+ \`\`\`
106
+ \`\`\``;
107
+ // The regex in processor.ts is: /```json\s*\n([\s\S]*?)\n```/i
108
+ // Let's see why it failed. Ah, the innerMarkdown was extracted but maybe the nested blocks were messed up.
109
+ // Actually, l3Result returned issues: [ 'content_outside_container', 'json_payload_missing' ]
110
+ // content_outside_container is due to the trailing \`\`\` outside if the first one was matched greedily?
111
+ // No, regex is non-greedy in container.ts: /\`\`\`" + fenceLang + "\\s*\\n([\\s\\S]*?)\\n\`\`\`/i
112
+ // Wait, the container extractor in container.ts uses: /\`\`\`" + fenceLang + "\\s*\\n([\\s\\S]*?)\\n\`\`\`/i
113
+ // In my L3 test:
114
+ // \`\`\`markdown
115
+ // ...
116
+ // \`\`\`json
117
+ // ...
118
+ // \`\`\`
119
+ // \`\`\`
120
+ // The second to last \`\`\` matches the end of the markdown block. The last \`\`\` is "outside".
121
+ const l3ResponseFixed = `\`\`\`markdown
122
+ ## Short answer
123
+ Data processed.
124
+ ## Long answer
125
+ None
126
+ ## Reasoning
127
+ 1. Step
128
+ ## Assumptions
129
+ None
130
+ ## Unknowns
131
+ None
132
+
133
+ \`\`\`json
134
+ { "status": "success", "val": 42 }
135
+ \`\`\`
136
+ \`\`\``;
137
+ // In this one there are two closing fences.
138
+ const l3ResponseSimple = `\`\`\`markdown
139
+ ## Short answer
140
+ Data processed.
141
+ ## Long answer
142
+ None
143
+ ## Reasoning
144
+ 1. Step
145
+ ## Assumptions
146
+ None
147
+ ## Unknowns
148
+ None
149
+
150
+ \`\`\`json
151
+ { "status": "success", "val": 42 }
152
+ \`\`\`
153
+ \`\`\``;
154
+ // Let's try again with a simpler L3 mock
155
+ const l3ResponseSuccess = `\`\`\`markdown
156
+ ## Short answer
157
+ Done.
158
+ ## Long answer
159
+ None
160
+ ## Reasoning
161
+ 1. A
162
+ ## Assumptions
163
+ None
164
+ ## Unknowns
165
+ None
166
+
167
+ \`\`\`json
168
+ {"ok": true}
169
+ \`\`\`
170
+ \`\`\``;
171
+ const lResult3 = processResponseMarkdown(l3ResponseSuccess, spec, strictnessDefaults(3));
172
+ console.log("L3 Success OK:", lResult3.ok);
173
+ console.log("L3 Issues:", lResult3.issues);
174
+ // --- Semi-Strict Test ---
175
+ console.log("\n--- Semi-Strict Test ---");
176
+ const semiStrictSpecText = `
177
+ ## Output format
178
+ - Answer — prose (required)
179
+ - Extra — list (optional)
180
+ `;
181
+ const semiStrictSpec = parseOutputFormatSpec(semiStrictSpecText);
182
+ const semiStrictResponse = `
183
+ ## Answer
184
+ This is the answer.
185
+ `;
186
+ // Level 1 defaults to requireAllSections: true.
187
+ // But "Extra" is marked (optional).
188
+ const semiStrictResult = processResponseMarkdown(semiStrictResponse, semiStrictSpec, { level: 1 });
189
+ console.log("Semi-Strict Missing Optional OK:", semiStrictResult.ok); // Should be true
190
+ console.log("Semi-Strict Issues:", semiStrictResult.issues);
191
+ const semiStrictResponseError = `
192
+ ## Extra
193
+ - Something
194
+ `;
195
+ const semiStrictResultError = processResponseMarkdown(semiStrictResponseError, semiStrictSpec, { level: 1 });
196
+ console.log("Semi-Strict Missing Required OK:", semiStrictResultError.ok); // Should be false
197
+ console.log("Semi-Strict Issues (should have missing_section:Answer):", semiStrictResultError.issues.map((i) => i.code));
198
+ // --- Mode B (Issues Envelope) Test ---
199
+ console.log("\n--- Mode B Test ---");
200
+ const modeBResponse = `
201
+ ## Status
202
+ - status: error
203
+ - code: missing_input
204
+ - message: The user forgot the org_id.
205
+
206
+ ## Issues
207
+ - issue: missing_org_id
208
+ hint: please provide org_id
209
+ `;
210
+ const modeBResult = processResponseMarkdown(modeBResponse, semiStrictSpec, { level: 1 });
211
+ console.log("Mode B Detected:", !!modeBResult.issuesEnvelope);
212
+ console.log("Mode B Status Code:", modeBResult.issuesEnvelope?.status.code);
213
+ console.log("Mode B OK (should be false for error status):", modeBResult.ok);
package/dist/types.d.ts CHANGED
@@ -1,12 +1,8 @@
1
1
  export type FlexMetaValue = string | string[] | number | boolean | null;
2
2
  export interface FlexPayload {
3
- /** Language from the fenced block, e.g. "json", "yaml", "text" */
4
3
  lang?: string;
5
- /** Parsed value for json fences; otherwise a string raw value */
6
4
  value: unknown;
7
- /** Raw text inside the fence (no surrounding ``` lines) */
8
5
  raw: string;
9
- /** If lang=json but parsing failed */
10
6
  parseError?: string;
11
7
  }
12
8
  export interface FlexFrame {
@@ -14,37 +10,23 @@ export interface FlexFrame {
14
10
  role?: string;
15
11
  id?: string;
16
12
  ts?: string;
17
- /** Metadata from @key: lines */
18
13
  meta?: Record<string, FlexMetaValue>;
19
- /** Body markdown between header/meta and next frame header */
20
14
  body_md?: string;
21
- /** Payloads keyed by @payload:name: X */
22
15
  payloads?: Record<string, FlexPayload>;
23
16
  }
24
17
  export interface FlexDocument {
18
+ title?: string;
19
+ meta?: Record<string, FlexMetaValue>;
25
20
  frames: FlexFrame[];
26
21
  }
27
22
  export interface ParseOptions {
28
- /**
29
- * Which metadata keys should be split by commas into string[]
30
- * Default: ["tags", "refs"]
31
- */
32
23
  arrayMetaKeys?: string[];
24
+ metaTypeMode?: "strings" | "infer" | "schema";
25
+ metaSchema?: Record<string, "string" | "number" | "boolean" | "null">;
33
26
  }
34
27
  export interface StringifyOptions {
35
- /**
36
- * If true, omit empty sections when converting JSON -> FlexMD.
37
- * Default: true
38
- */
39
28
  skipEmpty?: boolean;
40
- /**
41
- * Which meta keys should be rendered as comma-separated lists if arrays.
42
- * Default: ["tags", "refs"]
43
- */
44
29
  arrayMetaKeys?: string[];
45
- /**
46
- * Preferred fence marker. Default: "```"
47
- */
48
30
  fence?: "```" | "~~~";
49
31
  }
50
32
  export interface FlexValidationError {
@@ -56,3 +38,139 @@ export interface FlexValidationResult {
56
38
  valid: boolean;
57
39
  errors: FlexValidationError[];
58
40
  }
41
+ export type ResponseKind = "flexmd" | "issues" | "markdown";
42
+ export type Enforcement = "ignore" | "warn" | "error";
43
+ export interface ValidationPolicy {
44
+ missingSection?: Enforcement;
45
+ emptyRequiredSection?: Enforcement;
46
+ wrongSectionKind?: Enforcement;
47
+ containerMissing?: Enforcement;
48
+ containerMultiple?: Enforcement;
49
+ containerOutsideText?: Enforcement;
50
+ duplicateSection?: Enforcement;
51
+ malformedTable?: Enforcement;
52
+ malformedOrderedTable?: Enforcement;
53
+ noneIsAcceptableContent?: boolean;
54
+ }
55
+ export type Severity = "info" | "warn" | "error";
56
+ export type IssueCode = "ISSUES_ENVELOPE_PRESENT" | "CONTAINER_MISSING" | "CONTAINER_MULTIPLE" | "CONTAINER_OUTSIDE_TEXT" | "MISSING_SECTION" | "EMPTY_REQUIRED_SECTION" | "DUPLICATE_SECTION" | "WRONG_SECTION_KIND" | "MALFORMED_TABLE" | "MALFORMED_ORDERED_TABLE";
57
+ export interface ValidationIssue {
58
+ code: IssueCode;
59
+ severity: Severity;
60
+ message: string;
61
+ sectionName?: string;
62
+ span?: {
63
+ start: number;
64
+ end: number;
65
+ };
66
+ }
67
+ export interface ValidationResult {
68
+ ok: boolean;
69
+ level: 0 | 1 | 2 | 3;
70
+ issues: ValidationIssue[];
71
+ stats?: {
72
+ detectedKind: "markdown" | "issues" | "sectioned" | "fenced";
73
+ sectionCount: number;
74
+ missingRequired: string[];
75
+ duplicates: string[];
76
+ container: {
77
+ fenceCount: number;
78
+ hasOutsideText: boolean;
79
+ fenceLangs: string[];
80
+ };
81
+ };
82
+ }
83
+ export type SectionKind = "text" | "list" | "ordered_list" | "table" | "ordered_table";
84
+ export interface OutputSectionSpec {
85
+ name: string;
86
+ kind?: SectionKind;
87
+ required?: boolean;
88
+ columns?: string[];
89
+ }
90
+ /**
91
+ * @deprecated Use OutputSectionSpec
92
+ */
93
+ export interface OfsSection extends OutputSectionSpec {
94
+ hint?: string;
95
+ emptyAllowed?: boolean;
96
+ }
97
+ export interface OfsTable {
98
+ columns: string[];
99
+ kind: "table" | "ordered_table";
100
+ by?: string;
101
+ required?: boolean;
102
+ }
103
+ export interface OutputFormatSpec {
104
+ sections: OutputSectionSpec[];
105
+ emptySectionValue?: string;
106
+ descriptorType?: "output_format_spec";
107
+ format?: "markdown";
108
+ sectionOrderMatters?: boolean;
109
+ tablesOptional?: boolean;
110
+ tables?: OfsTable[];
111
+ }
112
+ export interface MdNode {
113
+ title: string;
114
+ level: number;
115
+ key: string;
116
+ id?: string;
117
+ content_md: string;
118
+ children: MdNode[];
119
+ }
120
+ export interface MdOutline {
121
+ type: "md_outline";
122
+ nodes: MdNode[];
123
+ }
124
+ export interface ListItem {
125
+ text: string;
126
+ index?: number;
127
+ children: ListItem[];
128
+ }
129
+ export interface ParsedList {
130
+ kind: "list";
131
+ ordered: boolean;
132
+ items: ListItem[];
133
+ }
134
+ export interface ParsedTable {
135
+ kind: "table" | "ordered_table";
136
+ by?: string;
137
+ columns: string[];
138
+ rows: string[][];
139
+ }
140
+ export interface ExtractedResult {
141
+ outline: MdOutline;
142
+ sectionsByName: Record<string, {
143
+ nodeKey: string;
144
+ nodeLevel: number;
145
+ md: string;
146
+ list?: ParsedList;
147
+ }>;
148
+ tables: ParsedTable[];
149
+ }
150
+ export interface ProcessResponseResult {
151
+ ok: boolean;
152
+ strictness: any;
153
+ usedContainer: boolean;
154
+ innerMarkdown: string;
155
+ validation: ValidationResult;
156
+ extracted?: ExtractedResult;
157
+ issues: ValidationIssue[];
158
+ issuesEnvelope?: IssuesEnvelope;
159
+ }
160
+ export type DetectedKind = "flexmd_fence" | "flexdoc_json_fence" | "raw_flexmd" | "markdown_snippet" | "none";
161
+ export interface DetectedObject {
162
+ kind: DetectedKind;
163
+ confidence: number;
164
+ start: number;
165
+ end: number;
166
+ raw: string;
167
+ inner?: string;
168
+ }
169
+ export interface IssuesEnvelope {
170
+ isIssuesEnvelope: boolean;
171
+ sections: Record<string, {
172
+ heading: string;
173
+ body: string;
174
+ bullets: string[];
175
+ }>;
176
+ }
@@ -0,0 +1,10 @@
1
+ import { Enforcement, SemiStrictPolicy } from "../strictness/types.js";
2
+ import { ValidationIssue } from "./types.js";
3
+ export declare function enforcementToSeverity(e: Enforcement): "error" | "warning";
4
+ /**
5
+ * Decide whether to emit an issue:
6
+ * - "ignore" => return null
7
+ * - "warn"/"error" => return ValidationIssue with chosen severity
8
+ */
9
+ export declare function makeIssue(policy: SemiStrictPolicy | undefined, key: keyof SemiStrictPolicy, fallback: Enforcement, issue: Omit<ValidationIssue, "severity">): ValidationIssue | null;
10
+ export declare function hasErrors(issues: ValidationIssue[]): boolean;
@@ -0,0 +1,17 @@
1
+ export function enforcementToSeverity(e) {
2
+ return e === "error" ? "error" : "warning";
3
+ }
4
+ /**
5
+ * Decide whether to emit an issue:
6
+ * - "ignore" => return null
7
+ * - "warn"/"error" => return ValidationIssue with chosen severity
8
+ */
9
+ export function makeIssue(policy, key, fallback, issue) {
10
+ const enforcement = (policy?.[key] ?? fallback);
11
+ if (enforcement === "ignore")
12
+ return null;
13
+ return { severity: enforcementToSeverity(enforcement), ...issue };
14
+ }
15
+ export function hasErrors(issues) {
16
+ return issues.some(i => i.severity === "error");
17
+ }
@@ -0,0 +1,11 @@
1
+ export type ValidationSeverity = "error" | "warning";
2
+ export interface ValidationIssue {
3
+ severity: ValidationSeverity;
4
+ code: string;
5
+ message: string;
6
+ path?: string;
7
+ }
8
+ export interface ValidationResult {
9
+ ok: boolean;
10
+ issues: ValidationIssue[];
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { OutputFormatSpec, ValidationPolicy, ValidationResult } from "../types.js";
2
+ export declare function validateMarkdownAgainstOfs(input: string, spec: OutputFormatSpec, level: 0 | 1 | 2 | 3, policyOverride?: ValidationPolicy): ValidationResult;