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,199 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeName = normalizeName;
4
+ exports.extractFencedBlocks = extractFencedBlocks;
5
+ exports.parseHeadingsAndSections = parseHeadingsAndSections;
6
+ exports.extractBullets = extractBullets;
7
+ exports.parseMarkdownTable = parseMarkdownTable;
8
+ exports.isIssuesEnvelopeCheck = isIssuesEnvelopeCheck;
9
+ exports.markdownToJson = markdownToJson;
10
+ const nx_helpers_1 = require("nx-helpers");
11
+ const normalize_js_1 = require("./normalize.cjs");
12
+ function normalizeName(s) {
13
+ return s.trim().replace(/\s+/g, " ").toLowerCase();
14
+ }
15
+ function extractFencedBlocks(text) {
16
+ const rx = /```(\w+)?\s*\n([\s\S]*?)\n```/g;
17
+ const blocks = [];
18
+ let m;
19
+ while ((m = rx.exec(text)) !== null) {
20
+ const full = m[0];
21
+ const lang = String(m[1] ?? "").trim().toLowerCase();
22
+ const content = m[2] ?? "";
23
+ const start = m.index;
24
+ const end = start + full.length;
25
+ // compute content start/end (best-effort)
26
+ const headerLen = full.indexOf("\n") + 1; // after first newline
27
+ const contentEnd = end - 3; // "```" at end
28
+ const contentStart = start + headerLen;
29
+ blocks.push({
30
+ lang,
31
+ start,
32
+ end,
33
+ contentStart,
34
+ contentEnd,
35
+ content,
36
+ full,
37
+ });
38
+ }
39
+ return blocks;
40
+ }
41
+ function parseHeadingsAndSections(md, options = {}) {
42
+ // Standard headings #... and alternative ===key.
43
+ // Also optionally allow "- Section" if specified in bulletNames.
44
+ const rx = /^((?:#{1,6})[ \t]+(.+?)[ \t]*|===(.+?)[ \t]*|([-*+])[ \t]+(.+?)[ \t]*)$/gm;
45
+ const headings = [];
46
+ const bulletNamesNormal = new Set((options.bulletNames || []).map(normalizeName));
47
+ let m;
48
+ while ((m = rx.exec(md)) !== null) {
49
+ const full = m[1] ?? "";
50
+ let level;
51
+ let name;
52
+ if (full.startsWith("===")) {
53
+ level = 1; // Treat ===key as a top-level heading
54
+ name = (m[3] ?? "").trim();
55
+ }
56
+ else if (m[4]) {
57
+ // It's a bullet (- / * / +)
58
+ const bulletChar = m[4];
59
+ const bulletText = (m[5] ?? "").trim();
60
+ const norm = normalizeName(bulletText);
61
+ if (bulletNamesNormal.has(norm)) {
62
+ level = 1;
63
+ name = bulletText;
64
+ }
65
+ else {
66
+ // Not a recognized section header bullet, ignore it
67
+ continue;
68
+ }
69
+ }
70
+ else {
71
+ const hashesMatch = full.match(/^#+/);
72
+ const hashes = hashesMatch ? hashesMatch[0] : "";
73
+ level = hashes.length;
74
+ name = (m[2] ?? "").trim();
75
+ }
76
+ const raw = m[0] ?? "";
77
+ const start = m.index;
78
+ const end = start + raw.length;
79
+ headings.push({
80
+ level,
81
+ raw,
82
+ name,
83
+ norm: normalizeName(name),
84
+ start,
85
+ end,
86
+ });
87
+ }
88
+ // compute section bodies
89
+ const sections = [];
90
+ for (let i = 0; i < headings.length; i++) {
91
+ const h = headings[i];
92
+ const bodyStart = nextLineIndex(md, h.end);
93
+ let bodyEnd = md.length;
94
+ for (let j = i + 1; j < headings.length; j++) {
95
+ const nxt = headings[j];
96
+ bodyEnd = nxt.start;
97
+ break;
98
+ }
99
+ sections.push({
100
+ heading: h,
101
+ bodyStart,
102
+ bodyEnd,
103
+ body: md.slice(bodyStart, bodyEnd).trimEnd(),
104
+ });
105
+ }
106
+ return sections;
107
+ }
108
+ function nextLineIndex(text, idx) {
109
+ if (idx >= text.length)
110
+ return text.length;
111
+ if (text[idx] === "\n")
112
+ return idx + 1;
113
+ const n = text.indexOf("\n", idx);
114
+ return n === -1 ? text.length : n + 1;
115
+ }
116
+ function extractBullets(body) {
117
+ const lines = body.split(/\r?\n/);
118
+ const out = [];
119
+ for (const line of lines) {
120
+ const m = line.match(/^\s*[-*•]\s+(.*)\s*$/) || line.match(/^\s*\d+\.\s+(.*)\s*$/);
121
+ if (m)
122
+ out.push(m[1].trim());
123
+ }
124
+ return out;
125
+ }
126
+ function parseMarkdownTable(body, columns) {
127
+ const lines = body.split(/\r?\n/).map(l => l.trim()).filter(l => l.startsWith("|"));
128
+ if (lines.length < 2)
129
+ return [];
130
+ // Identification of header vs separator
131
+ const headerLine = lines[0];
132
+ const separatorLine = lines[1];
133
+ // Check if second line is a separator (e.g. |---|---|)
134
+ const isSeparator = separatorLine && /^[|\s-:]+$/.test(separatorLine);
135
+ const dataStartIndex = isSeparator ? 2 : 1;
136
+ const dataLines = lines.slice(dataStartIndex);
137
+ const results = [];
138
+ for (const line of dataLines) {
139
+ const cells = line.split("|").map(c => c.trim()).filter((_, i, arr) => i > 0 && i < arr.length - 1);
140
+ const row = {};
141
+ // Match cells to provided columns by index
142
+ for (let i = 0; i < columns.length; i++) {
143
+ row[columns[i]] = cells[i] || "";
144
+ }
145
+ results.push(row);
146
+ }
147
+ return results;
148
+ }
149
+ function isIssuesEnvelopeCheck(md) {
150
+ const parsed = parseHeadingsAndSections(md);
151
+ const want = ["status", "issues", "expected", "found", "how to fix"].map(normalizeName);
152
+ const got = new Set(parsed.map(s => s.heading.norm));
153
+ const ok = want.every(w => got.has(w));
154
+ const sections = {};
155
+ if (ok) {
156
+ for (const sec of parsed) {
157
+ if (want.includes(sec.heading.norm)) {
158
+ sections[sec.heading.norm] = {
159
+ heading: sec.heading.name,
160
+ body: sec.body.trim(),
161
+ bullets: extractBullets(sec.body),
162
+ };
163
+ }
164
+ }
165
+ }
166
+ return { isIssuesEnvelope: ok, sections };
167
+ }
168
+ function markdownToJson(md) {
169
+ // Robustly handle both actual newlines and literal \n (common in LLM JSON outputs)
170
+ const normalizedMd = (0, normalize_js_1.normalizeMarkdownInput)(md);
171
+ // Collect all bullet names that look like headers ("- Name")
172
+ // We look for patterns like "- Name\n" at the start of lines, ensuring it's not a sub-bullet.
173
+ const bulletNames = [];
174
+ const bulletLinesRx = /^[-*+]\s+([^—:\n\r]{2,50})$/gm;
175
+ let m;
176
+ while ((m = bulletLinesRx.exec(normalizedMd)) !== null) {
177
+ bulletNames.push(m[1].trim());
178
+ }
179
+ // Use Flex-MD's native parser (supports === headings and avoids colon-as-object bug)
180
+ const sections = parseHeadingsAndSections(normalizedMd, { bulletNames });
181
+ const result = {};
182
+ for (const sec of sections) {
183
+ const key = (0, nx_helpers_1.toCamelCase)(sec.heading.name);
184
+ const body = sec.body.trim();
185
+ // 1. Try to detect list
186
+ const bullets = extractBullets(body);
187
+ if (bullets.length > 0) {
188
+ result[key] = bullets;
189
+ continue;
190
+ }
191
+ // 2. Try to detect table (basic check)
192
+ const lines = body.split("\n").map(l => l.trim()).filter(l => l);
193
+ if (lines.length >= 2 && lines[0].startsWith("|") && /^[|\s-:]+$/.test(lines[1])) {
194
+ // It looks like a table - we could use nx-md-parser's table logic here safely
195
+ }
196
+ result[key] = body;
197
+ }
198
+ return result;
199
+ }
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ofsToSchema = ofsToSchema;
4
+ exports.transformWithOfs = transformWithOfs;
5
+ const nx_md_parser_1 = require("nx-md-parser");
6
+ const memory_js_1 = require("./memory.cjs");
7
+ const parse_js_1 = require("../md/parse.cjs");
8
+ const normalize_js_1 = require("../md/normalize.cjs");
9
+ const nx_helpers_1 = require("nx-helpers");
10
+ const nx_json_parser_1 = require("nx-json-parser");
11
+ const logger_js_1 = require("../logger.cjs");
12
+ /**
13
+ * Converts a Flex-MD OutputFormatSpec to an nx-md-parser Schema.
14
+ */
15
+ function ofsToSchema(spec) {
16
+ const properties = {};
17
+ for (const section of spec.sections) {
18
+ let type;
19
+ switch (section.kind) {
20
+ case "list":
21
+ case "ordered_list":
22
+ type = nx_md_parser_1.Schema.array(nx_md_parser_1.Schema.string());
23
+ break;
24
+ case "table":
25
+ case "ordered_table":
26
+ if (section.columns && section.columns.length > 0) {
27
+ const rowProps = {};
28
+ for (const col of section.columns) {
29
+ rowProps[col] = nx_md_parser_1.Schema.string();
30
+ }
31
+ type = nx_md_parser_1.Schema.array(nx_md_parser_1.Schema.object(rowProps));
32
+ }
33
+ else {
34
+ type = nx_md_parser_1.Schema.string();
35
+ }
36
+ break;
37
+ case "text":
38
+ default:
39
+ type = nx_md_parser_1.Schema.string();
40
+ break;
41
+ }
42
+ properties[section.name] = type;
43
+ }
44
+ if (spec.tables && spec.tables.length > 0) {
45
+ for (const table of spec.tables) {
46
+ const tableKey = table.columns.join("_");
47
+ const rowProps = {};
48
+ for (const col of table.columns) {
49
+ rowProps[col] = nx_md_parser_1.Schema.string();
50
+ }
51
+ properties[tableKey] = nx_md_parser_1.Schema.array(nx_md_parser_1.Schema.object(rowProps));
52
+ }
53
+ }
54
+ return nx_md_parser_1.Schema.object(properties);
55
+ }
56
+ /**
57
+ * Transforms markdown text using a Flex-MD OutputFormatSpec or a recallId.
58
+ * If no spec is provided, it attempts to infer it from the markdown (autospecs).
59
+ */
60
+ function transformWithOfs(md, specOrRecallId) {
61
+ logger_js_1.logger.info("Starting Flex-MD transformation", {
62
+ inputLength: md.length,
63
+ hasSpec: !!specOrRecallId,
64
+ specType: specOrRecallId ? typeof specOrRecallId : 'none'
65
+ });
66
+ // 0. Normalize input (handle literal \n common in LLM outputs)
67
+ const normalizedMd = (0, normalize_js_1.normalizeMarkdownInput)(md);
68
+ logger_js_1.logger.debug("Input normalization", {
69
+ originalLength: md.length,
70
+ normalizedLength: normalizedMd.length,
71
+ changed: md !== normalizedMd
72
+ });
73
+ // 1. Automatic parsing (Dual-Response) using nx-json-parser
74
+ logger_js_1.logger.debug("Starting automatic parsing with nx-json-parser");
75
+ const parsedOutput = (0, nx_json_parser_1.markdownToJson)(normalizedMd);
76
+ logger_js_1.logger.debug("Automatic parsing completed", {
77
+ parsedKeys: Object.keys(parsedOutput),
78
+ parsedKeyCount: Object.keys(parsedOutput).length
79
+ });
80
+ if (!specOrRecallId) {
81
+ logger_js_1.logger.info("No spec provided, returning parsed output only");
82
+ return {
83
+ parsedOutput,
84
+ contractOutput: null,
85
+ contractStatus: "skipped",
86
+ status: "validated",
87
+ errors: []
88
+ };
89
+ }
90
+ let spec;
91
+ if (typeof specOrRecallId === "string") {
92
+ logger_js_1.logger.debug("Recalling spec from memory", { recallId: specOrRecallId });
93
+ const recalled = (0, memory_js_1.recall)(specOrRecallId);
94
+ if (!recalled) {
95
+ logger_js_1.logger.warn("Recall ID not found", { recallId: specOrRecallId });
96
+ return {
97
+ parsedOutput,
98
+ contractOutput: null,
99
+ contractStatus: "skipped",
100
+ status: "failed",
101
+ errors: [`Recall ID "${specOrRecallId}" not found in memory.`]
102
+ };
103
+ }
104
+ spec = recalled;
105
+ logger_js_1.logger.debug("Spec recalled successfully", {
106
+ sectionCount: spec.sections.length,
107
+ sectionNames: spec.sections.map(s => s.name)
108
+ });
109
+ }
110
+ else {
111
+ spec = specOrRecallId;
112
+ logger_js_1.logger.debug("Using provided spec directly", {
113
+ sectionCount: spec.sections.length,
114
+ sectionNames: spec.sections.map(s => s.name)
115
+ });
116
+ }
117
+ // 2. Parse sections using Flex-MD parser for the contract mapping
118
+ const bulletNames = spec.sections.map(s => s.name);
119
+ logger_js_1.logger.debug("Starting section parsing", {
120
+ expectedSections: bulletNames,
121
+ sectionCount: bulletNames.length
122
+ });
123
+ // Note: We use the local headings parser to find the specific sections defined in the spec
124
+ const parsedSections = (0, parse_js_1.parseHeadingsAndSections)(normalizedMd, { bulletNames });
125
+ logger_js_1.logger.debug("Section parsing completed", {
126
+ foundSections: parsedSections.map(s => s.heading.name),
127
+ foundCount: parsedSections.length
128
+ });
129
+ const parsedObj = {};
130
+ for (const sectionSpec of spec.sections) {
131
+ const normName = (0, parse_js_1.normalizeName)(sectionSpec.name);
132
+ const found = parsedSections.find(s => (0, parse_js_1.normalizeName)(s.heading.name) === normName);
133
+ logger_js_1.logger.verbose(`Processing section "${sectionSpec.name}"`, {
134
+ normalizedName: normName,
135
+ found: !!found,
136
+ kind: sectionSpec.kind,
137
+ hasColumns: !!sectionSpec.columns
138
+ });
139
+ if (found) {
140
+ let value;
141
+ switch (sectionSpec.kind) {
142
+ case "list":
143
+ case "ordered_list":
144
+ value = (0, parse_js_1.extractBullets)(found.body);
145
+ logger_js_1.logger.debug(`Extracted bullets for "${sectionSpec.name}"`, {
146
+ bulletCount: Array.isArray(value) ? value.length : 'unknown'
147
+ });
148
+ break;
149
+ case "table":
150
+ case "ordered_table":
151
+ if (sectionSpec.columns) {
152
+ value = (0, parse_js_1.parseMarkdownTable)(found.body, sectionSpec.columns);
153
+ logger_js_1.logger.debug(`Parsed table for "${sectionSpec.name}"`, {
154
+ columns: sectionSpec.columns,
155
+ rowCount: Array.isArray(value) ? value.length : 'unknown'
156
+ });
157
+ }
158
+ else {
159
+ value = found.body.trim();
160
+ logger_js_1.logger.debug(`Using raw body for table "${sectionSpec.name}" (no columns specified)`);
161
+ }
162
+ break;
163
+ default:
164
+ value = found.body.trim();
165
+ logger_js_1.logger.debug(`Using trimmed body for "${sectionSpec.name}"`);
166
+ break;
167
+ }
168
+ parsedObj[sectionSpec.name] = value;
169
+ }
170
+ else {
171
+ logger_js_1.logger.warn(`Section "${sectionSpec.name}" not found in markdown`);
172
+ }
173
+ }
174
+ logger_js_1.logger.debug("Contract parsing completed", {
175
+ parsedKeys: Object.keys(parsedObj),
176
+ parsedKeyCount: Object.keys(parsedObj).length
177
+ });
178
+ // 3. Transform using nx-md-parser (latest v2.2.0) for schema validation and fixing
179
+ const schema = ofsToSchema(spec);
180
+ const transformer = new nx_md_parser_1.JSONTransformer(schema);
181
+ const transformResult = transformer.transform(parsedObj);
182
+ // 4. Compare parsed results with contract results
183
+ const autoKeys = Object.keys(parsedOutput).sort();
184
+ const contractKeys = transformResult.result ? Object.keys(transformResult.result).map(k => (0, nx_helpers_1.toCamelCase)(k)).sort() : [];
185
+ const isSame = autoKeys.length > 0 &&
186
+ autoKeys.every(k => contractKeys.includes(k)) &&
187
+ contractKeys.length === autoKeys.length;
188
+ return {
189
+ parsedOutput,
190
+ contractOutput: transformResult.result,
191
+ contractStatus: isSame ? "ok" : "different",
192
+ status: transformResult.status,
193
+ errors: transformResult.errors || []
194
+ };
195
+ }
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildMarkdownGuidance = buildMarkdownGuidance;
4
+ exports.enrichInstructions = enrichInstructions;
5
+ exports.enrichInstructionsWithFlexMd = enrichInstructionsWithFlexMd;
6
+ /**
7
+ * Generates Markdown guidance instructions for the LLM based on the OFS and contract level.
8
+ * Strictly avoids the word "flex-md" and remains "tax-aware" by only including relevant rules.
9
+ */
10
+ function buildMarkdownGuidance(spec, strict, opts) {
11
+ const level = strict.level ?? 0;
12
+ const fence = opts?.containerFence === "flexmd" ? "```flexmd" : "```markdown";
13
+ // L0 - Minimal Markdown
14
+ if (level === 0) {
15
+ return "Reply in Markdown.";
16
+ }
17
+ const lines = [];
18
+ // L2+ Container Rule
19
+ if (level >= 2) {
20
+ lines.push(`Return your entire answer inside a single ${fence} fenced block and nothing else.`);
21
+ lines.push("");
22
+ lines.push("Inside the block, include these section headings somewhere (order does not matter):");
23
+ }
24
+ else {
25
+ lines.push("Reply in Markdown.");
26
+ lines.push("");
27
+ lines.push("Include these section headings somewhere (order does not matter):");
28
+ }
29
+ // Heading List
30
+ for (const s of spec.sections) {
31
+ let suffix = "";
32
+ if (level >= 3) {
33
+ if (s.kind === "list")
34
+ suffix = " (list)";
35
+ else if (s.kind === "ordered_list")
36
+ suffix = " (ordered list)";
37
+ }
38
+ lines.push(`- ${s.name}${suffix}`);
39
+ if (level >= 1) {
40
+ if (s.description) {
41
+ lines.push(` Description: ${s.description}`);
42
+ }
43
+ if (s.instruction) {
44
+ lines.push(` Instruction: ${s.instruction}`);
45
+ }
46
+ }
47
+ }
48
+ lines.push("");
49
+ // None Rule
50
+ const noneRule = opts?.includeNoneRule ?? "auto";
51
+ const hasRequired = spec.sections.some(s => s.required !== false);
52
+ const includeNone = noneRule === "always" || (noneRule === "auto" && (hasRequired || level >= 3));
53
+ if (includeNone) {
54
+ lines.push(`If a section is empty, write \`None\`.`);
55
+ lines.push("");
56
+ }
57
+ // List Rules
58
+ const listRule = opts?.includeListRules ?? "auto";
59
+ const hasListDecls = spec.sections.some(s => s.kind === "list" || s.kind === "ordered_list");
60
+ const includeList = listRule === "always" || (listRule === "auto" && (hasListDecls || level >= 3));
61
+ if (includeList) {
62
+ lines.push("List rules:");
63
+ lines.push("- Use numbered lists (`1.`, `2.`, …) for ordered lists.");
64
+ lines.push("- Use `-` bullets for unordered lists.");
65
+ lines.push("");
66
+ }
67
+ // Table Rules
68
+ const tableRule = opts?.includeTableRules ?? "auto";
69
+ const hasTableDecls = (spec.tables && spec.tables.length > 0) || spec.sections.some(s => s.kind === "table" || s.kind === "ordered_table");
70
+ const includeTable = tableRule === "always" || (tableRule === "auto" && (hasTableDecls || level >= 3));
71
+ if (includeTable) {
72
+ const hasOrderedTable = spec.tables?.some(t => t.kind === "ordered_table") || spec.sections.some(s => s.kind === "ordered_table");
73
+ lines.push("Tables:");
74
+ lines.push("- If you include a table, use a Markdown pipe table.");
75
+ if (hasOrderedTable || level >= 3) {
76
+ lines.push("- For ordered tables, add a first column named `#` and number rows 1..N.");
77
+ }
78
+ lines.push("");
79
+ }
80
+ if (level >= 3) {
81
+ lines.push("Do not return JSON as the response format.");
82
+ }
83
+ return lines.join("\n").trim();
84
+ }
85
+ /**
86
+ * @deprecated Use buildMarkdownGuidance
87
+ */
88
+ function enrichInstructions(spec, strict) {
89
+ return buildMarkdownGuidance(spec, strict);
90
+ }
91
+ /**
92
+ * Enrich instructions with flex-md compliance guidance.
93
+ * Returns detailed information about what was changed.
94
+ */
95
+ async function enrichInstructionsWithFlexMd(instructions, strictnessLevel, spec // Optional spec, if provided will include section guidance
96
+ ) {
97
+ const originalLength = instructions.length;
98
+ const level = parseInt(strictnessLevel.slice(1));
99
+ // Default spec if none provided (minimal)
100
+ const effectiveSpec = spec ?? { sections: [] };
101
+ const guidance = buildMarkdownGuidance(effectiveSpec, { level });
102
+ // Check if guidance is already present (simple check)
103
+ if (instructions.includes(guidance)) {
104
+ return {
105
+ enriched: instructions,
106
+ changes: [],
107
+ originalLength,
108
+ enhancedLength: originalLength,
109
+ metadata: {
110
+ sectionsAdded: 0,
111
+ containerAdded: false,
112
+ guidanceAdded: false
113
+ }
114
+ };
115
+ }
116
+ const separator = instructions.trim().length > 0 ? "\n\n" : "";
117
+ const enriched = instructions.trim() + separator + guidance;
118
+ const enhancedLength = enriched.length;
119
+ const changes = [
120
+ {
121
+ type: "added-guidance",
122
+ description: `Added Flex-MD ${strictnessLevel} compliance guidance.`,
123
+ location: {
124
+ start: instructions.trim().length + separator.length,
125
+ end: enhancedLength
126
+ },
127
+ content: guidance
128
+ }
129
+ ];
130
+ if (level >= 2) {
131
+ changes.push({
132
+ type: "added-container",
133
+ description: "Added requirement for a fenced container block.",
134
+ location: {
135
+ start: instructions.trim().length + separator.length,
136
+ end: enhancedLength
137
+ }
138
+ });
139
+ }
140
+ return {
141
+ enriched,
142
+ changes,
143
+ originalLength,
144
+ enhancedLength,
145
+ metadata: {
146
+ sectionsAdded: effectiveSpec.sections.length,
147
+ containerAdded: level >= 2,
148
+ guidanceAdded: true
149
+ }
150
+ };
151
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inferOfsFromMarkdown = inferOfsFromMarkdown;
4
+ const parse_js_1 = require("../md/parse.cjs");
5
+ /**
6
+ * Infers an OutputFormatSpec from a Markdown string.
7
+ */
8
+ function inferOfsFromMarkdown(md) {
9
+ // Collect all bullet names that look like headers ("- Name")
10
+ const lines = md.split("\n");
11
+ const bulletNames = [];
12
+ for (const line of lines) {
13
+ // Match "- Name" or "- Name\n" or "- Name " but NOT "- Name: more text"
14
+ const m = line.match(/^[-*+]\s+([^—:\n]+)$/);
15
+ if (m) {
16
+ bulletNames.push(m[1].trim());
17
+ }
18
+ }
19
+ const sections = (0, parse_js_1.parseHeadingsAndSections)(md, { bulletNames });
20
+ const specSections = [];
21
+ for (const sec of sections) {
22
+ const name = sec.heading.name;
23
+ const body = sec.body.trim();
24
+ // 1. Detect list
25
+ const bullets = (0, parse_js_1.extractBullets)(body);
26
+ if (bullets.length > 0) {
27
+ specSections.push({
28
+ name,
29
+ kind: "list",
30
+ required: true
31
+ });
32
+ continue;
33
+ }
34
+ // 2. Detect table (basic check)
35
+ const lines = body.split("\n").map(l => l.trim()).filter(Boolean);
36
+ if (lines.length >= 2 && lines[0].startsWith("|") && /^[|\s-:]+$/.test(lines[1])) {
37
+ // Extract columns
38
+ const cols = lines[0].split("|").map(c => c.trim()).filter(Boolean);
39
+ specSections.push({
40
+ name,
41
+ kind: "table",
42
+ columns: cols,
43
+ required: true
44
+ });
45
+ continue;
46
+ }
47
+ // Default to text
48
+ specSections.push({
49
+ name,
50
+ kind: "text",
51
+ required: true
52
+ });
53
+ }
54
+ return {
55
+ descriptorType: "output_format_spec",
56
+ format: "markdown",
57
+ sectionOrderMatters: false,
58
+ sections: specSections,
59
+ tablesOptional: true,
60
+ tables: [],
61
+ emptySectionValue: "None"
62
+ };
63
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseIssuesEnvelope = parseIssuesEnvelope;
4
+ exports.buildIssuesEnvelope = buildIssuesEnvelope;
5
+ exports.buildIssuesEnvelopeAuto = buildIssuesEnvelopeAuto;
6
+ const parse_js_1 = require("../md/parse.cjs");
7
+ function parseIssuesEnvelope(md) {
8
+ return (0, parse_js_1.isIssuesEnvelopeCheck)(md);
9
+ }
10
+ function buildIssuesEnvelope(args) {
11
+ const issues = args.validation.issues
12
+ .filter(i => i.severity === "warn" || i.severity === "error")
13
+ .map(i => `- ${i.message}`);
14
+ const expected = args.expectedSummary.map(x => `- ${x}`);
15
+ const found = args.foundSummary.map(x => `- ${x}`);
16
+ const how = args.howToFix.map(x => `- ${x}`);
17
+ return [
18
+ "## Status",
19
+ "Non-compliant output (cannot be repaired to the required format).",
20
+ "",
21
+ "## Issues",
22
+ issues.length ? issues.join("\n") : "- None",
23
+ "",
24
+ "## Expected",
25
+ expected.length ? expected.join("\n") : "- None",
26
+ "",
27
+ "## Found",
28
+ found.length ? found.join("\n") : "- None",
29
+ "",
30
+ "## How to fix",
31
+ how.length ? how.join("\n") : "- None",
32
+ "",
33
+ ].join("\n");
34
+ }
35
+ function buildIssuesEnvelopeAuto(args) {
36
+ const v = args.validation;
37
+ const expected = [];
38
+ const found = [];
39
+ const how = [];
40
+ if (args.level >= 2) {
41
+ expected.push("One single ```markdown fenced block containing the entire answer (no text outside).");
42
+ }
43
+ if (args.level >= 1 && (args.requiredSectionNames?.length ?? 0) > 0) {
44
+ expected.push(`Section headings present (order does not matter): ${args.requiredSectionNames.join(", ")}`);
45
+ }
46
+ if (args.level >= 3 && (args.kindRequirements?.length ?? 0) > 0) {
47
+ for (const k of args.kindRequirements)
48
+ expected.push(k);
49
+ }
50
+ if (v.stats) {
51
+ found.push(`Detected sections: ${v.stats.sectionCount}`);
52
+ if (v.stats.missingRequired.length)
53
+ found.push(`Missing: ${v.stats.missingRequired.join(", ")}`);
54
+ if (v.stats.duplicates.length)
55
+ found.push(`Duplicates: ${v.stats.duplicates.join(", ")}`);
56
+ found.push(`Fences: ${v.stats.container.fenceCount} (langs: ${v.stats.container.fenceLangs.join(", ") || "none"})`);
57
+ found.push(`Outside text: ${v.stats.container.hasOutsideText ? "yes" : "no"}`);
58
+ }
59
+ if (args.level >= 2)
60
+ how.push("Return a single ```markdown fenced block and nothing else.");
61
+ if (args.level >= 1)
62
+ how.push("Include the required headings and put content under each (order does not matter).");
63
+ how.push('If a section is empty, write "None".');
64
+ if (args.level >= 3) {
65
+ how.push('Use numbered lists ("1.", "2.", …) for ordered lists and "-" bullets for unordered lists.');
66
+ how.push("Use Markdown pipe tables for table sections; ordered tables include a first column '#'.");
67
+ how.push("Do not return JSON as the response format.");
68
+ }
69
+ return buildIssuesEnvelope({
70
+ validation: v,
71
+ level: args.level,
72
+ expectedSummary: expected,
73
+ foundSummary: found,
74
+ howToFix: how,
75
+ });
76
+ }