flex-md 4.1.0 → 4.2.1

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 CHANGED
@@ -7,6 +7,7 @@ Flex-MD is a TypeScript library for building and enforcing **Markdown Output Con
7
7
  - 📏 **System Parts Protocol**: Standardized size hints that guide LLMs AND enable token prediction
8
8
  - 🧠 **Smart Toolbox**: Cognitive cost analysis, confidence scoring, and improvement detection
9
9
  - 🔧 **Auto-Fix**: Automatically improve specs with one command
10
+ - 🔄 **Markdown to JSON**: Convert sectioned MD to structured objects with camel-casing (v4.1)
10
11
 
11
12
  ## Key Features
12
13
 
@@ -25,6 +26,7 @@ Flex-MD is a TypeScript library for building and enforcing **Markdown Output Con
25
26
  - **Confidence Scoring**: Know how accurate your token estimates will be
26
27
  - **Improvement Detection**: Find issues and get actionable suggestions
27
28
  - **Auto-Fix**: Apply improvements automatically
29
+ - **Markdown to JSON**: Transform sectioned Markdown into structured JSON with camel-cased keys
28
30
 
29
31
  ## Installation
30
32
 
@@ -432,6 +434,203 @@ const v3 = `
432
434
  - `formatImprovementReport(analysis)` - Format improvements
433
435
  - `formatSmartReport(analysis)` - Format complete analysis
434
436
 
437
+ ## Markdown to JSON Transformation (v4.1)
438
+
439
+ Flex-MD includes a robust utility to convert sectioned Markdown (headings and their respective bodies) into a standard JavaScript object/JSON. This is particularly useful for extracting structured data from LLM responses without needing a complex schema.
440
+
441
+ ### Features:
442
+ - **Automatic Camel-Casing**: Headings like `### Short Answer` or `===Next Steps` are automatically converted to valid camelCase keys (`shortAnswer`, `nextSteps`).
443
+ - **Robust Newline Handling**: Automatically handles both actual newlines and literal `\\n` escape sequences often found in LLM outputs.
444
+ - **Support for All Heading Types**: Works with standard `###` headings and `===key` alternative delimiters.
445
+
446
+ ### Usage:
447
+
448
+ ```typescript
449
+ import { markdownToJson } from 'flex-md';
450
+
451
+ const md = `
452
+ ### Short Answer
453
+ The asset is a server named server1 with private IP 192.168.1.1.
454
+
455
+ ### Next Steps
456
+ 1. Document in CMDB
457
+ 2. Perform security check
458
+ `;
459
+
460
+ const data = markdownToJson(md);
461
+
462
+ console.log(data.shortAnswer);
463
+ // "The asset is a server named server1 with private IP 192.168.1.1."
464
+
465
+ console.log(data.nextSteps);
466
+ // "1. Document in CMDB\\n2. Perform security check"
467
+ ```
468
+
469
+ ## NX Flex-MD: Schema-Driven Extraction (Advanced)
470
+
471
+ For more complex scenarios where you need to enforce a specific JSON schema, handle fuzzy matching of headings, or apply automatic fixes to the data, Flex-MD exposes the **NX Flex-MD** toolset (powered by `nx-md-parser`).
472
+
473
+ ### Feature Highlights:
474
+ - **Schema Validation**: Define the exact structure and types (`string`, `number`, `boolean`, `array`, `object`) you expect.
475
+ - **Intelligent Heading Matching**: Matches headings even if they aren't an exact match (e.g., "Summary" vs "Exec Summary").
476
+ - **Automatic Type Conversion**: Converts Markdown strings to numbers or booleans based on your schema.
477
+ - **Auto-Fixing**: Automatically corrects common formatting issues to match the schema.
478
+
479
+ ### Usage:
480
+
481
+ ```typescript
482
+ import { JSONTransformer, Schema } from 'flex-md';
483
+
484
+ // 1. Define a schema
485
+ const schema = Schema.object({
486
+ status: Schema.string(),
487
+ score: Schema.number(),
488
+ isVerified: Schema.boolean(),
489
+ tags: Schema.array(Schema.string())
490
+ });
491
+
492
+ // 2. Create the transformer
493
+ const transformer = new JSONTransformer(schema);
494
+
495
+ // 3. Transform complex Markdown
496
+ const md = `
497
+ ### Status
498
+ Active and operational
499
+
500
+ ### Score
501
+ 95.5
502
+
503
+ ### Verified
504
+ Yes
505
+
506
+ ### Tags
507
+ - production
508
+ - critical
509
+ `;
510
+
511
+ const { result, status, errors } = transformer.transformMarkdown(md);
512
+
513
+ if (status === 'validated' || status === 'fixed') {
514
+ console.log(result);
515
+ /*
516
+ {
517
+ status: "Active and operational",
518
+ score: 95.5,
519
+ isVerified: true,
520
+ tags: ["production", "critical"]
521
+ }
522
+ */
523
+ }
524
+ ```
525
+
526
+ ## Integrated OFS Transformation (The "Best of Both Worlds")
527
+
528
+ Flex-MD allows you to use its native **Output Format Spec (OFS)** (the markdown-style description) as the source of truth for **NX-MD-Parser's** structured extraction. This combines the simplicity of describing output with markdown and the power of schema-driven JSON transformation.
529
+
530
+ ### Usage:
531
+
532
+ ```typescript
533
+ import { parseOutputFormatSpec, transformWithOfs } from 'flex-md';
534
+
535
+ // 1. Describe your output naturally
536
+ const spec = parseOutputFormatSpec(`
537
+ ## Output format
538
+ - Executive Summary — text (required)
539
+ - Key Findings — ordered list (required)
540
+ - Technical Specs — table (optional)
541
+ Columns: Component, Version, Notes
542
+ `);
543
+
544
+ // 2. Transfrom Markdown output using the spec
545
+ const md = `
546
+ ### Summary
547
+ The system is fully operational with 99.9% uptime.
548
+
549
+ ### Findings
550
+ 1. Scalability improved by 40%
551
+ 2. Memory leak in cache module fixed
552
+ `;
553
+
554
+ const { result, status } = transformWithOfs(md, spec);
555
+
556
+ if (status === 'validated' || status === 'fixed') {
557
+ console.log(result['Executive Summary']); // "The system is fully operational..."
558
+ console.log(result['Key Findings']); // ["Scalability improved...", "Memory leak..."]
559
+ }
560
+ ```
561
+
562
+ ### Why use this?
563
+ 1. **Fuzzy Matching**: Even if the LLM slightly changes the heading (e.g., "Summary" instead of "Executive Summary"), the adapter will correctly map it based on the spec.
564
+ 2. **Type Enforcement**: Lists and Tables are automatically converted to JSON arrays and objects.
565
+ 3. **Single Source of Truth**: Use the same spec to guide the LLM AND parse its response.
566
+
567
+ ## Advanced AI Features (via NX-MD-Parser 1.4.0)
568
+
569
+ Flex-MD utilizes the full power of `nx-md-parser` v1.4.0, providing enterprise-grade AI transformation capabilities.
570
+
571
+ ### 🤖 Multi-Algorithm Fuzzy Matching
572
+ The engine uses a weighted combination of four powerful algorithms to find the best match for your headings and keys:
573
+ - **Jaro-Winkler**: Character-level similarity (40%)
574
+ - **Jaccard Tokens**: Token-based similarity (30%)
575
+ - **Dice Coefficient**: N-gram similarity (20%)
576
+ - **Levenshtein Ratio**: Edit distance (10%)
577
+
578
+ ### 🧠 Machine Learning (Learn Aliases)
579
+ You can let the system learn from your data to improve matching over time.
580
+
581
+ ```typescript
582
+ import { learnAliasesFromTransformations } from 'flex-md';
583
+
584
+ const learningResult = learnAliasesFromTransformations([
585
+ {
586
+ input: { "Projct Name": "Test" },
587
+ output: { title: "Test" },
588
+ schema: yourSchema
589
+ }
590
+ ]);
591
+ // System now knows "Projct Name" is an alias for "title"
592
+ ```
593
+
594
+ ### ⚙️ Intelligent Auto-Fixing
595
+ - **Typo Correction**: Automatically fixes property name typos.
596
+ - **Structural Repair**: Restructures flat objects into nested schemas.
597
+ - **Smart Conversion**: Automatically handles `string -> number`, `string -> boolean`, and wrapper types.
598
+
599
+ ## Spec Memory: Remember & Recall
600
+
601
+ Flex-MD includes an in-memory storage feature that allows you to "remember" an Output Format Spec and later reuse it by a unique `recallId`. This is especially useful for maintaining state within a single execution environment.
602
+
603
+ ### Usage:
604
+
605
+ ```typescript
606
+ import { remember, transformWithOfs } from 'flex-md';
607
+
608
+ // 1. Remember a spec extracted from instructions
609
+ const instructions = `
610
+ ## Output format
611
+ - Confidence — number (required)
612
+ - Reason — text (required)
613
+ `;
614
+ const recallId = remember(instructions); // Returns "unique-uuid-..."
615
+
616
+ // 2. Later, use the recallId instead of the spec object
617
+ const md = `
618
+ ### Confidence
619
+ 0.95
620
+
621
+ ### Reason
622
+ Everything looks correctly formatted based on initial evidence.
623
+ `;
624
+
625
+ const { result } = transformWithOfs(md, recallId);
626
+ console.log(result.Confidence); // 0.95
627
+ ```
628
+
629
+ ### Why use this?
630
+ - **Cleaner Code**: Passes simple strings instead of complex spec objects.
631
+ - **Reference-based workflow**: Useful when multiple parts of your system need to agree on the same specification.
632
+ - **Efficiency**: The spec is parsed once and reused.
633
+
435
634
  ## Documentation
