flex-md 4.7.2 → 4.7.3
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.
- package/README.md +1 -1
- package/dist/__tests__/diagnostics.test.js +45 -47
- package/dist/__tests__/ofs.test.js +28 -30
- package/dist/__tests__/structural.test.js +19 -21
- package/dist/__tests__/validate.test.js +27 -29
- package/dist/cli/index.js +13 -15
- package/dist/detect/json/detectIntent.js +1 -4
- package/dist/detect/json/detectMarkdown.js +29 -35
- package/dist/detect/json/detectPresence.js +2 -6
- package/dist/detect/json/index.js +10 -31
- package/dist/detect/json/types.js +1 -2
- package/dist/extract/extract.js +11 -14
- package/dist/extract/types.js +1 -2
- package/dist/index.js +22 -69
- package/dist/logger.js +3 -6
- package/dist/md/match.js +1 -4
- package/dist/md/normalize.js +1 -4
- package/dist/md/outline.js +4 -8
- package/dist/md/parse.js +11 -20
- package/dist/ofs/adapter.js +45 -49
- package/dist/ofs/enricher.js +3 -8
- package/dist/ofs/infer.js +4 -7
- package/dist/ofs/issuesEnvelope.js +5 -10
- package/dist/ofs/memory.js +6 -10
- package/dist/ofs/parser.js +7 -13
- package/dist/ofs/stringify.js +1 -4
- package/dist/pipeline/enforce.js +12 -15
- package/dist/pipeline/kind.js +3 -6
- package/dist/pipeline/repair.js +8 -11
- package/dist/strictness/container.js +1 -4
- package/dist/strictness/processor.js +7 -10
- package/dist/strictness/types.js +1 -4
- package/dist/tokens/auto-fix.js +1 -4
- package/dist/tokens/cognitive-cost.js +6 -10
- package/dist/tokens/compliance.js +4 -8
- package/dist/tokens/confidence.js +6 -9
- package/dist/tokens/estimator.js +20 -26
- package/dist/tokens/improvements.js +12 -16
- package/dist/tokens/index.js +22 -40
- package/dist/tokens/parser.js +4 -7
- package/dist/tokens/patterns.js +2 -5
- package/dist/tokens/runtime-estimator.js +9 -12
- package/dist/tokens/smart-report.js +10 -14
- package/dist/tokens/spec-estimator.js +10 -15
- package/dist/tokens/types.js +1 -2
- package/dist/tokens/validator.js +3 -6
- package/dist/types.js +1 -2
- package/dist/validate/compliance.js +4 -8
- package/dist/validate/connection.js +5 -8
- package/dist/validate/types.js +1 -2
- package/dist/validate/validate.js +16 -19
- package/dist-cjs/__tests__/diagnostics.test.cjs +61 -0
- package/dist-cjs/__tests__/ofs.test.cjs +53 -0
- package/dist-cjs/__tests__/structural.test.cjs +30 -0
- package/dist-cjs/__tests__/validate.test.cjs +110 -0
- package/dist-cjs/cli/index.cjs +110 -0
- package/dist-cjs/detect/json/detectIntent.cjs +82 -0
- package/dist-cjs/detect/json/detectMarkdown.cjs +304 -0
- package/dist-cjs/detect/json/detectPresence.cjs +195 -0
- package/dist-cjs/detect/json/index.cjs +34 -0
- package/dist-cjs/detect/json/types.cjs +2 -0
- package/dist-cjs/extract/extract.cjs +72 -0
- package/dist-cjs/extract/types.cjs +2 -0
- package/dist-cjs/flex-md-loader.cjs +102 -0
- package/dist-cjs/index.cjs +79 -0
- package/dist-cjs/logger.cjs +22 -0
- package/dist-cjs/md/match.cjs +47 -0
- package/dist-cjs/md/normalize.cjs +13 -0
- package/dist-cjs/md/outline.cjs +49 -0
- package/dist-cjs/md/parse.cjs +199 -0
- package/dist-cjs/ofs/adapter.cjs +195 -0
- package/dist-cjs/ofs/enricher.cjs +151 -0
- package/dist-cjs/ofs/infer.cjs +63 -0
- package/dist-cjs/ofs/issuesEnvelope.cjs +76 -0
- package/dist-cjs/ofs/memory.cjs +26 -0
- package/dist-cjs/ofs/parser.cjs +373 -0
- package/dist-cjs/ofs/stringify.cjs +45 -0
- package/dist-cjs/pipeline/enforce.cjs +49 -0
- package/dist-cjs/pipeline/kind.cjs +30 -0
- package/dist-cjs/pipeline/repair.cjs +115 -0
- package/dist-cjs/strictness/container.cjs +49 -0
- package/dist-cjs/strictness/processor.cjs +32 -0
- package/dist-cjs/strictness/types.cjs +109 -0
- package/dist-cjs/tokens/auto-fix.cjs +59 -0
- package/dist-cjs/tokens/cognitive-cost.cjs +209 -0
- package/dist-cjs/tokens/compliance.cjs +74 -0
- package/dist-cjs/tokens/confidence.cjs +335 -0
- package/dist-cjs/tokens/estimator.cjs +157 -0
- package/dist-cjs/tokens/improvements.cjs +701 -0
- package/dist-cjs/tokens/index.cjs +74 -0
- package/dist-cjs/tokens/parser.cjs +100 -0
- package/dist-cjs/tokens/patterns.cjs +23 -0
- package/dist-cjs/tokens/runtime-estimator.cjs +74 -0
- package/dist-cjs/tokens/smart-report.cjs +191 -0
- package/dist-cjs/tokens/spec-estimator.cjs +125 -0
- package/dist-cjs/tokens/types.cjs +2 -0
- package/dist-cjs/tokens/validator.cjs +62 -0
- package/dist-cjs/types.cjs +2 -0
- package/dist-cjs/validate/compliance.cjs +103 -0
- package/dist-cjs/validate/connection.cjs +47 -0
- package/dist-cjs/validate/types.cjs +2 -0
- package/dist-cjs/validate/validate.cjs +319 -0
- package/docs/consumption.md +1 -1
- 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
|
+
}
|