flex-md 4.1.0 → 4.2.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 +167 -0
- package/SPEC.md +8 -0
- package/dist/index.cjs +13 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/md/parse.d.ts +1 -0
- package/dist/md/parse.js +25 -2
- package/dist/ofs/adapter.d.ts +10 -0
- package/dist/ofs/adapter.js +101 -0
- package/dist/ofs/memory.d.ts +10 -0
- package/dist/ofs/memory.js +22 -0
- package/dist/ofs/parser.js +9 -2
- package/package.json +4 -2
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,171 @@ 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
|
+
## Spec Memory: Remember & Recall
|
|
568
|
+
|
|
569
|
+
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.
|
|
570
|
+
|
|
571
|
+
### Usage:
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
import { remember, transformWithOfs } from 'flex-md';
|
|
575
|
+
|
|
576
|
+
// 1. Remember a spec extracted from instructions
|
|
577
|
+
const instructions = `
|
|
578
|
+
## Output format
|
|
579
|
+
- Confidence — number (required)
|
|
580
|
+
- Reason — text (required)
|
|
581
|
+
`;
|
|
582
|
+
const recallId = remember(instructions); // Returns "unique-uuid-..."
|
|
583
|
+
|
|
584
|
+
// 2. Later, use the recallId instead of the spec object
|
|
585
|
+
const md = `
|
|
586
|
+
### Confidence
|
|
587
|
+
0.95
|
|
588
|
+
|
|
589
|
+
### Reason
|
|
590
|
+
Everything looks correctly formatted based on initial evidence.
|
|
591
|
+
`;
|
|
592
|
+
|
|
593
|
+
const { result } = transformWithOfs(md, recallId);
|
|
594
|
+
console.log(result.Confidence); // 0.95
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### Why use this?
|
|
598
|
+
- **Cleaner Code**: Passes simple strings instead of complex spec objects.
|
|
599
|
+
- **Reference-based workflow**: Useful when multiple parts of your system need to agree on the same specification.
|
|
600
|
+
- **Efficiency**: The spec is parsed once and reused.
|
|
601
|
+
|
|
435
602
|
## Documentation
|
|
436
603
|
|
|
437
604
|
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";
|
package/dist/md/parse.d.ts
CHANGED
|
@@ -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*[
|
|
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
|
+
}
|
package/dist/ofs/parser.js
CHANGED
|
@@ -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
|
-
|
|
144
|
-
|
|
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)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flex-md",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
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
|
-
"
|
|
51
|
+
"nd": "^1.2.0",
|
|
52
|
+
"nx-helpers": "^1.5.0",
|
|
53
|
+
"nx-md-parser": "^1.4.0"
|
|
52
54
|
}
|
|
53
55
|
}
|