436
635
 
437
636
  Detailed guides can be found in the [docs](./docs) folder:
package/SPEC.md CHANGED
@@ -471,6 +471,14 @@ Nested occurrences remain as children in outline.
471
471
  * `detectObjects(text) -> DetectedObject[]`
472
472
  * `parseAny(text) -> { flexDocs: FlexDocument[]; markdownSnippets: string[]; remainder: string }`
473
473
 
474
+ ### Shared Utilities
475
+
476
+ * `markdownToJson(md) -> Record<string, any>`: Convert headings to camel-cased keys and bodies to values. Robustly handles escaped newlines.
477
+ * `ofsToSchema(spec) -> SchemaType`: Convert a Flex-MD `OutputFormatSpec` to an `nx-md-parser` `SchemaType`.
478
+ * `transformWithOfs(md, specOrRecallId) -> TransformResult`: High-level wrapper to transform Markdown using an OFS definition or a stored recall ID.
479
+ * `remember(instructions: string) -> string`: Parse and store an OFS from instructions, returning a unique recall ID.
480
+ * `recall(id: string) -> OutputFormatSpec | undefined`: Retrieve a stored OFS.
481
+
474
482
  ---
475
483
 
476
484
  ## Appendix A — A canonical OFS generator (example)
package/dist/index.cjs CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.enforceFlexMd = exports.repairToMarkdownLevel = exports.detectResponseKind = exports.buildIssuesEnvelopeAuto = exports.buildIssuesEnvelope = exports.parseIssuesEnvelope = exports.processResponseMarkdown = exports.extractFromMarkdown = exports.checkConnection = exports.hasFlexMdContract = exports.checkCompliance = exports.validateMarkdownAgainstOfs = exports.enrichInstructionsWithFlexMd = exports.enrichInstructions = exports.buildMarkdownGuidance = exports.stringifyOutputFormatSpec = exports.validateFormat = exports.parseOutputFormatSpec = exports.buildOutline = void 0;
17
+ exports.jsonToMarkdown = exports.Schema = exports.MarkdownParser = exports.JSONTransformer = exports.enforceFlexMd = exports.repairToMarkdownLevel = exports.detectResponseKind = exports.buildIssuesEnvelopeAuto = exports.buildIssuesEnvelope = exports.parseIssuesEnvelope = exports.processResponseMarkdown = exports.extractFromMarkdown = exports.checkConnection = exports.hasFlexMdContract = exports.checkCompliance = exports.validateMarkdownAgainstOfs = exports.enrichInstructionsWithFlexMd = exports.enrichInstructions = exports.buildMarkdownGuidance = exports.stringifyOutputFormatSpec = exports.recall = exports.remember = exports.transformWithOfs = exports.ofsToSchema = exports.validateFormat = exports.parseOutputFormatSpec = exports.buildOutline = void 0;
18
18
  // Core SFMD Types
