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.
- 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
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ npm install flex-md
|
|
|
36
36
|
|
|
37
37
|
## Runtime Compatibility
|
|
38
38
|
|
|
39
|
-
- Node LTS baseline: `>=18
|
|
39
|
+
- Node LTS baseline: `>=18` (see `engines` in `package.json`).
|
|
40
40
|
- Dual-surface consumption guidance for library authors is documented in [docs/consumption.md](./docs/consumption.md).
|
|
41
41
|
|
|
42
42
|
## Quick Start
|
|
@@ -1,61 +1,59 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const ok = await (0, index_js_1.hasFlexMdContract)("Hello", "L0");
|
|
19
|
-
(0, vitest_1.expect)(ok).toBe(false);
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { checkCompliance, hasFlexMdContract, enrichInstructionsWithFlexMd, validateFormat } from "../index.js";
|
|
3
|
+
describe("Diagnostics", () => {
|
|
4
|
+
describe("checkCompliance", () => {
|
|
5
|
+
it("should return meetsCompliance: true for L0 if Markdown is mentioned", async () => {
|
|
6
|
+
const res = await checkCompliance("Reply in Markdown.", "L0");
|
|
7
|
+
expect(res.meetsCompliance).toBe(true);
|
|
8
|
+
expect(res.issues).toHaveLength(0);
|
|
9
|
+
});
|
|
10
|
+
it("should return issues for L2 if container is missing", async () => {
|
|
11
|
+
const res = await checkCompliance("Reply in Markdown. Include headings.", "L2");
|
|
12
|
+
expect(res.meetsCompliance).toBe(false);
|
|
13
|
+
expect(res.issues.some(i => i.type === "missing-container")).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
it("should return false for hasFlexMdContract if requirements not met", async () => {
|
|
16
|
+
const ok = await hasFlexMdContract("Hello", "L0");
|
|
17
|
+
expect(ok).toBe(false);
|
|
20
18
|
});
|
|
21
19
|
});
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const res = await
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const res = await
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
describe("enrichInstructionsWithFlexMd", () => {
|
|
21
|
+
it("should append guidance to existing instructions", async () => {
|
|
22
|
+
const res = await enrichInstructionsWithFlexMd("Do a good job.", "L0");
|
|
23
|
+
expect(res.enriched).toContain("Do a good job.");
|
|
24
|
+
expect(res.enriched).toContain("Reply in Markdown.");
|
|
25
|
+
expect(res.changes).toHaveLength(1);
|
|
26
|
+
expect(res.metadata?.guidanceAdded).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it("should track container addition for L2", async () => {
|
|
29
|
+
const res = await enrichInstructionsWithFlexMd("Do a good job.", "L2");
|
|
30
|
+
expect(res.changes.some(c => c.type === "added-container")).toBe(true);
|
|
31
|
+
expect(res.metadata?.containerAdded).toBe(true);
|
|
34
32
|
});
|
|
35
33
|
});
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
describe("validateFormat", () => {
|
|
35
|
+
it("should validate a correct flex-md OFS", async () => {
|
|
38
36
|
const ofs = `## Output format\n- Summary — prose\n- Actions — list`;
|
|
39
|
-
const res = await
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
const res = await validateFormat(ofs);
|
|
38
|
+
expect(res.valid).toBe(true);
|
|
39
|
+
expect(res.metadata?.sectionCount).toBe(2);
|
|
42
40
|
});
|
|
43
|
-
|
|
41
|
+
it("should return errors for missing heading", async () => {
|
|
44
42
|
const ofs = `- Summary — prose`;
|
|
45
|
-
const res = await
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
const res = await validateFormat(ofs);
|
|
44
|
+
expect(res.valid).toBe(false);
|
|
45
|
+
expect(res.issues.some(i => i.type === "missing-heading")).toBe(true);
|
|
48
46
|
});
|
|
49
|
-
|
|
47
|
+
it("should validate JSON schema", async () => {
|
|
50
48
|
const schema = `{"type": "object"}`;
|
|
51
|
-
const res = await
|
|
52
|
-
|
|
49
|
+
const res = await validateFormat(schema, "json-schema");
|
|
50
|
+
expect(res.valid).toBe(true);
|
|
53
51
|
});
|
|
54
|
-
|
|
52
|
+
it("should fail on invalid JSON schema", async () => {
|
|
55
53
|
const schema = `{"type": "object"`;
|
|
56
|
-
const res = await
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
const res = await validateFormat(schema, "json-schema");
|
|
55
|
+
expect(res.valid).toBe(false);
|
|
56
|
+
expect(res.issues[0].type).toBe("syntax-error");
|
|
59
57
|
});
|
|
60
58
|
});
|
|
61
59
|
});
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const enricher_js_1 = require("../ofs/enricher.js");
|
|
6
|
-
(0, vitest_1.describe)("Instructions Output Format Block Generation", () => {
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { stringifyOutputFormatSpec } from "../ofs/stringify.js";
|
|
3
|
+
import { buildMarkdownGuidance } from "../ofs/enricher.js";
|
|
4
|
+
describe("Instructions Output Format Block Generation", () => {
|
|
7
5
|
const spec = {
|
|
8
6
|
description: "Standard report format for technical analysis.",
|
|
9
7
|
sections: [
|
|
@@ -23,31 +21,31 @@ const enricher_js_1 = require("../ofs/enricher.js");
|
|
|
23
21
|
],
|
|
24
22
|
emptySectionValue: "N/A"
|
|
25
23
|
};
|
|
26
|
-
|
|
27
|
-
const md =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
it("should stringify an OFS object correctly", () => {
|
|
25
|
+
const md = stringifyOutputFormatSpec(spec);
|
|
26
|
+
expect(md).toContain("## Output format (Markdown)");
|
|
27
|
+
expect(md).toContain("Standard report format for technical analysis.");
|
|
28
|
+
expect(md).toContain("- Summary — text (required)");
|
|
29
|
+
expect(md).toContain("Description: A brief summary of the topic.");
|
|
30
|
+
expect(md).toContain("Instruction: Keep it under 3 sentences.");
|
|
31
|
+
expect(md).toContain("- Key Points — list (optional)");
|
|
32
|
+
expect(md).toContain("Description: Important takeaways.");
|
|
33
|
+
expect(md).toContain("If a section is empty, write `N/A`.");
|
|
36
34
|
});
|
|
37
|
-
|
|
38
|
-
const guidance =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
it("should build markdown guidance (L1) correctly", () => {
|
|
36
|
+
const guidance = buildMarkdownGuidance(spec, { level: 1 });
|
|
37
|
+
expect(guidance).toContain("Include these section headings somewhere");
|
|
38
|
+
expect(guidance).toContain("- Summary");
|
|
39
|
+
expect(guidance).toContain("Description: A brief summary of the topic.");
|
|
40
|
+
expect(guidance).toContain("Instruction: Keep it under 3 sentences.");
|
|
41
|
+
expect(guidance).toContain("- Key Points");
|
|
42
|
+
expect(guidance).toContain("Description: Important takeaways.");
|
|
45
43
|
});
|
|
46
|
-
|
|
47
|
-
const guidance =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
it("should build markdown guidance (L3) correctly", () => {
|
|
45
|
+
const guidance = buildMarkdownGuidance(spec, { level: 3 });
|
|
46
|
+
expect(guidance).toContain("Return your entire answer inside a single ```markdown fenced block");
|
|
47
|
+
expect(guidance).toContain("- Summary");
|
|
48
|
+
expect(guidance).toContain("- Key Points (list)");
|
|
49
|
+
expect(guidance).toContain("Do not return JSON");
|
|
52
50
|
});
|
|
53
51
|
});
|
|
@@ -1,30 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
(
|
|
6
|
-
(0, vitest_1.describe)("===key support", () => {
|
|
7
|
-
(0, vitest_1.it)("should parse ===key as a top-level section", () => {
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { buildOutline, checkConnection } from "../index.js";
|
|
3
|
+
describe("Structural Diagnostics", () => {
|
|
4
|
+
describe("===key support", () => {
|
|
5
|
+
it("should parse ===key as a top-level section", () => {
|
|
8
6
|
const md = `===skills\nThis is the skills section.\n\n## Subskill\nDetails.`;
|
|
9
|
-
const outline =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
const outline = buildOutline(md);
|
|
8
|
+
expect(outline.nodes).toHaveLength(1);
|
|
9
|
+
expect(outline.nodes[0].title).toBe("skills");
|
|
10
|
+
expect(outline.nodes[0].children).toHaveLength(1);
|
|
11
|
+
expect(outline.nodes[0].children[0].title).toBe("Subskill");
|
|
14
12
|
});
|
|
15
13
|
});
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
describe("checkConnection", () => {
|
|
15
|
+
it("should find a section by name", () => {
|
|
18
16
|
const md = `# Overview\n## Details\n===skills\nContent.`;
|
|
19
|
-
const res =
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
const res = checkConnection(md, "skills");
|
|
18
|
+
expect(res.connected).toBe(true);
|
|
19
|
+
expect(res.path).toContain("skills");
|
|
22
20
|
});
|
|
23
|
-
|
|
21
|
+
it("should fail gracefully for missing sections", () => {
|
|
24
22
|
const md = `# Overview`;
|
|
25
|
-
const res =
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
const res = checkConnection(md, "missing");
|
|
24
|
+
expect(res.connected).toBe(false);
|
|
25
|
+
expect(res.suggestion).toContain("Overview");
|
|
28
26
|
});
|
|
29
27
|
});
|
|
30
28
|
});
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const validate_js_1 = require("../validate/validate.js");
|
|
5
|
-
const issuesEnvelope_js_1 = require("../ofs/issuesEnvelope.js");
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { validateMarkdownAgainstOfs } from "../validate/validate.js";
|
|
3
|
+
import { parseIssuesEnvelope } from "../ofs/issuesEnvelope.js";
|
|
6
4
|
const SPEC = {
|
|
7
5
|
emptySectionValue: "None",
|
|
8
6
|
sections: [
|
|
@@ -32,8 +30,8 @@ function mdL1Good() {
|
|
|
32
30
|
"- U",
|
|
33
31
|
].join("\n");
|
|
34
32
|
}
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
describe("parseIssuesEnvelope()", () => {
|
|
34
|
+
it("detects envelope and extracts bullets", () => {
|
|
37
35
|
const env = [
|
|
38
36
|
"## Status",
|
|
39
37
|
"Non-compliant output (cannot be repaired to the required format).",
|
|
@@ -50,17 +48,17 @@ function mdL1Good() {
|
|
|
50
48
|
"## How to fix",
|
|
51
49
|
"- Do X",
|
|
52
50
|
].join("\n");
|
|
53
|
-
const parsed =
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
const parsed = parseIssuesEnvelope(env);
|
|
52
|
+
expect(parsed.isIssuesEnvelope).toBe(true);
|
|
53
|
+
expect(parsed.sections["issues"].bullets[0]).toContain("Missing required section");
|
|
56
54
|
});
|
|
57
55
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const r =
|
|
61
|
-
|
|
56
|
+
describe("validateMarkdownAgainstOfs()", () => {
|
|
57
|
+
it("L0 accepts anything", () => {
|
|
58
|
+
const r = validateMarkdownAgainstOfs("whatever", SPEC, 0);
|
|
59
|
+
expect(r.ok).toBe(true);
|
|
62
60
|
});
|
|
63
|
-
|
|
61
|
+
it("L1 fails when required section missing", () => {
|
|
64
62
|
const md = [
|
|
65
63
|
"## Short answer",
|
|
66
64
|
"Yes.",
|
|
@@ -68,21 +66,21 @@ function mdL1Good() {
|
|
|
68
66
|
"## Reasoning",
|
|
69
67
|
"1. A",
|
|
70
68
|
].join("\n");
|
|
71
|
-
const r =
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
const r = validateMarkdownAgainstOfs(md, SPEC, 1);
|
|
70
|
+
expect(r.ok).toBe(false);
|
|
71
|
+
expect(r.issues.some(i => i.code === "MISSING_SECTION")).toBe(true);
|
|
74
72
|
});
|
|
75
|
-
|
|
76
|
-
const r =
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
it("L2 requires a single fenced markdown block", () => {
|
|
74
|
+
const r = validateMarkdownAgainstOfs(mdL1Good(), SPEC, 2);
|
|
75
|
+
expect(r.ok).toBe(false);
|
|
76
|
+
expect(r.issues.some(i => i.code === "CONTAINER_MISSING")).toBe(true);
|
|
79
77
|
});
|
|
80
|
-
|
|
78
|
+
it("L2 passes with one fence containing valid L1", () => {
|
|
81
79
|
const md = ["```markdown", mdL1Good(), "```"].join("\n");
|
|
82
|
-
const r =
|
|
83
|
-
|
|
80
|
+
const r = validateMarkdownAgainstOfs(md, SPEC, 2);
|
|
81
|
+
expect(r.ok).toBe(true);
|
|
84
82
|
});
|
|
85
|
-
|
|
83
|
+
it("L3 enforces section kinds (Reasoning must be ordered list)", () => {
|
|
86
84
|
const bad = [
|
|
87
85
|
"```markdown",
|
|
88
86
|
[
|
|
@@ -103,8 +101,8 @@ function mdL1Good() {
|
|
|
103
101
|
].join("\n"),
|
|
104
102
|
"```",
|
|
105
103
|
].join("\n");
|
|
106
|
-
const r =
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
const r = validateMarkdownAgainstOfs(bad, SPEC, 3);
|
|
105
|
+
expect(r.ok).toBe(false);
|
|
106
|
+
expect(r.issues.some(i => i.code === "WRONG_SECTION_KIND")).toBe(true);
|
|
109
107
|
});
|
|
110
108
|
});
|
package/dist/cli/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const path_1 = require("path");
|
|
6
|
-
const index_js_1 = require("../index.js");
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
import { checkCompliance, checkConnection } from "../index.js";
|
|
7
5
|
const [, , command, ...args] = process.argv;
|
|
8
6
|
async function main() {
|
|
9
7
|
if (!command || command === "--help" || command === "-h") {
|
|
@@ -57,8 +55,8 @@ async function handleCheck(args) {
|
|
|
57
55
|
if (!file)
|
|
58
56
|
throw new Error("Missing file argument.");
|
|
59
57
|
const level = (args.includes("--level") ? args[args.indexOf("--level") + 1] : "L2");
|
|
60
|
-
const content =
|
|
61
|
-
const result = await
|
|
58
|
+
const content = readFileSync(resolve(file), "utf-8");
|
|
59
|
+
const result = await checkCompliance(content, level);
|
|
62
60
|
if (result.meetsCompliance) {
|
|
63
61
|
console.log("✅ Content meets compliance.");
|
|
64
62
|
}
|
|
@@ -74,10 +72,10 @@ async function handleDiagnose(args) {
|
|
|
74
72
|
const key = args[1];
|
|
75
73
|
if (!file || !key)
|
|
76
74
|
throw new Error("Usage: flex-md diagnose <file> <key>");
|
|
77
|
-
if (!
|
|
75
|
+
if (!existsSync(file))
|
|
78
76
|
throw new Error(`File not found: ${file}`);
|
|
79
|
-
const content =
|
|
80
|
-
const result =
|
|
77
|
+
const content = readFileSync(resolve(file), "utf-8");
|
|
78
|
+
const result = checkConnection(content, key);
|
|
81
79
|
if (result.connected) {
|
|
82
80
|
console.log(`✅ Key "${key}" found at path: ${result.path?.join(" -> ")}`);
|
|
83
81
|
}
|
|
@@ -91,9 +89,9 @@ async function handleCreate(args) {
|
|
|
91
89
|
const key = args[1];
|
|
92
90
|
if (!file || !key)
|
|
93
91
|
throw new Error("Usage: flex-md create <file> <key>");
|
|
94
|
-
const content =
|
|
92
|
+
const content = existsSync(file) ? readFileSync(file, "utf-8") : "";
|
|
95
93
|
const newContent = content + `\n\n## ${key}\n\n[Content for ${key}]\n`;
|
|
96
|
-
|
|
94
|
+
writeFileSync(file, newContent);
|
|
97
95
|
console.log(`✅ Section "${key}" created in ${file}`);
|
|
98
96
|
}
|
|
99
97
|
async function handleSync(args) {
|
|
@@ -101,10 +99,10 @@ async function handleSync(args) {
|
|
|
101
99
|
const target = args[1];
|
|
102
100
|
if (!source || !target)
|
|
103
101
|
throw new Error("Usage: flex-md sync <source> <target>");
|
|
104
|
-
if (!
|
|
102
|
+
if (!existsSync(source))
|
|
105
103
|
throw new Error(`Source file not found: ${source}`);
|
|
106
|
-
const content =
|
|
107
|
-
|
|
104
|
+
const content = readFileSync(source, "utf-8");
|
|
105
|
+
writeFileSync(target, content);
|
|
108
106
|
console.log(`✅ Content synced from ${source} to ${target}`);
|
|
109
107
|
}
|
|
110
108
|
main();
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.detectJsonIntent = detectJsonIntent;
|
|
4
1
|
const HARD_PATTERNS = [
|
|
5
2
|
/\breturn\s+only\s+json\b/i,
|
|
6
3
|
/\boutput\s+only\s+json\b/i,
|
|
@@ -28,7 +25,7 @@ const TOOLING_PATTERNS = [
|
|
|
28
25
|
/\btool\s+call\b/i,
|
|
29
26
|
/\barguments\b.*\bjson\b/i,
|
|
30
27
|
];
|
|
31
|
-
function detectJsonIntent(text) {
|
|
28
|
+
export function detectJsonIntent(text) {
|
|
32
29
|
const signals = [];
|
|
33
30
|
const add = (rx, strength, idx, len) => {
|
|
34
31
|
signals.push({
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Helpers for "framed vs frameless" Markdown handling before Flex-MD.
|
|
4
3
|
*
|
|
@@ -9,29 +8,24 @@
|
|
|
9
8
|
* Notes:
|
|
10
9
|
* - This is heuristic by design; tune thresholds as you learn your data.
|
|
11
10
|
*/
|
|
12
|
-
|
|
13
|
-
exports.detectMarkdown = detectMarkdown;
|
|
14
|
-
exports.stripSingleFence = stripSingleFence;
|
|
15
|
-
exports.forceWrapAsMarkdown = forceWrapAsMarkdown;
|
|
16
|
-
exports.normalizeForFlexMd = normalizeForFlexMd;
|
|
17
|
-
const logger_js_1 = require("../../logger.js");
|
|
11
|
+
import { logger } from "../../logger.js";
|
|
18
12
|
const SINGLE_FENCE_BLOCK_RE = /^```([a-zA-Z0-9_-]+)?([^\n]*)\n([\s\S]*?)\n```$/;
|
|
19
13
|
const FENCE_OPEN_RE = /(^|\n)```/g;
|
|
20
|
-
function detectMarkdown(text) {
|
|
21
|
-
|
|
14
|
+
export function detectMarkdown(text) {
|
|
15
|
+
logger.debug("Starting markdown detection", {
|
|
22
16
|
inputType: typeof text,
|
|
23
17
|
inputLength: typeof text === "string" ? text.length : JSON.stringify(text ?? "").length
|
|
24
18
|
});
|
|
25
19
|
const reasons = [];
|
|
26
20
|
const raw = typeof text === "string" ? text : JSON.stringify(text ?? "");
|
|
27
21
|
const s = raw.replace(/\r\n/g, "\n");
|
|
28
|
-
|
|
22
|
+
logger.verbose("Input normalized for processing", {
|
|
29
23
|
originalType: typeof text,
|
|
30
24
|
normalizedLength: s.length,
|
|
31
25
|
hasNewlines: s.includes('\n')
|
|
32
26
|
});
|
|
33
27
|
const codeFences = [...s.matchAll(FENCE_OPEN_RE)].length;
|
|
34
|
-
|
|
28
|
+
logger.debug("Code fence analysis", {
|
|
35
29
|
totalFences: codeFences,
|
|
36
30
|
fenceRegex: FENCE_OPEN_RE.source
|
|
37
31
|
});
|
|
@@ -39,7 +33,7 @@ function detectMarkdown(text) {
|
|
|
39
33
|
const m = s.match(SINGLE_FENCE_BLOCK_RE);
|
|
40
34
|
const isFramed = !!m && codeFences === 2;
|
|
41
35
|
const frameLanguage = isFramed ? (m?.[1] ?? null) : null;
|
|
42
|
-
|
|
36
|
+
logger.debug("Framed markdown detection", {
|
|
43
37
|
regexMatch: !!m,
|
|
44
38
|
expectedFences: 2,
|
|
45
39
|
actualFences: codeFences,
|
|
@@ -53,24 +47,24 @@ function detectMarkdown(text) {
|
|
|
53
47
|
});
|
|
54
48
|
if (isFramed) {
|
|
55
49
|
reasons.push("Single fenced code block detected (framed payload).");
|
|
56
|
-
|
|
50
|
+
logger.info("Detected framed markdown", { language: frameLanguage });
|
|
57
51
|
}
|
|
58
52
|
else if (codeFences > 2) {
|
|
59
53
|
reasons.push("Multiple fenced code blocks detected.");
|
|
60
|
-
|
|
54
|
+
logger.debug("Multiple fences detected, not treating as single framed block", { fenceCount: codeFences });
|
|
61
55
|
}
|
|
62
56
|
else if (codeFences === 1) {
|
|
63
57
|
reasons.push("Single fence marker found but not properly framed.");
|
|
64
|
-
|
|
58
|
+
logger.debug("Single fence found but regex didn't match", {
|
|
65
59
|
regex: SINGLE_FENCE_BLOCK_RE.source,
|
|
66
60
|
hasMatch: !!m
|
|
67
61
|
});
|
|
68
62
|
}
|
|
69
63
|
else {
|
|
70
|
-
|
|
64
|
+
logger.debug("No fence markers detected");
|
|
71
65
|
}
|
|
72
66
|
const lines = s.split("\n");
|
|
73
|
-
|
|
67
|
+
logger.verbose("Line-by-line analysis started", { totalLines: lines.length });
|
|
74
68
|
const atxHeadings = lines.filter((l) => /^#{1,6}\s+\S/.test(l.trim())).length;
|
|
75
69
|
let setextHeadings = 0;
|
|
76
70
|
for (let i = 0; i < lines.length - 1; i++) {
|
|
@@ -95,7 +89,7 @@ function detectMarkdown(text) {
|
|
|
95
89
|
(s.match(/__[^_\n]+__/g) ?? []).length +
|
|
96
90
|
(s.match(/(^|[^*])\*[^*\n]+\*([^*]|$)/g) ?? []).length +
|
|
97
91
|
(s.match(/(^|[^_])_[^_\n]+_([^_]|$)/g) ?? []).length;
|
|
98
|
-
|
|
92
|
+
logger.debug("Markdown structure analysis", {
|
|
99
93
|
atxHeadings,
|
|
100
94
|
setextHeadings,
|
|
101
95
|
totalHeadings: headings,
|
|
@@ -110,7 +104,7 @@ function detectMarkdown(text) {
|
|
|
110
104
|
const hasList = unorderedListLines + orderedListLines >= 2;
|
|
111
105
|
const hasTable = tableRows >= 2;
|
|
112
106
|
const hasOtherSignals = inlineCodeSpans + mdLinks + emphasisTokens >= 2;
|
|
113
|
-
|
|
107
|
+
logger.debug("Heuristic analysis", {
|
|
114
108
|
hasList,
|
|
115
109
|
hasTable,
|
|
116
110
|
hasOtherSignals,
|
|
@@ -121,17 +115,17 @@ function detectMarkdown(text) {
|
|
|
121
115
|
let isMarkdownLikely = false;
|
|
122
116
|
if (isFramed) {
|
|
123
117
|
isMarkdownLikely = true;
|
|
124
|
-
|
|
118
|
+
logger.debug("Markdown likelihood determined: framed content is always considered markdown");
|
|
125
119
|
}
|
|
126
120
|
else if (headings >= 2) {
|
|
127
121
|
isMarkdownLikely = true;
|
|
128
122
|
reasons.push(`Detected ${headings} markdown heading(s) (>=2).`);
|
|
129
|
-
|
|
123
|
+
logger.debug("Markdown likelihood: sufficient headings detected", { headingCount: headings });
|
|
130
124
|
}
|
|
131
125
|
else if (headings >= 1 && (hasList || hasTable)) {
|
|
132
126
|
isMarkdownLikely = true;
|
|
133
127
|
reasons.push(`Detected heading(s) plus ${hasList ? "list" : "table"} structure.`);
|
|
134
|
-
|
|
128
|
+
logger.debug("Markdown likelihood: heading plus structural element", {
|
|
135
129
|
hasHeading: headings >= 1,
|
|
136
130
|
hasList,
|
|
137
131
|
hasTable
|
|
@@ -140,7 +134,7 @@ function detectMarkdown(text) {
|
|
|
140
134
|
else if ((hasList && hasTable) || (hasTable && hasOtherSignals) || (hasList && hasOtherSignals)) {
|
|
141
135
|
isMarkdownLikely = true;
|
|
142
136
|
reasons.push("Detected multiple markdown structural signals (lists/tables/links/code/emphasis).");
|
|
143
|
-
|
|
137
|
+
logger.debug("Markdown likelihood: multiple structural signals", {
|
|
144
138
|
listAndTable: hasList && hasTable,
|
|
145
139
|
tableAndOther: hasTable && hasOtherSignals,
|
|
146
140
|
listAndOther: hasList && hasOtherSignals
|
|
@@ -148,7 +142,7 @@ function detectMarkdown(text) {
|
|
|
148
142
|
}
|
|
149
143
|
else {
|
|
150
144
|
reasons.push("Insufficient markdown structure signals; treating as plain text.");
|
|
151
|
-
|
|
145
|
+
logger.debug("Markdown likelihood: insufficient signals, treating as plain text", {
|
|
152
146
|
headings,
|
|
153
147
|
hasList,
|
|
154
148
|
hasTable,
|
|
@@ -158,7 +152,7 @@ function detectMarkdown(text) {
|
|
|
158
152
|
if (/^\s*#{1,6}\s+\S/.test(lines[0] ?? "")) {
|
|
159
153
|
reasons.push("Text starts with an ATX heading (#...).");
|
|
160
154
|
isMarkdownLikely = true;
|
|
161
|
-
|
|
155
|
+
logger.debug("Additional check: starts with heading, upgrading to markdown");
|
|
162
156
|
}
|
|
163
157
|
return {
|
|
164
158
|
isMarkdownLikely,
|
|
@@ -185,7 +179,7 @@ function detectMarkdown(text) {
|
|
|
185
179
|
*
|
|
186
180
|
* - Also handles ```md / ```markdown / ```json etc.
|
|
187
181
|
*/
|
|
188
|
-
function stripSingleFence(input) {
|
|
182
|
+
export function stripSingleFence(input) {
|
|
189
183
|
const s = input.replace(/\r\n/g, "\n").trim();
|
|
190
184
|
const fenceCount = [...s.matchAll(FENCE_OPEN_RE)].length;
|
|
191
185
|
if (fenceCount !== 2) {
|
|
@@ -205,7 +199,7 @@ function stripSingleFence(input) {
|
|
|
205
199
|
* Choose a heading that exists in your OFS to maximize alignment.
|
|
206
200
|
* Default: "Full Answer" (common sink section).
|
|
207
201
|
*/
|
|
208
|
-
function forceWrapAsMarkdown(plainText, opts) {
|
|
202
|
+
export function forceWrapAsMarkdown(plainText, opts) {
|
|
209
203
|
const heading = opts?.heading ?? "Full Answer";
|
|
210
204
|
const level = opts?.level ?? 3;
|
|
211
205
|
const hashes = "#".repeat(level);
|
|
@@ -221,15 +215,15 @@ function forceWrapAsMarkdown(plainText, opts) {
|
|
|
221
215
|
* - Else: if markdown-likely: keep as-is
|
|
222
216
|
* - Else: wrap as markdown under a chosen heading
|
|
223
217
|
*/
|
|
224
|
-
function normalizeForFlexMd(input, opts) {
|
|
225
|
-
|
|
218
|
+
export function normalizeForFlexMd(input, opts) {
|
|
219
|
+
logger.info("Starting Flex-MD normalization", {
|
|
226
220
|
inputType: typeof input,
|
|
227
221
|
inputLength: typeof input === "string" ? input.length : JSON.stringify(input ?? "").length,
|
|
228
222
|
options: opts
|
|
229
223
|
});
|
|
230
224
|
const raw = typeof input === "string" ? input : JSON.stringify(input ?? "");
|
|
231
225
|
const detection = detectMarkdown(raw);
|
|
232
|
-
|
|
226
|
+
logger.debug("Detection completed", {
|
|
233
227
|
isFramed: detection.isFramed,
|
|
234
228
|
isMarkdownLikely: detection.isMarkdownLikely,
|
|
235
229
|
frameLanguage: detection.frameLanguage,
|
|
@@ -237,14 +231,14 @@ function normalizeForFlexMd(input, opts) {
|
|
|
237
231
|
});
|
|
238
232
|
// 1) Strip if framed
|
|
239
233
|
const { stripped, wasFramed, language } = stripSingleFence(raw);
|
|
240
|
-
|
|
234
|
+
logger.debug("Strip fence check", {
|
|
241
235
|
wasFramed,
|
|
242
236
|
language,
|
|
243
237
|
strippedLength: stripped.length,
|
|
244
238
|
originalLength: raw.length
|
|
245
239
|
});
|
|
246
240
|
if (wasFramed) {
|
|
247
|
-
|
|
241
|
+
logger.info("Normalization: stripped framed markdown", {
|
|
248
242
|
language,
|
|
249
243
|
contentLength: stripped.length
|
|
250
244
|
});
|
|
@@ -258,7 +252,7 @@ function normalizeForFlexMd(input, opts) {
|
|
|
258
252
|
}
|
|
259
253
|
// 2) Keep if markdown-likely
|
|
260
254
|
if (detection.isMarkdownLikely) {
|
|
261
|
-
|
|
255
|
+
logger.info("Normalization: keeping as-is (markdown-likely)", {
|
|
262
256
|
reasons: detection.reasons
|
|
263
257
|
});
|
|
264
258
|
return {
|
|
@@ -272,7 +266,7 @@ function normalizeForFlexMd(input, opts) {
|
|
|
272
266
|
// 3) Wrap if not markdown-likely
|
|
273
267
|
const fallbackHeading = opts?.fallbackHeading ?? "Full Answer";
|
|
274
268
|
const fallbackLevel = opts?.fallbackHeadingLevel ?? 3;
|
|
275
|
-
|
|
269
|
+
logger.info("Normalization: wrapping as markdown", {
|
|
276
270
|
fallbackHeading,
|
|
277
271
|
fallbackLevel,
|
|
278
272
|
reasons: detection.reasons
|
|
@@ -281,7 +275,7 @@ function normalizeForFlexMd(input, opts) {
|
|
|
281
275
|
heading: fallbackHeading,
|
|
282
276
|
level: fallbackLevel,
|
|
283
277
|
});
|
|
284
|
-
|
|
278
|
+
logger.debug("Wrapping completed", {
|
|
285
279
|
wrappedLength: wrapped.length,
|
|
286
280
|
originalLength: raw.length
|
|
287
281
|
});
|