flex-md 2.0.0 → 3.1.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.
- package/README.md +57 -309
- package/dist/__tests__/ofs.test.d.ts +1 -0
- package/dist/__tests__/ofs.test.js +51 -0
- package/dist/__tests__/validate.test.d.ts +1 -0
- package/dist/__tests__/validate.test.js +108 -0
- package/dist/detect/json/detectIntent.d.ts +2 -0
- package/dist/detect/json/detectIntent.js +79 -0
- package/dist/detect/json/detectPresence.d.ts +6 -0
- package/dist/detect/json/detectPresence.js +191 -0
- package/dist/detect/json/index.d.ts +7 -0
- package/dist/detect/json/index.js +12 -0
- package/dist/detect/json/types.d.ts +43 -0
- package/dist/detect/json/types.js +1 -0
- package/dist/extract/extract.d.ts +5 -0
- package/dist/extract/extract.js +50 -0
- package/dist/extract/types.d.ts +11 -0
- package/dist/extract/types.js +1 -0
- package/dist/index.d.ts +11 -15
- package/dist/index.js +18 -17
- package/dist/issues/build.d.ts +26 -0
- package/dist/issues/build.js +62 -0
- package/dist/md/lists.d.ts +14 -0
- package/dist/md/lists.js +33 -0
- package/dist/md/match.d.ts +12 -0
- package/dist/md/match.js +44 -0
- package/dist/md/outline.d.ts +6 -0
- package/dist/md/outline.js +67 -0
- package/dist/md/parse.d.ts +29 -0
- package/dist/md/parse.js +105 -0
- package/dist/md/tables.d.ts +25 -0
- package/dist/md/tables.js +72 -0
- package/dist/ofs/enricher.d.ts +14 -4
- package/dist/ofs/enricher.js +76 -20
- package/dist/ofs/issues.d.ts +14 -0
- package/dist/ofs/issues.js +92 -0
- package/dist/ofs/issuesEnvelope.d.ts +15 -0
- package/dist/ofs/issuesEnvelope.js +71 -0
- package/dist/ofs/parser.d.ts +5 -17
- package/dist/ofs/parser.js +114 -45
- package/dist/ofs/stringify.js +33 -21
- package/dist/pipeline/enforce.d.ts +10 -0
- package/dist/pipeline/enforce.js +46 -0
- package/dist/pipeline/kind.d.ts +16 -0
- package/dist/pipeline/kind.js +24 -0
- package/dist/pipeline/repair.d.ts +14 -0
- package/dist/pipeline/repair.js +112 -0
- package/dist/strictness/container.d.ts +14 -0
- package/dist/strictness/container.js +46 -0
- package/dist/strictness/processor.d.ts +5 -0
- package/dist/strictness/processor.js +29 -0
- package/dist/strictness/types.d.ts +77 -0
- package/dist/strictness/types.js +106 -0
- package/dist/test-pipeline.d.ts +1 -0
- package/dist/test-pipeline.js +53 -0
- package/dist/test-runner.js +10 -7
- package/dist/test-strictness.d.ts +1 -0
- package/dist/test-strictness.js +213 -0
- package/dist/types.d.ts +82 -43
- package/dist/validate/policy.d.ts +10 -0
- package/dist/validate/policy.js +17 -0
- package/dist/validate/types.d.ts +11 -0
- package/dist/validate/types.js +1 -0
- package/dist/validate/validate.d.ts +2 -0
- package/dist/validate/validate.js +308 -0
- package/docs/mdflex-compliance.md +216 -0
- package/package.json +7 -3
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export function strictnessDefaults(level) {
|
|
2
|
+
const common = {
|
|
3
|
+
tablesGuidance: "auto",
|
|
4
|
+
listsGuidance: "auto",
|
|
5
|
+
emptyNoneGuidance: "auto",
|
|
6
|
+
containerGuidance: "auto",
|
|
7
|
+
typedJsonGuidance: "auto",
|
|
8
|
+
acceptAnyHeadingLevel: true,
|
|
9
|
+
duplicateSectionStrategy: "merge",
|
|
10
|
+
};
|
|
11
|
+
if (level === 0) {
|
|
12
|
+
return {
|
|
13
|
+
...common,
|
|
14
|
+
level,
|
|
15
|
+
container: "none",
|
|
16
|
+
requireOfs: false,
|
|
17
|
+
requireAllSections: false,
|
|
18
|
+
policy: {
|
|
19
|
+
outsideContainer: "ignore",
|
|
20
|
+
missingContainer: "ignore",
|
|
21
|
+
multipleContainers: "ignore",
|
|
22
|
+
missingRequiredSection: "warn",
|
|
23
|
+
missingOptionalSection: "ignore",
|
|
24
|
+
wrongSectionKindRequired: "warn",
|
|
25
|
+
wrongSectionKindOptional: "ignore",
|
|
26
|
+
emptySectionWithoutNoneRequired: "warn",
|
|
27
|
+
emptySectionWithoutNoneOptional: "ignore",
|
|
28
|
+
missingRequiredTable: "warn",
|
|
29
|
+
missingOptionalTable: "ignore",
|
|
30
|
+
orderedTableViolationsRequired: "warn",
|
|
31
|
+
orderedTableViolationsOptional: "ignore",
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (level === 1) {
|
|
36
|
+
return {
|
|
37
|
+
...common,
|
|
38
|
+
level,
|
|
39
|
+
container: "none",
|
|
40
|
+
requireOfs: true,
|
|
41
|
+
requireAllSections: true,
|
|
42
|
+
policy: {
|
|
43
|
+
missingRequiredSection: "error",
|
|
44
|
+
missingOptionalSection: "ignore",
|
|
45
|
+
wrongSectionKindRequired: "error",
|
|
46
|
+
wrongSectionKindOptional: "warn",
|
|
47
|
+
emptySectionWithoutNoneRequired: "error",
|
|
48
|
+
emptySectionWithoutNoneOptional: "warn",
|
|
49
|
+
missingRequiredTable: "error",
|
|
50
|
+
missingOptionalTable: "ignore",
|
|
51
|
+
orderedTableViolationsRequired: "error",
|
|
52
|
+
orderedTableViolationsOptional: "warn",
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (level === 2) {
|
|
57
|
+
return {
|
|
58
|
+
...common,
|
|
59
|
+
level,
|
|
60
|
+
container: "markdown_fence",
|
|
61
|
+
requireSingleContainer: true,
|
|
62
|
+
requireOfs: true,
|
|
63
|
+
requireAllSections: true,
|
|
64
|
+
policy: {
|
|
65
|
+
outsideContainer: "error",
|
|
66
|
+
missingContainer: "error",
|
|
67
|
+
multipleContainers: "error",
|
|
68
|
+
missingRequiredSection: "error",
|
|
69
|
+
missingOptionalSection: "ignore",
|
|
70
|
+
wrongSectionKindRequired: "error",
|
|
71
|
+
wrongSectionKindOptional: "warn",
|
|
72
|
+
emptySectionWithoutNoneRequired: "error",
|
|
73
|
+
emptySectionWithoutNoneOptional: "warn",
|
|
74
|
+
missingRequiredTable: "error",
|
|
75
|
+
missingOptionalTable: "ignore",
|
|
76
|
+
orderedTableViolationsRequired: "error",
|
|
77
|
+
orderedTableViolationsOptional: "warn",
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
...common,
|
|
83
|
+
level: 3,
|
|
84
|
+
container: "markdown_fence",
|
|
85
|
+
requireSingleContainer: true,
|
|
86
|
+
requireOfs: true,
|
|
87
|
+
requireAllSections: true,
|
|
88
|
+
requireTypedJson: true,
|
|
89
|
+
policy: {
|
|
90
|
+
outsideContainer: "error",
|
|
91
|
+
missingContainer: "error",
|
|
92
|
+
multipleContainers: "error",
|
|
93
|
+
missingRequiredSection: "error",
|
|
94
|
+
missingOptionalSection: "ignore",
|
|
95
|
+
wrongSectionKindRequired: "error",
|
|
96
|
+
wrongSectionKindOptional: "warn",
|
|
97
|
+
emptySectionWithoutNoneRequired: "error",
|
|
98
|
+
emptySectionWithoutNoneOptional: "warn",
|
|
99
|
+
missingRequiredTable: "error",
|
|
100
|
+
missingOptionalTable: "ignore",
|
|
101
|
+
orderedTableViolationsRequired: "error",
|
|
102
|
+
orderedTableViolationsOptional: "warn",
|
|
103
|
+
jsonPayloadMissing: "error"
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { enforceFlexMd, parseOutputFormatSpec, detectResponseKind } from "./index.js";
|
|
2
|
+
function assert(condition, message) {
|
|
3
|
+
if (!condition) {
|
|
4
|
+
throw new Error(`Assertion failed: ${message}`);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
console.log("🧪 Running Pipeline & JSON Detection Tests\n");
|
|
8
|
+
const spec = parseOutputFormatSpec(`
|
|
9
|
+
## Output format
|
|
10
|
+
- Answer — prose (required)
|
|
11
|
+
- Reasoning — ordered list (optional)
|
|
12
|
+
`);
|
|
13
|
+
// 1. Detection Kind
|
|
14
|
+
console.log("--- Detection Kind ---");
|
|
15
|
+
const mdResp = "## Answer\nThis is an answer.";
|
|
16
|
+
const kind1 = detectResponseKind(mdResp, spec);
|
|
17
|
+
console.log("FlexMD Kind:", kind1.kind);
|
|
18
|
+
assert(kind1.kind === "flexmd", "Should detect flexmd");
|
|
19
|
+
const issuesResp = "## Status\n- status: error\n- code: fail\n- message: fail\n\n## Issues\n- issue: x";
|
|
20
|
+
const kind2 = detectResponseKind(issuesResp, spec);
|
|
21
|
+
console.log("Issues Kind:", kind2.kind);
|
|
22
|
+
assert(kind2.kind === "issues", "Should detect issues");
|
|
23
|
+
const plainResp = "Just some text.";
|
|
24
|
+
const kind3 = detectResponseKind(plainResp, spec);
|
|
25
|
+
console.log("Markdown Kind:", kind3.kind);
|
|
26
|
+
assert(kind3.kind === "markdown", "Should detect markdown");
|
|
27
|
+
// 2. JSON fixing
|
|
28
|
+
console.log("\n--- JSON Fixing ---");
|
|
29
|
+
const jsonResp = "```json\n{\n \"Answer\": \"Parsed from JSON\",\n \"Reasoning\": [\"Step 1\", \"Step 2\"]\n}\n```";
|
|
30
|
+
const resultJson = enforceFlexMd(jsonResp, spec, { level: 1 });
|
|
31
|
+
console.log("JSON Fixed OK:", resultJson.ok);
|
|
32
|
+
console.log("JSON Fixed Content:\n", resultJson.extracted?.sectionsByName["Answer"]?.md);
|
|
33
|
+
assert(resultJson.ok === true, "Enforce should succeed with fixed JSON");
|
|
34
|
+
assert(!!resultJson.extracted?.sectionsByName["Answer"]?.md.includes("Parsed from JSON"), "Should extract from fixed JSON");
|
|
35
|
+
assert(resultJson.extracted?.sectionsByName["Reasoning"]?.list?.items.length === 2, "Should extract list from fixed JSON");
|
|
36
|
+
// 3. Raw JSON object
|
|
37
|
+
console.log("\n--- Raw JSON Object ---");
|
|
38
|
+
const rawJson = "{\"Answer\": \"Raw JSON\"}";
|
|
39
|
+
const resultRaw = enforceFlexMd(rawJson, spec, { level: 1 });
|
|
40
|
+
console.log("Raw JSON Fixed OK:", resultRaw.ok);
|
|
41
|
+
assert(resultRaw.ok === true, "Should fix raw JSON");
|
|
42
|
+
// 4. Missing sections fixing
|
|
43
|
+
console.log("\n--- Missing Sections ---");
|
|
44
|
+
// The fixer (repairToMarkdownLevel) SHOULD add missing required sections.
|
|
45
|
+
const failingResp = "## SomeOtherSection\nContent";
|
|
46
|
+
const resultFixed = enforceFlexMd(failingResp, spec, { level: 1 });
|
|
47
|
+
console.log("Auto-Repaired OK:", resultFixed.ok);
|
|
48
|
+
console.log("Repaired Content Contains 'Answer':", resultFixed.outputText.includes("## Answer"));
|
|
49
|
+
console.log("Repaired Content Contains 'None':", resultFixed.outputText.includes("None"));
|
|
50
|
+
assert(resultFixed.ok === true, "Should be ok if required sections are auto-repaired");
|
|
51
|
+
assert(resultFixed.outputText.includes("## Answer"), "Should have added 'Answer' heading");
|
|
52
|
+
assert(resultFixed.outputText.includes("None"), "Should have filled with 'None'");
|
|
53
|
+
console.log("\n✅ Pipeline Tests Passed");
|
package/dist/test-runner.js
CHANGED
|
@@ -2,11 +2,13 @@ import {
|
|
|
2
2
|
// Layer A
|
|
3
3
|
parseFlexMd, stringifyFlexMd, validateFlexMd,
|
|
4
4
|
// Layer B
|
|
5
|
-
parseOutputFormatSpec, stringifyOutputFormatSpec, enrichInstructions, validateOutput, extractOutput,
|
|
5
|
+
parseOutputFormatSpec, stringifyOutputFormatSpec, enrichInstructions, validateMarkdownAgainstOfs as validateOutput, extractFromMarkdown as extractOutput,
|
|
6
6
|
// Outline
|
|
7
7
|
buildOutline, renderOutline,
|
|
8
8
|
// Parsers
|
|
9
|
-
parseList, parsePipeTable, extractAllTables,
|
|
9
|
+
parseNestedList as parseList, parsePipeTable, extractPipeTables as extractAllTables, enforceOrderedTable,
|
|
10
|
+
// Strictness
|
|
11
|
+
strictnessDefaults,
|
|
10
12
|
// Layer C
|
|
11
13
|
detectObjects, parseAny } from "./index.js";
|
|
12
14
|
let passed = 0;
|
|
@@ -90,6 +92,7 @@ Include these sections somewhere (order does not matter):
|
|
|
90
92
|
- Assumptions — list
|
|
91
93
|
`;
|
|
92
94
|
const spec = parseOutputFormatSpec(md);
|
|
95
|
+
assert(spec !== null, "Spec should not be null");
|
|
93
96
|
assert(spec.sections.length === 3, "Should have 3 sections");
|
|
94
97
|
assert(spec.sections[0].name === "Short answer", "First section name");
|
|
95
98
|
assert(spec.sections[0].kind === "prose", "First section kind");
|
|
@@ -124,7 +127,7 @@ test("Enrich instructions", () => {
|
|
|
124
127
|
tables: [],
|
|
125
128
|
emptySectionValue: "None"
|
|
126
129
|
};
|
|
127
|
-
const instructions = enrichInstructions(spec);
|
|
130
|
+
const instructions = enrichInstructions(spec, strictnessDefaults(1));
|
|
128
131
|
assert(instructions.includes("numbered items"), "Should mention ordered lists");
|
|
129
132
|
assert(instructions.includes("bullets"), "Should mention lists");
|
|
130
133
|
assert(instructions.includes("None"), "Should mention empty value");
|
|
@@ -141,7 +144,7 @@ test("Validate output against OFS", () => {
|
|
|
141
144
|
tables: []
|
|
142
145
|
};
|
|
143
146
|
const md = `## Answer\nThe answer is 42.`;
|
|
144
|
-
const result = validateOutput(md, spec);
|
|
147
|
+
const result = validateOutput(md, spec, strictnessDefaults(1));
|
|
145
148
|
assert(result.ok === true, "Should validate successfully");
|
|
146
149
|
});
|
|
147
150
|
test("Extract output from OFS", () => {
|
|
@@ -163,7 +166,7 @@ The answer is 42.
|
|
|
163
166
|
1. First step
|
|
164
167
|
2. Second step
|
|
165
168
|
`;
|
|
166
|
-
const extracted = extractOutput(md, spec);
|
|
169
|
+
const extracted = extractOutput(md, spec, strictnessDefaults(1));
|
|
167
170
|
assert(extracted.sectionsByName["Answer"].md.includes("42"), "Should extract answer");
|
|
168
171
|
assert(extracted.sectionsByName["Steps"].list?.ordered === true, "Should parse ordered list");
|
|
169
172
|
assert(extracted.sectionsByName["Steps"].list?.items.length === 2, "Should have 2 items");
|
|
@@ -249,7 +252,6 @@ test("Parse pipe table", () => {
|
|
|
249
252
|
assert(table !== null, "Should parse table");
|
|
250
253
|
assert(table.columns.length === 2, "Should have 2 columns");
|
|
251
254
|
assert(table.rows.length === 2, "Should have 2 rows");
|
|
252
|
-
assert(table.kind === "table", "Should be regular table");
|
|
253
255
|
});
|
|
254
256
|
test("Parse ordered table", () => {
|
|
255
257
|
const md = `| # | Task | Status |
|
|
@@ -259,7 +261,8 @@ test("Parse ordered table", () => {
|
|
|
259
261
|
`;
|
|
260
262
|
const table = parsePipeTable(md);
|
|
261
263
|
assert(table !== null, "Should parse table");
|
|
262
|
-
|
|
264
|
+
const issues = enforceOrderedTable(table);
|
|
265
|
+
assert(issues.length === 0, "Should have no ordered table issues");
|
|
263
266
|
assert(table.columns[0] === "#", "First column should be #");
|
|
264
267
|
});
|
|
265
268
|
test("Extract all tables", () => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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,11 +10,8 @@ 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 {
|
|
@@ -27,38 +20,13 @@ export interface FlexDocument {
|
|
|
27
20
|
frames: FlexFrame[];
|
|
28
21
|
}
|
|
29
22
|
export interface ParseOptions {
|
|
30
|
-
/**
|
|
31
|
-
* Which metadata keys should be split by commas into string[]
|
|
32
|
-
* Default: ["tags", "refs"]
|
|
33
|
-
*/
|
|
34
23
|
arrayMetaKeys?: string[];
|
|
35
|
-
/**
|
|
36
|
-
* How to parse metadata values:
|
|
37
|
-
* - "strings": all values remain strings (default)
|
|
38
|
-
* - "infer": safely infer boolean, null, number
|
|
39
|
-
* - "schema": use metaSchema to determine types
|
|
40
|
-
*/
|
|
41
24
|
metaTypeMode?: "strings" | "infer" | "schema";
|
|
42
|
-
/**
|
|
43
|
-
* Schema for metadata value types (used when metaTypeMode="schema")
|
|
44
|
-
* Example: { priority: "number", enabled: "boolean" }
|
|
45
|
-
*/
|
|
46
25
|
metaSchema?: Record<string, "string" | "number" | "boolean" | "null">;
|
|
47
26
|
}
|
|
48
27
|
export interface StringifyOptions {
|
|
49
|
-
/**
|
|
50
|
-
* If true, omit empty sections when converting JSON -> FlexMD.
|
|
51
|
-
* Default: true
|
|
52
|
-
*/
|
|
53
28
|
skipEmpty?: boolean;
|
|
54
|
-
/**
|
|
55
|
-
* Which meta keys should be rendered as comma-separated lists if arrays.
|
|
56
|
-
* Default: ["tags", "refs"]
|
|
57
|
-
*/
|
|
58
29
|
arrayMetaKeys?: string[];
|
|
59
|
-
/**
|
|
60
|
-
* Preferred fence marker. Default: "```"
|
|
61
|
-
*/
|
|
62
30
|
fence?: "```" | "~~~";
|
|
63
31
|
}
|
|
64
32
|
export interface FlexValidationError {
|
|
@@ -70,26 +38,79 @@ export interface FlexValidationResult {
|
|
|
70
38
|
valid: boolean;
|
|
71
39
|
errors: FlexValidationError[];
|
|
72
40
|
}
|
|
73
|
-
export type
|
|
74
|
-
export
|
|
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 {
|
|
75
85
|
name: string;
|
|
76
|
-
kind
|
|
86
|
+
kind?: SectionKind;
|
|
87
|
+
required?: boolean;
|
|
88
|
+
columns?: string[];
|
|
89
|
+
description?: string;
|
|
90
|
+
instruction?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* @deprecated Use OutputSectionSpec
|
|
94
|
+
*/
|
|
95
|
+
export interface OfsSection extends OutputSectionSpec {
|
|
77
96
|
hint?: string;
|
|
97
|
+
emptyAllowed?: boolean;
|
|
78
98
|
}
|
|
79
|
-
export type TableKind = "table" | "ordered_table";
|
|
80
99
|
export interface OfsTable {
|
|
81
100
|
columns: string[];
|
|
82
|
-
kind:
|
|
101
|
+
kind: "table" | "ordered_table";
|
|
83
102
|
by?: string;
|
|
103
|
+
required?: boolean;
|
|
84
104
|
}
|
|
85
105
|
export interface OutputFormatSpec {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
sectionOrderMatters: false;
|
|
89
|
-
sections: OfsSection[];
|
|
90
|
-
tablesOptional: boolean;
|
|
91
|
-
tables: OfsTable[];
|
|
106
|
+
description?: string;
|
|
107
|
+
sections: OutputSectionSpec[];
|
|
92
108
|
emptySectionValue?: string;
|
|
109
|
+
descriptorType?: "output_format_spec";
|
|
110
|
+
format?: "markdown";
|
|
111
|
+
sectionOrderMatters?: boolean;
|
|
112
|
+
tablesOptional?: boolean;
|
|
113
|
+
tables?: OfsTable[];
|
|
93
114
|
}
|
|
94
115
|
export interface MdNode {
|
|
95
116
|
title: string;
|
|
@@ -129,6 +150,16 @@ export interface ExtractedResult {
|
|
|
129
150
|
}>;
|
|
130
151
|
tables: ParsedTable[];
|
|
131
152
|
}
|
|
153
|
+
export interface ProcessResponseResult {
|
|
154
|
+
ok: boolean;
|
|
155
|
+
strictness: any;
|
|
156
|
+
usedContainer: boolean;
|
|
157
|
+
innerMarkdown: string;
|
|
158
|
+
validation: ValidationResult;
|
|
159
|
+
extracted?: ExtractedResult;
|
|
160
|
+
issues: ValidationIssue[];
|
|
161
|
+
issuesEnvelope?: IssuesEnvelope;
|
|
162
|
+
}
|
|
132
163
|
export type DetectedKind = "flexmd_fence" | "flexdoc_json_fence" | "raw_flexmd" | "markdown_snippet" | "none";
|
|
133
164
|
export interface DetectedObject {
|
|
134
165
|
kind: DetectedKind;
|
|
@@ -138,3 +169,11 @@ export interface DetectedObject {
|
|
|
138
169
|
raw: string;
|
|
139
170
|
inner?: string;
|
|
140
171
|
}
|
|
172
|
+
export interface IssuesEnvelope {
|
|
173
|
+
isIssuesEnvelope: boolean;
|
|
174
|
+
sections: Record<string, {
|
|
175
|
+
heading: string;
|
|
176
|
+
body: string;
|
|
177
|
+
bullets: string[];
|
|
178
|
+
}>;
|
|
179
|
+
}
|
|
@@ -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 {};
|