19
19
  __exportStar(require("./types.js"), exports);
20
20
  __exportStar(require("./strictness/types.js"), exports);
@@ -26,6 +26,12 @@ Object.defineProperty(exports, "buildOutline", { enumerable: true, get: function
26
26
  var parser_js_1 = require("./ofs/parser.js");
27
27
  Object.defineProperty(exports, "parseOutputFormatSpec", { enumerable: true, get: function () { return parser_js_1.parseOutputFormatSpec; } });
28
28
  Object.defineProperty(exports, "validateFormat", { enumerable: true, get: function () { return parser_js_1.validateFormat; } });
29
+ var adapter_js_1 = require("./ofs/adapter.js");
30
+ Object.defineProperty(exports, "ofsToSchema", { enumerable: true, get: function () { return adapter_js_1.ofsToSchema; } });
31
+ Object.defineProperty(exports, "transformWithOfs", { enumerable: true, get: function () { return adapter_js_1.transformWithOfs; } });
32
+ var memory_js_1 = require("./ofs/memory.js");
33
+ Object.defineProperty(exports, "remember", { enumerable: true, get: function () { return memory_js_1.remember; } });
34
+ Object.defineProperty(exports, "recall", { enumerable: true, get: function () { return memory_js_1.recall; } });
29
35
  var stringify_js_1 = require("./ofs/stringify.js");
30
36
  Object.defineProperty(exports, "stringifyOutputFormatSpec", { enumerable: true, get: function () { return stringify_js_1.stringifyOutputFormatSpec; } });
31
37
  var enricher_js_1 = require("./ofs/enricher.js");
@@ -60,3 +66,9 @@ Object.defineProperty(exports, "enforceFlexMd", { enumerable: true, get: functio
60
66
  __exportStar(require("./detect/json/index.js"), exports);
61
67
  // Token Estimation
62
68
  __exportStar(require("./tokens/index.js"), exports);
69
+ // NX-MD-Parser Integration (Structured MD -> JSON)
70
+ var nx_md_parser_1 = require("nx-md-parser");
71
+ Object.defineProperty(exports, "JSONTransformer", { enumerable: true, get: function () { return nx_md_parser_1.JSONTransformer; } });
72
+ Object.defineProperty(exports, "MarkdownParser", { enumerable: true, get: function () { return nx_md_parser_1.MarkdownParser; } });
73
+ Object.defineProperty(exports, "Schema", { enumerable: true, get: function () { return nx_md_parser_1.Schema; } });
74
+ Object.defineProperty(exports, "jsonToMarkdown", { enumerable: true, get: function () { return nx_md_parser_1.jsonToMarkdown; } });
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ export * from "./strictness/types.js";
3
3
  export * from "./md/parse.js";
4
4
  export { buildOutline } from "./md/outline.js";
5
5
  export { parseOutputFormatSpec, validateFormat } from "./ofs/parser.js";
6
+ export { ofsToSchema, transformWithOfs } from "./ofs/adapter.js";
7
+ export { remember, recall } from "./ofs/memory.js";
6
8
  export { stringifyOutputFormatSpec } from "./ofs/stringify.js";
7
9
  export { buildMarkdownGuidance, enrichInstructions, enrichInstructionsWithFlexMd } from "./ofs/enricher.js";
8
10
  export { validateMarkdownAgainstOfs } from "./validate/validate.js";
@@ -16,3 +18,5 @@ export { repairToMarkdownLevel } from "./pipeline/repair.js";
16
18
  export { enforceFlexMd } from "./pipeline/enforce.js";
17
19
  export * from "./detect/json/index.js";
18
20
  export * from "./tokens/index.js";
21
+ export { JSONTransformer, MarkdownParser, Schema, jsonToMarkdown } from "nx-md-parser";
22
+ export type { SchemaType, ValidationStatus, TransformResult, MarkdownSection } from "nx-md-parser";
package/dist/index.js CHANGED
@@ -6,6 +6,8 @@ export * from "./md/parse.js";
6
6
  export { buildOutline } from "./md/outline.js";
7
7
  // Output Format Spec (OFS)
8
8
  export { parseOutputFormatSpec, validateFormat } from "./ofs/parser.js";
9
+ export { ofsToSchema, transformWithOfs } from "./ofs/adapter.js";
10
+ export { remember, recall } from "./ofs/memory.js";
9
11
  export { stringifyOutputFormatSpec } from "./ofs/stringify.js";
10
12
  export { buildMarkdownGuidance, enrichInstructions, enrichInstructionsWithFlexMd } from "./ofs/enricher.js";
11
13
  // Validation & Extraction
@@ -24,3 +26,5 @@ export { enforceFlexMd } from "./pipeline/enforce.js";
24
26
  export * from "./detect/json/index.js";
25
27
  // Token Estimation
26
28
  export * from "./tokens/index.js";
29
+ // NX-MD-Parser Integration (Structured MD -> JSON)
30
+ export { JSONTransformer, MarkdownParser, Schema, jsonToMarkdown } from "nx-md-parser";
@@ -26,5 +26,6 @@ export interface ParsedSection {
26
26
  }
27
27
  export declare function parseHeadingsAndSections(md: string): ParsedSection[];
28
28
  export declare function extractBullets(body: string): string[];
29
+ export declare function parseMarkdownTable(body: string, columns: string[]): Record<string, string>[];
29
30
  export declare function isIssuesEnvelopeCheck(md: string): IssuesEnvelope;
30
31
  export declare function markdownToJson(md: string): Record<string, any>;
package/dist/md/parse.js CHANGED
@@ -90,12 +90,35 @@ export function extractBullets(body) {
90
90
  const lines = body.split(/\r?\n/);
91
91
  const out = [];
92
92
  for (const line of lines) {
93
- const m = line.match(/^\s*[-*]\s+(.*)\s*$/);
93
+ const m = line.match(/^\s*[-*•]\s+(.*)\s*$/) || line.match(/^\s*\d+\.\s+(.*)\s*$/);
94
94
  if (m)
95
- out.push(m[1] ?? "");
95
+ out.push(m[1].trim());
96
96
  }
97
97
  return out;
98
98
  }
99
+ export function parseMarkdownTable(body, columns) {
100
+ const lines = body.split(/\r?\n/).map(l => l.trim()).filter(l => l.startsWith("|"));
101
+ if (lines.length < 2)
102
+ return [];
103
+ // Identification of header vs separator
104
+ const headerLine = lines[0];
105
+ const separatorLine = lines[1];
106
+ // Check if second line is a separator (e.g. |---|---|)
107
+ const isSeparator = separatorLine && /^[|\s-:]+$/.test(separatorLine);
108
+ const dataStartIndex = isSeparator ? 2 : 1;
109
+ const dataLines = lines.slice(dataStartIndex);
110
+ const results = [];
111
+ for (const line of dataLines) {
112
+ const cells = line.split("|").map(c => c.trim()).filter((_, i, arr) => i > 0 && i < arr.length - 1);
113
+ const row = {};
114
+ // Match cells to provided columns by index
115
+ for (let i = 0; i < columns.length; i++) {
116
+ row[columns[i]] = cells[i] || "";
117
+ }
118
+ results.push(row);
119
+ }
120
+ return results;
121
+ }
99
122
  export function isIssuesEnvelopeCheck(md) {
100
123
  const parsed = parseHeadingsAndSections(md);
101
124
  const want = ["status", "issues", "expected", "found", "how to fix"].map(normalizeName);
@@ -0,0 +1,10 @@
1
+ import { type SchemaType, type TransformResult } from "nx-md-parser";
2
+ import { type OutputFormatSpec } from "../types.js";
3
+ /**
4
+ * Converts a Flex-MD OutputFormatSpec to an nx-md-parser Schema.
5
+ */
6
+ export declare function ofsToSchema(spec: OutputFormatSpec): SchemaType;
7
+ /**
8
+ * Transforms markdown text using a Flex-MD OutputFormatSpec or a recallId.
9
+ */
10
+ export declare function transformWithOfs<T = any>(md: string, specOrRecallId: OutputFormatSpec | string): TransformResult<T>;
@@ -0,0 +1,101 @@
1
+ import { JSONTransformer, Schema } from "nx-md-parser";
2
+ import { recall } from "./memory.js";
3
+ import { parseHeadingsAndSections, extractBullets, parseMarkdownTable, normalizeName } from "../md/parse.js";
4
+ /**
5
+ * Converts a Flex-MD OutputFormatSpec to an nx-md-parser Schema.
6
+ */
7
+ export function ofsToSchema(spec) {
8
+ const properties = {};
9
+ for (const section of spec.sections) {
10
+ let type;
11
+ switch (section.kind) {
12
+ case "list":
13
+ case "ordered_list":
14
+ type = Schema.array(Schema.string());
15
+ break;
16
+ case "table":
17
+ case "ordered_table":
18
+ if (section.columns && section.columns.length > 0) {
19
+ const rowProps = {};
20
+ for (const col of section.columns) {
21
+ rowProps[col] = Schema.string();
22
+ }
23
+ type = Schema.array(Schema.object(rowProps));
24
+ }
25
+ else {
26
+ type = Schema.string();
27
+ }
28
+ break;
29
+ case "text":
30
+ default:
31
+ type = Schema.string();
32
+ break;
33
+ }
34
+ properties[section.name] = type;
35
+ }
36
+ if (spec.tables && spec.tables.length > 0) {
37
+ for (const table of spec.tables) {
38
+ const tableKey = table.columns.join("_");
39
+ const rowProps = {};
40
+ for (const col of table.columns) {
41
+ rowProps[col] = Schema.string();
42
+ }
43
+ properties[tableKey] = Schema.array(Schema.object(rowProps));
44
+ }
45
+ }
46
+ return Schema.object(properties);
47
+ }
48
+ /**
49
+ * Transforms markdown text using a Flex-MD OutputFormatSpec or a recallId.
50
+ */
51
+ export function transformWithOfs(md, specOrRecallId) {
52
+ let spec;
53
+ if (typeof specOrRecallId === "string") {
54
+ spec = recall(specOrRecallId);
55
+ if (!spec) {
56
+ return {
57
+ status: "failed",
58
+ result: null,
59
+ errors: [`Recall ID "${specOrRecallId}" not found in memory.`]
60
+ };
61
+ }
62
+ }
63
+ else {
64
+ spec = specOrRecallId;
65
+ }
66
+ // 1. Parse sections using Flex-MD parser
67
+ const parsedSections = parseHeadingsAndSections(md);
68
+ const parsedObj = {};
69
+ // 2. Map sections to OFS and apply complex parsing (tables/lists)
70
+ for (const sectionSpec of spec.sections) {
71
+ const normName = normalizeName(sectionSpec.name);
72
+ // Find section with similar name
73
+ const found = parsedSections.find(s => normalizeName(s.heading.name) === normName);
74
+ if (found) {
75
+ let value;
76
+ switch (sectionSpec.kind) {
77
+ case "list":
78
+ case "ordered_list":
79
+ value = extractBullets(found.body);
80
+ break;
81
+ case "table":
82
+ case "ordered_table":
83
+ if (sectionSpec.columns) {
84
+ value = parseMarkdownTable(found.body, sectionSpec.columns);
85
+ }
86
+ else {
87
+ value = found.body.trim();
88
+ }
89
+ break;
90
+ default:
91
+ value = found.body.trim();
92
+ break;
93
+ }
94
+ parsedObj[sectionSpec.name] = value;
95
+ }
96
+ }
97
+ // 3. Transform using nx-md-parser for schema validation and auto-fixing
98
+ const schema = ofsToSchema(spec);
99
+ const transformer = new JSONTransformer(schema);
100
+ return transformer.transform(parsedObj);
101
+ }
@@ -0,0 +1,10 @@
1
+ import { type OutputFormatSpec } from "../types.js";
2
+ /**
3
+ * Parses Flex-MD instructions and "remembers" the Output Format Spec.
4
+ * Returns a unique recallId that can be used later.
5
+ */
6
+ export declare function remember(instructions: string): string;
7
+ /**
8
+ * Recalls a previously remembered Output Format Spec.
9
+ */
10
+ export declare function recall(id: string): OutputFormatSpec | undefined;
@@ -0,0 +1,22 @@
1
+ import { parseOutputFormatSpec } from "./parser.js";
2
+ import { randomUUID } from "node:crypto";
3
+ const specMemory = new Map();
4
+ /**
5
+ * Parses Flex-MD instructions and "remembers" the Output Format Spec.
6
+ * Returns a unique recallId that can be used later.
7
+ */
8
+ export function remember(instructions) {
9
+ const spec = parseOutputFormatSpec(instructions);
10
+ if (!spec) {
11
+ throw new Error("No valid Output Format Spec found in instructions.");
12
+ }
13
+ const id = randomUUID();
14
+ specMemory.set(id, spec);
15
+ return id;
16
+ }
17
+ /**
18
+ * Recalls a previously remembered Output Format Spec.
19
+ */
20
+ export function recall(id) {
21
+ return specMemory.get(id);
22
+ }
@@ -140,8 +140,15 @@ export function parseOutputFormatSpec(md, opts = {}) {
140
140
  }
141
141
  // If not a bullet and we have a current section, it's an instruction
142
142
  if (currentSection && line.length > 0) {
143
- const existing = currentSection.instruction || "";
144
- currentSection.instruction = existing ? `${existing} ${line}` : line;
143
+ // Support "Columns: A, B, C" in instructions for tables
144
+ const colMatch = line.match(/^Columns:\s*(.+)$/i);
145
+ if (colMatch && (currentSection.kind === "table" || currentSection.kind === "ordered_table")) {
146
+ currentSection.columns = colMatch[1].split(",").map(c => c.trim()).filter(Boolean);
147
+ }
148
+ else {
149
+ const existing = currentSection.instruction || "";
150
+ currentSection.instruction = existing ? `${existing} ${line}` : line;
151
+ }
145
152
  }
146
153
  }
147
154
  if (!sections.length)
@@ -2,8 +2,11 @@ import { isIssuesEnvelopeCheck } from "../md/parse.js";
2
2
  export function detectResponseKind(text, spec) {
3
3
  const issuesResult = isIssuesEnvelopeCheck(text);
4
4
  const hasIssues = issuesResult.isIssuesEnvelope;
5
+ // Use more robust detection: check for both #+ Name and ===Name
5
6
  const hasSections = spec.sections.some(s => {
6
- const rx = new RegExp(`^#+\\s+${s.name}`, "im");
7
+ // Escape special chars in name but match case-insensitively and with flexible whitespace
8
+ const escapedName = s.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
9
+ const rx = new RegExp(`^((?:#{1,6}\\s+${escapedName})|(?:===${escapedName}))\\s*$`, "im");
7
10
  return rx.test(text);
8
11
  });
9
12
  const isRawJson = /^\s*(\{|\[)/.test(text.trim()) && /\s*(\}|\])$/.test(text.trim());
@@ -248,13 +248,21 @@ export function validateMarkdownAgainstOfs(input, spec, level, policyOverride) {
248
248
  }
249
249
  }
250
250
  }
251
+ // Compute detectedKind more robustly: if we found more than zero sections, it's at least sectioned
252
+ let detectedKind = "markdown";
253
+ if (level >= 2) {
254
+ detectedKind = fencesAll.length > 0 ? "fenced" : (parsed.length > 0 ? "sectioned" : "markdown");
255
+ }
256
+ else {
257
+ detectedKind = parsed.length > 0 ? "sectioned" : "markdown";
258
+ }
251
259
  const ok = !issues.some(i => i.severity === "error");
252
260
  return {
253
261
  ok,
254
262
  level,
255
263
  issues,
256
264
  stats: {
257
- detectedKind: level >= 2 ? (fencesAll.length ? "fenced" : "markdown") : (parsed.length ? "sectioned" : "markdown"),
265
+ detectedKind,
258
266
  sectionCount: occurrences.size,
259
267
  missingRequired,
260
268
  duplicates,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flex-md",
3
- "version": "4.1.0",
3
+ "version": "4.2.1",
4
4
  "description": "Parse and stringify FlexMD: semi-structured Markdown with three powerful layers - Frames, Output Format Spec (OFS), and Detection/Extraction.",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -48,6 +48,8 @@
48
48
  "vitest": "^4.0.16"
49
49
  },
50
50
  "dependencies": {
51
- "nx-helpers": "^1.5.0"
51
+ "nd": "^1.2.0",
52
+ "nx-helpers": "^1.5.0",
53
+ "nx-md-parser": "^1.4.0"
52
54
  }
53
55
  }