flex-md 4.7.2 → 4.7.4

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 (104) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/diagnostics.test.js +45 -47
  3. package/dist/__tests__/ofs.test.js +28 -30
  4. package/dist/__tests__/structural.test.js +19 -21
  5. package/dist/__tests__/validate.test.js +27 -29
  6. package/dist/cli/index.js +13 -15
  7. package/dist/detect/json/detectIntent.js +1 -4
  8. package/dist/detect/json/detectMarkdown.js +29 -35
  9. package/dist/detect/json/detectPresence.js +2 -6
  10. package/dist/detect/json/index.js +10 -31
  11. package/dist/detect/json/types.js +1 -2
  12. package/dist/extract/extract.js +11 -14
  13. package/dist/extract/types.js +1 -2
  14. package/dist/index.js +22 -69
  15. package/dist/logger.js +3 -6
  16. package/dist/md/match.js +1 -4
  17. package/dist/md/normalize.js +1 -4
  18. package/dist/md/outline.js +4 -8
  19. package/dist/md/parse.js +11 -20
  20. package/dist/ofs/adapter.js +45 -49
  21. package/dist/ofs/enricher.js +3 -8
  22. package/dist/ofs/infer.js +4 -7
  23. package/dist/ofs/issuesEnvelope.js +5 -10
  24. package/dist/ofs/memory.js +6 -10
  25. package/dist/ofs/parser.js +7 -13
  26. package/dist/ofs/stringify.js +1 -4
  27. package/dist/pipeline/enforce.js +12 -15
  28. package/dist/pipeline/kind.js +3 -6
  29. package/dist/pipeline/repair.js +8 -11
  30. package/dist/strictness/container.js +1 -4
  31. package/dist/strictness/processor.js +7 -10
  32. package/dist/strictness/types.js +1 -4
  33. package/dist/tokens/auto-fix.js +1 -4
  34. package/dist/tokens/cognitive-cost.js +6 -10
  35. package/dist/tokens/compliance.js +4 -8
  36. package/dist/tokens/confidence.js +6 -9
  37. package/dist/tokens/estimator.js +20 -26
  38. package/dist/tokens/improvements.js +12 -16
  39. package/dist/tokens/index.js +22 -40
  40. package/dist/tokens/parser.js +4 -7
  41. package/dist/tokens/patterns.js +2 -5
  42. package/dist/tokens/runtime-estimator.js +9 -12
  43. package/dist/tokens/smart-report.js +10 -14
  44. package/dist/tokens/spec-estimator.js +10 -15
  45. package/dist/tokens/types.js +1 -2
  46. package/dist/tokens/validator.js +3 -6
  47. package/dist/types.js +1 -2
  48. package/dist/validate/compliance.js +4 -8
  49. package/dist/validate/connection.js +5 -8
  50. package/dist/validate/types.js +1 -2
  51. package/dist/validate/validate.js +16 -19
  52. package/dist-cjs/__tests__/diagnostics.test.cjs +61 -0
  53. package/dist-cjs/__tests__/ofs.test.cjs +53 -0
  54. package/dist-cjs/__tests__/structural.test.cjs +30 -0
  55. package/dist-cjs/__tests__/validate.test.cjs +110 -0
  56. package/dist-cjs/cli/index.cjs +110 -0
  57. package/dist-cjs/detect/json/detectIntent.cjs +82 -0
  58. package/dist-cjs/detect/json/detectMarkdown.cjs +304 -0
  59. package/dist-cjs/detect/json/detectPresence.cjs +195 -0
  60. package/dist-cjs/detect/json/index.cjs +34 -0
  61. package/dist-cjs/detect/json/types.cjs +2 -0
  62. package/dist-cjs/extract/extract.cjs +72 -0
  63. package/dist-cjs/extract/types.cjs +2 -0
  64. package/dist-cjs/flex-md-loader.cjs +102 -0
  65. package/dist-cjs/index.cjs +79 -0
  66. package/dist-cjs/logger.cjs +22 -0
  67. package/dist-cjs/md/match.cjs +47 -0
  68. package/dist-cjs/md/normalize.cjs +13 -0
  69. package/dist-cjs/md/outline.cjs +49 -0
  70. package/dist-cjs/md/parse.cjs +199 -0
  71. package/dist-cjs/ofs/adapter.cjs +195 -0
  72. package/dist-cjs/ofs/enricher.cjs +151 -0
  73. package/dist-cjs/ofs/infer.cjs +63 -0
  74. package/dist-cjs/ofs/issuesEnvelope.cjs +76 -0
  75. package/dist-cjs/ofs/memory.cjs +26 -0
  76. package/dist-cjs/ofs/parser.cjs +373 -0
  77. package/dist-cjs/ofs/stringify.cjs +45 -0
  78. package/dist-cjs/pipeline/enforce.cjs +49 -0
  79. package/dist-cjs/pipeline/kind.cjs +30 -0
  80. package/dist-cjs/pipeline/repair.cjs +115 -0
  81. package/dist-cjs/strictness/container.cjs +49 -0
  82. package/dist-cjs/strictness/processor.cjs +32 -0
  83. package/dist-cjs/strictness/types.cjs +109 -0
  84. package/dist-cjs/tokens/auto-fix.cjs +59 -0
  85. package/dist-cjs/tokens/cognitive-cost.cjs +209 -0
  86. package/dist-cjs/tokens/compliance.cjs +74 -0
  87. package/dist-cjs/tokens/confidence.cjs +335 -0
  88. package/dist-cjs/tokens/estimator.cjs +157 -0
  89. package/dist-cjs/tokens/improvements.cjs +701 -0
  90. package/dist-cjs/tokens/index.cjs +74 -0
  91. package/dist-cjs/tokens/parser.cjs +100 -0
  92. package/dist-cjs/tokens/patterns.cjs +23 -0
  93. package/dist-cjs/tokens/runtime-estimator.cjs +74 -0
  94. package/dist-cjs/tokens/smart-report.cjs +191 -0
  95. package/dist-cjs/tokens/spec-estimator.cjs +125 -0
  96. package/dist-cjs/tokens/types.cjs +2 -0
  97. package/dist-cjs/tokens/validator.cjs +62 -0
  98. package/dist-cjs/types.cjs +2 -0
  99. package/dist-cjs/validate/compliance.cjs +103 -0
  100. package/dist-cjs/validate/connection.cjs +47 -0
  101. package/dist-cjs/validate/types.cjs +2 -0
  102. package/dist-cjs/validate/validate.cjs +319 -0
  103. package/docs/consumption.md +1 -1
  104. package/package.json +14 -8
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkCompliance = checkCompliance;
4
+ exports.hasFlexMdContract = hasFlexMdContract;
5
+ const parser_js_1 = require("../ofs/parser.cjs");
6
+ /**
7
+ * Check if instructions meet the required flex-md compliance level.
8
+ * Returns detailed results including what's missing or wrong.
9
+ * Now also checks for Input Format Spec if present.
10
+ */
11
+ async function checkCompliance(instructions, complianceLevel) {
12
+ const issues = [];
13
+ const suggestions = [];
14
+ const lower = instructions.toLowerCase();
15
+ // Extract both input and output format specs
16
+ const formatSpecs = (0, parser_js_1.parseFormatSpecs)(instructions);
17
+ const hasInputFormat = !!formatSpecs.input;
18
+ const hasOutputFormat = !!formatSpecs.output;
19
+ const hasMarkdownMention = lower.includes("markdown");
20
+ const hasSectionMention = lower.includes("section") || lower.includes("heading");
21
+ const hasContainerMention = (lower.includes("fenced block") || lower.includes("```")) && (lower.includes("inside") || lower.includes("wrapped"));
22
+ const hasNoneRule = lower.includes("none") && lower.includes("empty");
23
+ const hasListRules = lower.includes("list rules") || (lower.includes("ordered list") && lower.includes("unordered list"));
24
+ const hasTableRules = lower.includes("table") && lower.includes("pipe");
25
+ const levelNum = parseInt(complianceLevel.slice(1));
26
+ // L0 requirements
27
+ if (levelNum >= 0) {
28
+ if (!hasMarkdownMention) {
29
+ issues.push({
30
+ type: "other",
31
+ description: "Instructions do not mention 'Markdown'.",
32
+ severity: "error",
33
+ suggestion: "Add 'Reply in Markdown.' to your instructions."
34
+ });
35
+ }
36
+ }
37
+ // L1 requirements
38
+ if (levelNum >= 1) {
39
+ if (!hasSectionMention) {
40
+ issues.push({
41
+ type: "missing-section",
42
+ description: "Instructions do not specify required sections or headings.",
43
+ severity: "error",
44
+ suggestion: "Include a list of required section headings."
45
+ });
46
+ }
47
+ }
48
+ // L2 requirements
49
+ if (levelNum >= 2) {
50
+ if (!hasContainerMention) {
51
+ issues.push({
52
+ type: "missing-container",
53
+ description: "Instructions do not require a fenced container block (L2+ requirement).",
54
+ severity: "error",
55
+ suggestion: "Add 'Return your entire answer inside a single ```markdown fenced block and nothing else.' to your instructions."
56
+ });
57
+ }
58
+ }
59
+ // L3 requirements
60
+ if (levelNum >= 3) {
61
+ if (!hasNoneRule) {
62
+ issues.push({
63
+ type: "other",
64
+ description: "Instructions are missing the 'None' rule for empty sections (L3 requirement).",
65
+ severity: "warning",
66
+ suggestion: "Add 'If a section is empty, write `None`.' to your instructions."
67
+ });
68
+ }
69
+ if (!hasListRules) {
70
+ issues.push({
71
+ type: "invalid-format",
72
+ description: "Instructions are missing specific list formatting rules (L3 requirement).",
73
+ severity: "warning",
74
+ suggestion: "Add bullet and numbered list formatting instructions."
75
+ });
76
+ }
77
+ }
78
+ const meetsCompliance = !issues.some(i => i.severity === "error");
79
+ if (!meetsCompliance) {
80
+ suggestions.push(...issues.map(i => i.suggestion).filter((s) => !!s));
81
+ }
82
+ return {
83
+ meetsCompliance,
84
+ complianceLevel,
85
+ issues,
86
+ suggestions,
87
+ metadata: {
88
+ hasContainer: hasContainerMention,
89
+ hasRequiredSections: hasSectionMention,
90
+ containerType: hasContainerMention ? "fenced-block" : "none",
91
+ hasInputFormat,
92
+ hasOutputFormat
93
+ }
94
+ };
95
+ }
96
+ /**
97
+ * Check if instructions meet the required flex-md compliance level.
98
+ * @deprecated Use checkCompliance for detailed results.
99
+ */
100
+ async function hasFlexMdContract(instructions, complianceLevel) {
101
+ const result = await checkCompliance(instructions, complianceLevel);
102
+ return result.meetsCompliance;
103
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkConnection = checkConnection;
4
+ const outline_js_1 = require("../md/outline.cjs");
5
+ const parse_js_1 = require("../md/parse.cjs");
6
+ /**
7
+ * Checks if a specific key (section) is "connected" in the given markdown.
8
+ */
9
+ function checkConnection(md, key) {
10
+ const outline = (0, outline_js_1.buildOutline)(md);
11
+ const normKey = (0, parse_js_1.normalizeName)(key);
12
+ const path = [];
13
+ // Breadth-first search for the key
14
+ const queue = outline.nodes.map(n => ({ node: n, currentPath: [n.title] }));
15
+ const alternatives = [];
16
+ while (queue.length > 0) {
17
+ const { node, currentPath } = queue.shift();
18
+ if (node.key === normKey) {
19
+ return {
20
+ connected: true,
21
+ key,
22
+ normalizedKey: normKey,
23
+ path: currentPath
24
+ };
25
+ }
26
+ // Collect alternatives (any heading that exists)
27
+ alternatives.push(node.title);
28
+ for (const child of node.children) {
29
+ queue.push({ node: child, currentPath: [...currentPath, child.title] });
30
+ }
31
+ }
32
+ // Not found, look for fuzzy matches or common issues
33
+ const suggestions = [];
34
+ if (alternatives.length === 0) {
35
+ suggestions.push("The document appears to have no headings.");
36
+ }
37
+ else {
38
+ suggestions.push(`Available sections: ${alternatives.join(", ")}`);
39
+ }
40
+ return {
41
+ connected: false,
42
+ key,
43
+ normalizedKey: normKey,
44
+ alternativeMatches: alternatives,
45
+ suggestion: suggestions.join(" ")
46
+ };
47
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,319 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateMarkdownAgainstOfs = validateMarkdownAgainstOfs;
4
+ const parse_js_1 = require("../md/parse.cjs");
5
+ function enforcementToSeverity(e) {
6
+ if (e === "ignore")
7
+ return null;
8
+ return e === "warn" ? "warn" : "error";
9
+ }
10
+ function defaultPolicy(level) {
11
+ return {
12
+ missingSection: level >= 1 ? "error" : "ignore",
13
+ emptyRequiredSection: level >= 1 ? "error" : "ignore",
14
+ wrongSectionKind: level >= 3 ? "error" : "ignore",
15
+ containerMissing: level >= 2 ? "error" : "ignore",
16
+ containerMultiple: level >= 2 ? "error" : "ignore",
17
+ containerOutsideText: level >= 2 ? "error" : "ignore",
18
+ duplicateSection: level >= 1 ? "warn" : "ignore",
19
+ malformedTable: level >= 3 ? "error" : "ignore",
20
+ malformedOrderedTable: level >= 3 ? "error" : "ignore",
21
+ noneIsAcceptableContent: true,
22
+ };
23
+ }
24
+ function addIssue(issues, policyLevel, issue) {
25
+ const sev = enforcementToSeverity(policyLevel);
26
+ if (!sev)
27
+ return;
28
+ issues.push({ ...issue, severity: sev });
29
+ }
30
+ function isRequiredSection(spec, level) {
31
+ if (typeof spec.required === "boolean")
32
+ return spec.required;
33
+ return level >= 1;
34
+ }
35
+ function stripToWorkingMarkdown(input, level) {
36
+ if (level < 2)
37
+ return { working: input, container: null };
38
+ const fences = (0, parse_js_1.extractFencedBlocks)(input);
39
+ if (fences.length === 1) {
40
+ const f = fences[0];
41
+ return { working: f.content, container: { fence: f } };
42
+ }
43
+ return { working: input, container: null };
44
+ }
45
+ function validateMarkdownAgainstOfs(input, spec, level, policyOverride) {
46
+ const policy = { ...defaultPolicy(level), ...(policyOverride ?? {}) };
47
+ const issues = [];
48
+ const fencesAll = (0, parse_js_1.extractFencedBlocks)(input);
49
+ if (level === 0) {
50
+ const env = (0, parse_js_1.isIssuesEnvelopeCheck)(input);
51
+ return {
52
+ ok: true,
53
+ level,
54
+ issues: [],
55
+ stats: {
56
+ detectedKind: env.isIssuesEnvelope ? "issues" : "markdown",
57
+ sectionCount: 0,
58
+ missingRequired: [],
59
+ duplicates: [],
60
+ container: { fenceCount: fencesAll.length, hasOutsideText: false, fenceLangs: fencesAll.map(f => f.lang) },
61
+ },
62
+ };
63
+ }
64
+ const env = (0, parse_js_1.isIssuesEnvelopeCheck)(input);
65
+ if (env.isIssuesEnvelope) {
66
+ issues.push({
67
+ code: "ISSUES_ENVELOPE_PRESENT",
68
+ severity: "error",
69
+ message: "Issues envelope present (model did not return a normal answer).",
70
+ });
71
+ return {
72
+ ok: false,
73
+ level,
74
+ issues,
75
+ stats: {
76
+ detectedKind: "issues",
77
+ sectionCount: Object.keys(env.sections).length,
78
+ missingRequired: [],
79
+ duplicates: [],
80
+ container: { fenceCount: fencesAll.length, hasOutsideText: false, fenceLangs: fencesAll.map(f => f.lang) },
81
+ },
82
+ };
83
+ }
84
+ const fenceLangs = fencesAll.map(f => f.lang || "(none)");
85
+ let hasOutsideText = false;
86
+ if (level >= 2) {
87
+ if (fencesAll.length === 0) {
88
+ addIssue(issues, policy.containerMissing, {
89
+ code: "CONTAINER_MISSING",
90
+ message: "L2+ requires a single fenced ```markdown block (none found).",
91
+ });
92
+ }
93
+ else if (fencesAll.length > 1) {
94
+ addIssue(issues, policy.containerMultiple, {
95
+ code: "CONTAINER_MULTIPLE",
96
+ message: `L2+ requires a single fenced block (found ${fencesAll.length}).`,
97
+ });
98
+ }
99
+ else {
100
+ const f = fencesAll[0];
101
+ const before = input.slice(0, f.start).trim();
102
+ const after = input.slice(f.end).trim();
103
+ hasOutsideText = before.length > 0 || after.length > 0;
104
+ if (hasOutsideText) {
105
+ addIssue(issues, policy.containerOutsideText, {
106
+ code: "CONTAINER_OUTSIDE_TEXT",
107
+ message: "Text exists outside the single fenced block (L2+ requires nothing outside).",
108
+ });
109
+ }
110
+ }
111
+ }
112
+ const { working } = stripToWorkingMarkdown(input, level);
113
+ const parsed = (0, parse_js_1.parseHeadingsAndSections)(working);
114
+ const specByNorm = new Map();
115
+ for (const s of spec.sections)
116
+ specByNorm.set((0, parse_js_1.normalizeName)(s.name), s);
117
+ const occurrences = new Map();
118
+ const bodiesByNorm = {};
119
+ for (const sec of parsed) {
120
+ if (!specByNorm.has(sec.heading.norm))
121
+ continue;
122
+ occurrences.set(sec.heading.norm, (occurrences.get(sec.heading.norm) ?? 0) + 1);
123
+ bodiesByNorm[sec.heading.norm] = bodiesByNorm[sec.heading.norm] ?? [];
124
+ bodiesByNorm[sec.heading.norm].push(sec.body.trim());
125
+ }
126
+ const missingRequired = [];
127
+ for (const s of spec.sections) {
128
+ const req = isRequiredSection(s, level);
129
+ if (!req)
130
+ continue;
131
+ const norm = (0, parse_js_1.normalizeName)(s.name);
132
+ const count = occurrences.get(norm) ?? 0;
133
+ if (count === 0) {
134
+ missingRequired.push(s.name);
135
+ addIssue(issues, policy.missingSection, {
136
+ code: "MISSING_SECTION",
137
+ message: `Missing required section: "${s.name}"`,
138
+ sectionName: s.name,
139
+ });
140
+ }
141
+ }
142
+ const noneValue = spec.emptySectionValue ?? "None";
143
+ for (const s of spec.sections) {
144
+ const req = isRequiredSection(s, level);
145
+ if (!req)
146
+ continue;
147
+ const norm = (0, parse_js_1.normalizeName)(s.name);
148
+ const bodies = bodiesByNorm[norm] ?? [];
149
+ if (!bodies.length)
150
+ continue;
151
+ const allEmpty = bodies.every(b => b.trim().length === 0);
152
+ const allNone = bodies.every(b => (0, parse_js_1.normalizeName)(b) === (0, parse_js_1.normalizeName)(noneValue));
153
+ const emptyByPolicy = allEmpty || (!policy.noneIsAcceptableContent && allNone);
154
+ if (emptyByPolicy) {
155
+ addIssue(issues, policy.emptyRequiredSection, {
156
+ code: "EMPTY_REQUIRED_SECTION",
157
+ message: `Empty required section: "${s.name}"`,
158
+ sectionName: s.name,
159
+ });
160
+ }
161
+ }
162
+ const duplicates = [];
163
+ for (const [norm, count] of occurrences.entries()) {
164
+ if (count > 1) {
165
+ const name = specByNorm.get(norm).name;
166
+ duplicates.push(name);
167
+ addIssue(issues, policy.duplicateSection, {
168
+ code: "DUPLICATE_SECTION",
169
+ message: `Duplicate section heading: "${name}" (appears ${count} times)`,
170
+ sectionName: name,
171
+ });
172
+ }
173
+ }
174
+ if (level >= 3) {
175
+ for (const s of spec.sections) {
176
+ const norm = (0, parse_js_1.normalizeName)(s.name);
177
+ const bodies = bodiesByNorm[norm] ?? [];
178
+ if (!bodies.length)
179
+ continue;
180
+ const kind = s.kind ?? "text";
181
+ for (const body of bodies) {
182
+ const b = body.trim();
183
+ if ((0, parse_js_1.normalizeName)(b) === (0, parse_js_1.normalizeName)(noneValue))
184
+ continue;
185
+ if (kind === "text")
186
+ continue;
187
+ if (kind === "list") {
188
+ const ok = /^\s*[-*]\s+/m.test(b);
189
+ if (!ok) {
190
+ addIssue(issues, policy.wrongSectionKind, {
191
+ code: "WRONG_SECTION_KIND",
192
+ message: `Section "${s.name}" must be a bullet list (use "-" bullets).`,
193
+ sectionName: s.name,
194
+ });
195
+ }
196
+ }
197
+ else if (kind === "ordered_list") {
198
+ const ok = /^\s*\d+\.\s+/m.test(b);
199
+ if (!ok) {
200
+ addIssue(issues, policy.wrongSectionKind, {
201
+ code: "WRONG_SECTION_KIND",
202
+ message: `Section "${s.name}" must be an ordered list (use "1.", "2.", …).`,
203
+ sectionName: s.name,
204
+ });
205
+ }
206
+ }
207
+ else if (kind === "table" || kind === "ordered_table") {
208
+ const table = findPipeTable(b);
209
+ if (!table) {
210
+ addIssue(issues, policy.malformedTable, {
211
+ code: "MALFORMED_TABLE",
212
+ message: `Section "${s.name}" must be a Markdown pipe table.`,
213
+ sectionName: s.name,
214
+ });
215
+ continue;
216
+ }
217
+ const cols = (s.columns ?? []).map(c => (0, parse_js_1.normalizeName)(c));
218
+ if (cols.length) {
219
+ const headerNorm = table.header.map(c => (0, parse_js_1.normalizeName)(c));
220
+ const missingCols = cols.filter(c => !headerNorm.includes(c));
221
+ if (missingCols.length) {
222
+ addIssue(issues, policy.malformedTable, {
223
+ code: "MALFORMED_TABLE",
224
+ message: `Table in section "${s.name}" is missing columns: ${missingCols.join(", ")}`,
225
+ sectionName: s.name,
226
+ });
227
+ }
228
+ }
229
+ if (kind === "ordered_table") {
230
+ const header0 = (0, parse_js_1.normalizeName)(table.header[0] ?? "");
231
+ if (header0 !== "#") {
232
+ addIssue(issues, policy.malformedOrderedTable, {
233
+ code: "MALFORMED_ORDERED_TABLE",
234
+ message: `Ordered table in section "${s.name}" must include a first column named "#".`,
235
+ sectionName: s.name,
236
+ });
237
+ }
238
+ else {
239
+ const nums = table.rows.map(r => (r[0] ?? "").trim()).filter(Boolean);
240
+ const okSeq = nums.every((v, idx) => String(idx + 1) === v);
241
+ if (!okSeq && nums.length) {
242
+ addIssue(issues, policy.malformedOrderedTable, {
243
+ code: "MALFORMED_ORDERED_TABLE",
244
+ message: `Ordered table in section "${s.name}" should have sequential row numbers 1..N in column "#".`,
245
+ sectionName: s.name,
246
+ });
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }
254
+ // Compute detectedKind more robustly: if we found more than zero sections, it's at least sectioned
255
+ let detectedKind = "markdown";
256
+ if (level >= 2) {
257
+ detectedKind = fencesAll.length > 0 ? "fenced" : (parsed.length > 0 ? "sectioned" : "markdown");
258
+ }
259
+ else {
260
+ detectedKind = parsed.length > 0 ? "sectioned" : "markdown";
261
+ }
262
+ const ok = !issues.some(i => i.severity === "error");
263
+ return {
264
+ ok,
265
+ level,
266
+ issues,
267
+ stats: {
268
+ detectedKind,
269
+ sectionCount: occurrences.size,
270
+ missingRequired,
271
+ duplicates,
272
+ container: {
273
+ fenceCount: fencesAll.length,
274
+ hasOutsideText,
275
+ fenceLangs,
276
+ },
277
+ },
278
+ };
279
+ }
280
+ function findPipeTable(body) {
281
+ const lines = body.split(/\r?\n/).map(l => l.trim()).filter(l => l.length > 0);
282
+ for (let i = 0; i < lines.length - 1; i++) {
283
+ const h = lines[i];
284
+ const sep = lines[i + 1];
285
+ if (!looksLikePipeRow(h))
286
+ continue;
287
+ if (!looksLikeSeparatorRow(sep))
288
+ continue;
289
+ const header = splitPipeRow(h);
290
+ const rows = [];
291
+ for (let j = i + 2; j < lines.length; j++) {
292
+ const r = lines[j];
293
+ if (!looksLikePipeRow(r))
294
+ break;
295
+ rows.push(splitPipeRow(r));
296
+ }
297
+ return { header, rows };
298
+ }
299
+ return null;
300
+ }
301
+ function looksLikePipeRow(line) {
302
+ return line.includes("|") && /[^|]/.test(line.replace(/\|/g, ""));
303
+ }
304
+ function looksLikeSeparatorRow(line) {
305
+ if (!line.includes("|"))
306
+ return false;
307
+ const cells = splitPipeRow(line);
308
+ if (!cells.length)
309
+ return false;
310
+ return cells.every(c => /^:?-{3,}:?$/.test(c.trim()));
311
+ }
312
+ function splitPipeRow(line) {
313
+ let s = line.trim();
314
+ if (s.startsWith("|"))
315
+ s = s.slice(1);
316
+ if (s.endsWith("|"))
317
+ s = s.slice(0, -1);
318
+ return s.split("|").map(c => c.trim());
319
+ }
@@ -4,7 +4,7 @@ This document is for library/tool authors (gateways, SDK adapters, orchestrators
4
4
 
5
5
  ## Node support baseline
6
6
 
7
- - `flex-md` supports Node LTS in the range `>=18 <22`.
7
+ - `flex-md` supports Node LTS in the range `>=18`.
8
8
  - Release validation should run `npm run build` then `npm run test:smoke`.
9
9
 
10
10
  ## Export map behavior
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flex-md",
3
- "version": "4.7.2",
3
+ "version": "4.7.4",
4
4
  "description": "Parse and stringify FlexMD: semi-structured Markdown with three powerful layers - Frames, Output Format Spec (OFS), and Detection/Extraction.",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -19,11 +19,11 @@
19
19
  "ts-node": {
20
20
  "esm": true
21
21
  },
22
- "type": "commonjs",
22
+ "type": "module",
23
23
  "engines": {
24
- "node": ">=18 <22"
24
+ "node": ">=18"
25
25
  },
26
- "main": "./dist/index.cjs",
26
+ "main": "./dist-cjs/index.cjs",
27
27
  "module": "./dist/index.js",
28
28
  "types": "./dist/index.d.ts",
29
29
  "bin": {
@@ -33,26 +33,32 @@
33
33
  ".": {
34
34
  "types": "./dist/index.d.ts",
35
35
  "import": "./dist/index.js",
36
- "require": "./dist/index.cjs"
36
+ "require": "./dist-cjs/index.cjs"
37
37
  }
38
38
  },
39
39
  "files": [
40
40
  "dist",
41
+ "dist-cjs",
41
42
  "docs",
42
43
  "athenices-ai-gateway.md",
43
44
  "README.md",
44
45
  "SPEC.md"
45
46
  ],
46
47
  "scripts": {
47
- "build": "tsc && tsc -p tsconfig.cjs.json && node ./scripts/cjs-bridge.mjs",
48
+ "build": "tsc && tsc -p tsconfig.cjs.json && node ./scripts/emit-cjs-extensions.mjs",
48
49
  "test": "vitest run",
49
50
  "test:watch": "vitest",
50
51
  "test:smoke": "node test/smoke-require.cjs && node test/smoke-import.mjs && node test/smoke-package-resolution.cjs",
51
52
  "deps:latest": "npx npm-check-updates -u && npm install"
52
53
  },
53
- "devDependencies": {},
54
+ "devDependencies": {
55
+ "@types/node": "^25.0.3",
56
+ "tsx": "^4.21.0",
57
+ "typescript": "^5.6.3",
58
+ "vitest": "^4.0.16"
59
+ },
54
60
  "dependencies": {
55
- "micro-logs": "^1.0.0",
61
+ "micro-logs": "^1.0.5",
56
62
  "nx-helpers": "^1.5.0",
57
63
  "nx-json-parser": "^1.3.0",
58
64
  "nx-md-parser": "^2.2.1"