@lokascript/framework 2.0.0 → 2.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/dist/aot/index.d.ts +1 -0
- package/dist/aot/index.d.ts.map +1 -1
- package/dist/aot/registry-bridge.d.ts +27 -0
- package/dist/aot/registry-bridge.d.ts.map +1 -0
- package/dist/api/create-dsl.d.ts.map +1 -1
- package/dist/api/dispatcher.d.ts +22 -0
- package/dist/api/dispatcher.d.ts.map +1 -1
- package/dist/api/domain-registry.d.ts +41 -0
- package/dist/api/domain-registry.d.ts.map +1 -1
- package/dist/api/index.js +1351 -21
- package/dist/api/index.js.map +1 -1
- package/dist/core/index.js +114 -10
- package/dist/core/index.js.map +1 -1
- package/dist/core/pattern-matching/index.js +74 -10
- package/dist/core/pattern-matching/index.js.map +1 -1
- package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -1
- package/dist/core/types.d.ts +80 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +40 -0
- package/dist/core/types.js.map +1 -1
- package/dist/feedback/confidence-gate.d.ts +28 -0
- package/dist/feedback/confidence-gate.d.ts.map +1 -0
- package/dist/feedback/feedback-formatter.d.ts +21 -0
- package/dist/feedback/feedback-formatter.d.ts.map +1 -0
- package/dist/feedback/index.d.ts +11 -0
- package/dist/feedback/index.d.ts.map +1 -0
- package/dist/feedback/pattern-tracker.d.ts +47 -0
- package/dist/feedback/pattern-tracker.d.ts.map +1 -0
- package/dist/feedback/types.d.ts +113 -0
- package/dist/feedback/types.d.ts.map +1 -0
- package/dist/generation/index.js +2 -1
- package/dist/generation/index.js.map +1 -1
- package/dist/index.cjs +2685 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2652 -71
- package/dist/index.js.map +1 -1
- package/dist/ir/explicit-parser.d.ts +31 -0
- package/dist/ir/explicit-parser.d.ts.map +1 -0
- package/dist/ir/explicit-renderer.d.ts +17 -0
- package/dist/ir/explicit-renderer.d.ts.map +1 -0
- package/dist/ir/from-interchange.d.ts +51 -0
- package/dist/ir/from-interchange.d.ts.map +1 -0
- package/dist/ir/index.d.ts +29 -0
- package/dist/ir/index.d.ts.map +1 -0
- package/dist/ir/index.js +1557 -0
- package/dist/ir/index.js.map +1 -0
- package/dist/ir/json-schema.d.ts +40 -0
- package/dist/ir/json-schema.d.ts.map +1 -0
- package/dist/ir/protocol-json.d.ts +73 -0
- package/dist/ir/protocol-json.d.ts.map +1 -0
- package/dist/ir/references.d.ts +19 -0
- package/dist/ir/references.d.ts.map +1 -0
- package/dist/ir/types.d.ts +141 -0
- package/dist/ir/types.d.ts.map +1 -0
- package/dist/parsing/index.js +74 -10
- package/dist/parsing/index.js.map +1 -1
- package/dist/prompts/index.d.ts +9 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/prompt-generator.d.ts +49 -0
- package/dist/prompts/prompt-generator.d.ts.map +1 -0
- package/dist/prompts/types.d.ts +64 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/schema/command-schema.d.ts +17 -0
- package/dist/schema/command-schema.d.ts.map +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/testing/index.js +3 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/training/index.d.ts +11 -0
- package/dist/training/index.d.ts.map +1 -0
- package/dist/training/jsonl-writer.d.ts +42 -0
- package/dist/training/jsonl-writer.d.ts.map +1 -0
- package/dist/training/schema-synthesizer.d.ts +20 -0
- package/dist/training/schema-synthesizer.d.ts.map +1 -0
- package/dist/training/types.d.ts +73 -0
- package/dist/training/types.d.ts.map +1 -0
- package/package.json +5 -1
- package/src/aot/index.ts +1 -0
- package/src/aot/registry-bridge.test.ts +104 -0
- package/src/aot/registry-bridge.ts +66 -0
- package/src/api/create-dsl.test.ts +306 -0
- package/src/api/create-dsl.ts +21 -0
- package/src/api/dispatcher.test.ts +66 -0
- package/src/api/dispatcher.ts +77 -9
- package/src/api/domain-registry.ts +120 -4
- package/src/core/pattern-matching/pattern-matcher.test.ts +131 -0
- package/src/core/pattern-matching/pattern-matcher.ts +102 -19
- package/src/core/types.ts +145 -2
- package/src/feedback/confidence-gate.test.ts +111 -0
- package/src/feedback/confidence-gate.ts +70 -0
- package/src/feedback/feedback-formatter.test.ts +198 -0
- package/src/feedback/feedback-formatter.ts +298 -0
- package/src/feedback/index.ts +24 -0
- package/src/feedback/pattern-tracker.test.ts +198 -0
- package/src/feedback/pattern-tracker.ts +140 -0
- package/src/feedback/types.ts +149 -0
- package/src/generation/pattern-generator.ts +2 -2
- package/src/index.ts +22 -0
- package/src/integration/llm-lse-integration.test.ts +457 -0
- package/src/ir/explicit-parser.test.ts +306 -0
- package/src/ir/explicit-parser.ts +419 -0
- package/src/ir/explicit-renderer.test.ts +144 -0
- package/src/ir/explicit-renderer.ts +146 -0
- package/src/ir/from-interchange.test.ts +853 -0
- package/src/ir/from-interchange.ts +594 -0
- package/src/ir/index.ts +66 -0
- package/src/ir/json-schema.test.ts +281 -0
- package/src/ir/json-schema.ts +299 -0
- package/src/ir/protocol-json.test.ts +1185 -0
- package/src/ir/protocol-json.ts +745 -0
- package/src/ir/references.test.ts +38 -0
- package/src/ir/references.ts +33 -0
- package/src/ir/round-trip.test.ts +557 -0
- package/src/ir/types.ts +181 -0
- package/src/prompts/index.ts +15 -0
- package/src/prompts/prompt-generator.test.ts +386 -0
- package/src/prompts/prompt-generator.ts +531 -0
- package/src/prompts/types.ts +89 -0
- package/src/schema/command-schema.ts +19 -0
- package/src/training/index.ts +13 -0
- package/src/training/jsonl-writer.test.ts +138 -0
- package/src/training/jsonl-writer.ts +67 -0
- package/src/training/schema-synthesizer.test.ts +250 -0
- package/src/training/schema-synthesizer.ts +306 -0
- package/src/training/types.ts +103 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for JSONL Writer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { toJSONL, toJSONLRow, parseJSONL } from './jsonl-writer';
|
|
7
|
+
import type { TrainingPair } from './types';
|
|
8
|
+
import type { SemanticJSON } from '../ir/types';
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Test Data
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
function makePair(overrides?: Partial<TrainingPair>): TrainingPair {
|
|
15
|
+
return {
|
|
16
|
+
id: 'test-001',
|
|
17
|
+
natural: 'fetch /api/users as json into #list',
|
|
18
|
+
language: 'en',
|
|
19
|
+
explicit: '[fetch source:/api/users style:json destination:#list]',
|
|
20
|
+
json: {
|
|
21
|
+
action: 'fetch',
|
|
22
|
+
roles: {
|
|
23
|
+
source: { type: 'expression', value: '/api/users' },
|
|
24
|
+
style: { type: 'expression', value: 'json' },
|
|
25
|
+
destination: { type: 'selector', value: '#list' },
|
|
26
|
+
},
|
|
27
|
+
} as SemanticJSON,
|
|
28
|
+
domain: 'flow',
|
|
29
|
+
action: 'fetch',
|
|
30
|
+
source: 'synthetic',
|
|
31
|
+
quality: 1.0,
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// toJSONLRow()
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
describe('toJSONLRow', () => {
|
|
41
|
+
it('converts pair to JSONL row', () => {
|
|
42
|
+
const row = toJSONLRow(makePair());
|
|
43
|
+
expect(row.id).toBe('test-001');
|
|
44
|
+
expect(row.prompt).toBe('fetch /api/users as json into #list');
|
|
45
|
+
expect(row.completion).toBe('[fetch source:/api/users style:json destination:#list]');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('includes metadata', () => {
|
|
49
|
+
const row = toJSONLRow(makePair());
|
|
50
|
+
expect(row.metadata.domain).toBe('flow');
|
|
51
|
+
expect(row.metadata.action).toBe('fetch');
|
|
52
|
+
expect(row.metadata.language).toBe('en');
|
|
53
|
+
expect(row.metadata.source).toBe('synthetic');
|
|
54
|
+
expect(row.metadata.quality).toBe(1.0);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('includes confidence when present', () => {
|
|
58
|
+
const row = toJSONLRow(makePair({ confidence: 0.95 }));
|
|
59
|
+
expect(row.metadata.confidence).toBe(0.95);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('omits confidence when absent', () => {
|
|
63
|
+
const row = toJSONLRow(makePair());
|
|
64
|
+
expect(row.metadata.confidence).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// toJSONL()
|
|
70
|
+
// =============================================================================
|
|
71
|
+
|
|
72
|
+
describe('toJSONL', () => {
|
|
73
|
+
it('produces one line per pair', () => {
|
|
74
|
+
const pairs = [makePair({ id: 'a' }), makePair({ id: 'b' }), makePair({ id: 'c' })];
|
|
75
|
+
const jsonl = toJSONL(pairs);
|
|
76
|
+
const lines = jsonl.split('\n');
|
|
77
|
+
expect(lines.length).toBe(3);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('produces valid JSON on each line', () => {
|
|
81
|
+
const pairs = [makePair({ id: 'a' }), makePair({ id: 'b' })];
|
|
82
|
+
const jsonl = toJSONL(pairs);
|
|
83
|
+
for (const line of jsonl.split('\n')) {
|
|
84
|
+
expect(() => JSON.parse(line)).not.toThrow();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns empty string for empty pairs', () => {
|
|
89
|
+
const jsonl = toJSONL([]);
|
|
90
|
+
expect(jsonl).toBe('');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('preserves pair data through serialization', () => {
|
|
94
|
+
const pair = makePair();
|
|
95
|
+
const jsonl = toJSONL([pair]);
|
|
96
|
+
const parsed = JSON.parse(jsonl);
|
|
97
|
+
expect(parsed.id).toBe(pair.id);
|
|
98
|
+
expect(parsed.prompt).toBe(pair.natural);
|
|
99
|
+
expect(parsed.completion).toBe(pair.explicit);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// =============================================================================
|
|
104
|
+
// parseJSONL()
|
|
105
|
+
// =============================================================================
|
|
106
|
+
|
|
107
|
+
describe('parseJSONL', () => {
|
|
108
|
+
it('roundtrips through toJSONL and parseJSONL', () => {
|
|
109
|
+
const pairs = [makePair({ id: 'a' }), makePair({ id: 'b' })];
|
|
110
|
+
const jsonl = toJSONL(pairs);
|
|
111
|
+
const rows = parseJSONL(jsonl);
|
|
112
|
+
expect(rows.length).toBe(2);
|
|
113
|
+
expect(rows[0].id).toBe('a');
|
|
114
|
+
expect(rows[1].id).toBe('b');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('skips empty lines', () => {
|
|
118
|
+
const jsonl =
|
|
119
|
+
'{"id":"a","prompt":"x","completion":"y","metadata":{}}\n\n{"id":"b","prompt":"x","completion":"y","metadata":{}}';
|
|
120
|
+
const rows = parseJSONL(jsonl);
|
|
121
|
+
expect(rows.length).toBe(2);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('handles single line', () => {
|
|
125
|
+
const jsonl = '{"id":"only","prompt":"x","completion":"y","metadata":{}}';
|
|
126
|
+
const rows = parseJSONL(jsonl);
|
|
127
|
+
expect(rows.length).toBe(1);
|
|
128
|
+
expect(rows[0].id).toBe('only');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('preserves metadata through roundtrip', () => {
|
|
132
|
+
const pair = makePair({ confidence: 0.85 });
|
|
133
|
+
const jsonl = toJSONL([pair]);
|
|
134
|
+
const rows = parseJSONL(jsonl);
|
|
135
|
+
expect(rows[0].metadata.domain).toBe('flow');
|
|
136
|
+
expect(rows[0].metadata.confidence).toBe(0.85);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONL Writer
|
|
3
|
+
*
|
|
4
|
+
* Exports training pairs in JSONL (JSON Lines) format,
|
|
5
|
+
* compatible with standard fine-tuning pipelines.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { TrainingPair } from './types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convert training pairs to JSONL format (one JSON object per line).
|
|
12
|
+
*
|
|
13
|
+
* Each line contains:
|
|
14
|
+
* - id: unique pair identifier
|
|
15
|
+
* - prompt: the natural language input
|
|
16
|
+
* - completion: the LSE bracket syntax output
|
|
17
|
+
* - metadata: domain, action, language, source, quality
|
|
18
|
+
*/
|
|
19
|
+
export function toJSONL(pairs: readonly TrainingPair[]): string {
|
|
20
|
+
return pairs.map(pair => JSON.stringify(toJSONLRow(pair))).join('\n');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert a single training pair to a JSONL row.
|
|
25
|
+
*/
|
|
26
|
+
export function toJSONLRow(pair: TrainingPair): JSONLRow {
|
|
27
|
+
return {
|
|
28
|
+
id: pair.id,
|
|
29
|
+
prompt: pair.natural,
|
|
30
|
+
completion: pair.explicit,
|
|
31
|
+
metadata: {
|
|
32
|
+
domain: pair.domain,
|
|
33
|
+
action: pair.action,
|
|
34
|
+
language: pair.language,
|
|
35
|
+
source: pair.source,
|
|
36
|
+
quality: pair.quality,
|
|
37
|
+
...(pair.confidence !== undefined && { confidence: pair.confidence }),
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parse JSONL back into rows (for validation/roundtrip testing).
|
|
44
|
+
*/
|
|
45
|
+
export function parseJSONL(text: string): JSONLRow[] {
|
|
46
|
+
return text
|
|
47
|
+
.split('\n')
|
|
48
|
+
.filter(line => line.trim().length > 0)
|
|
49
|
+
.map(line => JSON.parse(line) as JSONLRow);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A single row in JSONL format.
|
|
54
|
+
*/
|
|
55
|
+
export interface JSONLRow {
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly prompt: string;
|
|
58
|
+
readonly completion: string;
|
|
59
|
+
readonly metadata: {
|
|
60
|
+
readonly domain: string;
|
|
61
|
+
readonly action: string;
|
|
62
|
+
readonly language: string;
|
|
63
|
+
readonly source: string;
|
|
64
|
+
readonly quality: number;
|
|
65
|
+
readonly confidence?: number;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Schema Synthesizer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { synthesizeFromSchemas } from './schema-synthesizer';
|
|
7
|
+
import type { SynthesisConfig } from './types';
|
|
8
|
+
import { defineCommand, defineRole } from '../schema/command-schema';
|
|
9
|
+
import type { CommandSchema } from '../schema/command-schema';
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Test Schemas
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
const fetchSchema: CommandSchema = defineCommand({
|
|
16
|
+
action: 'fetch',
|
|
17
|
+
description: 'Fetch data from a URL',
|
|
18
|
+
category: 'source',
|
|
19
|
+
primaryRole: 'source',
|
|
20
|
+
roles: [
|
|
21
|
+
defineRole({
|
|
22
|
+
role: 'source',
|
|
23
|
+
description: 'URL to fetch from',
|
|
24
|
+
required: true,
|
|
25
|
+
expectedTypes: ['expression'],
|
|
26
|
+
}),
|
|
27
|
+
defineRole({
|
|
28
|
+
role: 'style',
|
|
29
|
+
description: 'Response format',
|
|
30
|
+
required: false,
|
|
31
|
+
expectedTypes: ['expression'],
|
|
32
|
+
markerOverride: { en: 'as', es: 'como' },
|
|
33
|
+
}),
|
|
34
|
+
defineRole({
|
|
35
|
+
role: 'destination',
|
|
36
|
+
description: 'Target element',
|
|
37
|
+
required: false,
|
|
38
|
+
expectedTypes: ['selector', 'expression'],
|
|
39
|
+
markerOverride: { en: 'into', es: 'en' },
|
|
40
|
+
}),
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const toggleSchema: CommandSchema = defineCommand({
|
|
45
|
+
action: 'toggle',
|
|
46
|
+
description: 'Toggle a class',
|
|
47
|
+
category: 'dom',
|
|
48
|
+
primaryRole: 'patient',
|
|
49
|
+
roles: [
|
|
50
|
+
defineRole({
|
|
51
|
+
role: 'patient',
|
|
52
|
+
description: 'CSS selector',
|
|
53
|
+
required: true,
|
|
54
|
+
expectedTypes: ['selector'],
|
|
55
|
+
}),
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const submitSchema: CommandSchema = defineCommand({
|
|
60
|
+
action: 'submit',
|
|
61
|
+
description: 'Submit form data',
|
|
62
|
+
category: 'action',
|
|
63
|
+
primaryRole: 'patient',
|
|
64
|
+
roles: [
|
|
65
|
+
defineRole({
|
|
66
|
+
role: 'patient',
|
|
67
|
+
description: 'Form element',
|
|
68
|
+
required: true,
|
|
69
|
+
expectedTypes: ['selector'],
|
|
70
|
+
}),
|
|
71
|
+
defineRole({
|
|
72
|
+
role: 'destination',
|
|
73
|
+
description: 'URL to submit to',
|
|
74
|
+
required: true,
|
|
75
|
+
expectedTypes: ['expression'],
|
|
76
|
+
markerOverride: { en: 'to', es: 'a' },
|
|
77
|
+
}),
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const testSchemas = [fetchSchema, toggleSchema, submitSchema];
|
|
82
|
+
|
|
83
|
+
function makeConfig(overrides?: Partial<SynthesisConfig>): SynthesisConfig {
|
|
84
|
+
return {
|
|
85
|
+
domain: 'test',
|
|
86
|
+
...overrides,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// =============================================================================
|
|
91
|
+
// synthesizeFromSchemas()
|
|
92
|
+
// =============================================================================
|
|
93
|
+
|
|
94
|
+
describe('synthesizeFromSchemas', () => {
|
|
95
|
+
it('generates pairs for each schema', () => {
|
|
96
|
+
const result = synthesizeFromSchemas(testSchemas, makeConfig());
|
|
97
|
+
expect(result.pairs.length).toBeGreaterThan(0);
|
|
98
|
+
expect(result.metadata.commandCount).toBe(3);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('includes metadata', () => {
|
|
102
|
+
const result = synthesizeFromSchemas(testSchemas, makeConfig());
|
|
103
|
+
expect(result.metadata.domain).toBe('test');
|
|
104
|
+
expect(result.metadata.pairCount).toBe(result.pairs.length);
|
|
105
|
+
expect(result.metadata.bySource.synthetic).toBe(result.pairs.length);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('generates valid bracket syntax', () => {
|
|
109
|
+
const result = synthesizeFromSchemas(testSchemas, makeConfig());
|
|
110
|
+
for (const pair of result.pairs) {
|
|
111
|
+
expect(pair.explicit).toMatch(/^\[/);
|
|
112
|
+
expect(pair.explicit).toMatch(/\]$/);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('generates valid JSON', () => {
|
|
117
|
+
const result = synthesizeFromSchemas(testSchemas, makeConfig());
|
|
118
|
+
for (const pair of result.pairs) {
|
|
119
|
+
expect(pair.json.action).toBeTruthy();
|
|
120
|
+
expect(typeof pair.json.roles).toBe('object');
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('includes required roles in every pair', () => {
|
|
125
|
+
const result = synthesizeFromSchemas([fetchSchema], makeConfig());
|
|
126
|
+
for (const pair of result.pairs) {
|
|
127
|
+
expect(pair.json.roles['source']).toBeDefined();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('generates natural language form', () => {
|
|
132
|
+
const result = synthesizeFromSchemas([fetchSchema], makeConfig());
|
|
133
|
+
for (const pair of result.pairs) {
|
|
134
|
+
expect(pair.natural).toContain('fetch');
|
|
135
|
+
expect(pair.natural.length).toBeGreaterThan(0);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('uses markers in natural language', () => {
|
|
140
|
+
const result = synthesizeFromSchemas([fetchSchema], makeConfig({ languages: ['en'] }));
|
|
141
|
+
// Find a pair with the style role
|
|
142
|
+
const withStyle = result.pairs.find(p => p.json.roles['style']);
|
|
143
|
+
if (withStyle) {
|
|
144
|
+
expect(withStyle.natural).toContain('as');
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// --- Configuration ---
|
|
149
|
+
|
|
150
|
+
it('respects maxPairsPerCommand', () => {
|
|
151
|
+
const result = synthesizeFromSchemas([fetchSchema], makeConfig({ maxPairsPerCommand: 2 }));
|
|
152
|
+
expect(result.pairs.length).toBeLessThanOrEqual(2);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('respects minQuality filter', () => {
|
|
156
|
+
const result = synthesizeFromSchemas(testSchemas, makeConfig({ minQuality: 0.9 }));
|
|
157
|
+
for (const pair of result.pairs) {
|
|
158
|
+
expect(pair.quality).toBeGreaterThanOrEqual(0.9);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('generates for multiple languages', () => {
|
|
163
|
+
const result = synthesizeFromSchemas([toggleSchema], makeConfig({ languages: ['en', 'es'] }));
|
|
164
|
+
const languages = new Set(result.pairs.map(p => p.language));
|
|
165
|
+
expect(languages.has('en')).toBe(true);
|
|
166
|
+
expect(languages.has('es')).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('generates shuffled role order variants', () => {
|
|
170
|
+
const result = synthesizeFromSchemas([submitSchema], makeConfig({ shuffleRoles: true }));
|
|
171
|
+
// submit has 2 required roles, so shuffled variant should exist
|
|
172
|
+
expect(result.pairs.length).toBeGreaterThanOrEqual(2);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('disables shuffling when configured', () => {
|
|
176
|
+
const withShuffle = synthesizeFromSchemas(
|
|
177
|
+
[submitSchema],
|
|
178
|
+
makeConfig({ shuffleRoles: true, maxPairsPerCommand: 10 })
|
|
179
|
+
);
|
|
180
|
+
const noShuffle = synthesizeFromSchemas(
|
|
181
|
+
[submitSchema],
|
|
182
|
+
makeConfig({ shuffleRoles: false, maxPairsPerCommand: 10 })
|
|
183
|
+
);
|
|
184
|
+
expect(noShuffle.pairs.length).toBeLessThanOrEqual(withShuffle.pairs.length);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// --- Pair Quality ---
|
|
188
|
+
|
|
189
|
+
it('assigns quality based on role coverage', () => {
|
|
190
|
+
const result = synthesizeFromSchemas([fetchSchema], makeConfig());
|
|
191
|
+
for (const pair of result.pairs) {
|
|
192
|
+
expect(pair.quality).toBeGreaterThanOrEqual(0);
|
|
193
|
+
expect(pair.quality).toBeLessThanOrEqual(1);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('gives quality 1.0 when all required roles present', () => {
|
|
198
|
+
const result = synthesizeFromSchemas([toggleSchema], makeConfig());
|
|
199
|
+
// toggle has 1 required role, always filled
|
|
200
|
+
expect(result.pairs[0].quality).toBe(1);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// --- Pair Metadata ---
|
|
204
|
+
|
|
205
|
+
it('tags pairs as synthetic source', () => {
|
|
206
|
+
const result = synthesizeFromSchemas(testSchemas, makeConfig());
|
|
207
|
+
for (const pair of result.pairs) {
|
|
208
|
+
expect(pair.source).toBe('synthetic');
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('assigns unique IDs', () => {
|
|
213
|
+
const result = synthesizeFromSchemas(testSchemas, makeConfig());
|
|
214
|
+
const ids = new Set(result.pairs.map(p => p.id));
|
|
215
|
+
expect(ids.size).toBe(result.pairs.length);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('includes domain in each pair', () => {
|
|
219
|
+
const result = synthesizeFromSchemas(testSchemas, makeConfig());
|
|
220
|
+
for (const pair of result.pairs) {
|
|
221
|
+
expect(pair.domain).toBe('test');
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('includes action in each pair', () => {
|
|
226
|
+
const result = synthesizeFromSchemas(testSchemas, makeConfig());
|
|
227
|
+
const actions = new Set(result.pairs.map(p => p.action));
|
|
228
|
+
expect(actions.has('fetch')).toBe(true);
|
|
229
|
+
expect(actions.has('toggle')).toBe(true);
|
|
230
|
+
expect(actions.has('submit')).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// --- Edge Cases ---
|
|
234
|
+
|
|
235
|
+
it('handles empty schemas', () => {
|
|
236
|
+
const result = synthesizeFromSchemas([], makeConfig());
|
|
237
|
+
expect(result.pairs.length).toBe(0);
|
|
238
|
+
expect(result.metadata.commandCount).toBe(0);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('handles schema with no roles', () => {
|
|
242
|
+
const noRoleSchema = defineCommand({
|
|
243
|
+
action: 'noop',
|
|
244
|
+
roles: [],
|
|
245
|
+
primaryRole: 'patient',
|
|
246
|
+
});
|
|
247
|
+
const result = synthesizeFromSchemas([noRoleSchema], makeConfig());
|
|
248
|
+
expect(result.pairs.length).toBeGreaterThanOrEqual(0);
|
|
249
|
+
});
|
|
250
|
+
});
|