@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
package/src/ir/types.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IR (Intermediate Representation) Types
|
|
3
|
+
*
|
|
4
|
+
* Types for the explicit bracket syntax, LLM JSON format, and
|
|
5
|
+
* schema-based validation. These are domain-agnostic — they work
|
|
6
|
+
* for any DSL built on the framework.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { CommandSchema } from '../schema/command-schema';
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// LLM JSON Format
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Structured input format for LLMs.
|
|
17
|
+
* No parsing ambiguity — action and roles are explicitly typed.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```json
|
|
21
|
+
* {
|
|
22
|
+
* "action": "toggle",
|
|
23
|
+
* "roles": { "patient": { "type": "selector", "value": ".active" } },
|
|
24
|
+
* "trigger": { "event": "click" }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export interface SemanticJSON {
|
|
29
|
+
/** The command/action name */
|
|
30
|
+
action: string;
|
|
31
|
+
/** Named semantic roles with typed values */
|
|
32
|
+
roles: Record<string, SemanticJSONValue>;
|
|
33
|
+
/** Optional event trigger (wraps command in event handler) */
|
|
34
|
+
trigger?: {
|
|
35
|
+
event: string;
|
|
36
|
+
modifiers?: Record<string, unknown>;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A typed value in the LLM JSON format.
|
|
42
|
+
*/
|
|
43
|
+
export interface SemanticJSONValue {
|
|
44
|
+
type: 'selector' | 'literal' | 'reference' | 'expression' | 'property-path' | 'flag';
|
|
45
|
+
value: string | number | boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// Schema Lookup
|
|
50
|
+
// =============================================================================
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Interface for optional schema-based role validation in the explicit parser.
|
|
54
|
+
* Consumers provide this to validate roles against command definitions.
|
|
55
|
+
*
|
|
56
|
+
* When not provided, the parser accepts any role names.
|
|
57
|
+
*/
|
|
58
|
+
export interface SchemaLookup {
|
|
59
|
+
getSchema(action: string): CommandSchema | undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Options for parseExplicit().
|
|
64
|
+
*/
|
|
65
|
+
export interface ParseExplicitOptions {
|
|
66
|
+
/** Optional schema lookup for role validation */
|
|
67
|
+
schemaLookup?: SchemaLookup;
|
|
68
|
+
/** Custom set of valid reference names (defaults to DEFAULT_REFERENCES) */
|
|
69
|
+
referenceSet?: ReadonlySet<string>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// =============================================================================
|
|
73
|
+
// Diagnostics
|
|
74
|
+
// =============================================================================
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A diagnostic message from IR validation.
|
|
78
|
+
*
|
|
79
|
+
* Uses required `code` field (compatible with both framework Diagnostic
|
|
80
|
+
* and compilation-service Diagnostic types via structural typing).
|
|
81
|
+
*/
|
|
82
|
+
export interface IRDiagnostic {
|
|
83
|
+
severity: 'error' | 'warning' | 'info';
|
|
84
|
+
code: string;
|
|
85
|
+
message: string;
|
|
86
|
+
suggestion?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// =============================================================================
|
|
90
|
+
// Protocol Full-Fidelity JSON Format
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// These types match the wire format spec (protocol/spec/wire-format.md).
|
|
93
|
+
// Wire format has 3 node kinds; TS-only kinds (conditional, loop) are losslessly
|
|
94
|
+
// encoded as command nodes with v1.1 extension fields.
|
|
95
|
+
// property-path is flattened to expression.
|
|
96
|
+
|
|
97
|
+
export type ProtocolNodeKind = 'command' | 'event-handler' | 'compound';
|
|
98
|
+
export type ProtocolChainType = 'then' | 'and' | 'async' | 'sequential' | 'pipe';
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A typed semantic value in the protocol full-fidelity JSON format.
|
|
102
|
+
* Matches protocol/spec/wire-format.md "Value Shapes" table.
|
|
103
|
+
*/
|
|
104
|
+
export interface ProtocolValueJSON {
|
|
105
|
+
type: 'selector' | 'literal' | 'reference' | 'expression' | 'property-path' | 'flag';
|
|
106
|
+
value?: string | number | boolean;
|
|
107
|
+
dataType?: 'string' | 'number' | 'boolean' | 'duration';
|
|
108
|
+
raw?: string; // expression only
|
|
109
|
+
name?: string; // flag only
|
|
110
|
+
enabled?: boolean; // flag only
|
|
111
|
+
selectorKind?: 'id' | 'class' | 'attribute' | 'element' | 'complex'; // selector only, optional
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Protocol JSON node — matches protocol/spec/wire-format.md.
|
|
116
|
+
* Produced by toProtocolJSON() and consumed by fromProtocolJSON().
|
|
117
|
+
*
|
|
118
|
+
* `kind` defaults to `"command"` when omitted.
|
|
119
|
+
* `trigger` is convenience sugar that wraps a command in an event handler.
|
|
120
|
+
*/
|
|
121
|
+
export interface ProtocolNodeJSON {
|
|
122
|
+
kind?: ProtocolNodeKind; // defaults to "command" when absent
|
|
123
|
+
action: string;
|
|
124
|
+
roles: Record<string, ProtocolValueJSON>;
|
|
125
|
+
trigger?: { event: string; modifiers?: Record<string, unknown> }; // convenience sugar
|
|
126
|
+
body?: ProtocolNodeJSON[]; // event-handler body OR try body (v1.2)
|
|
127
|
+
statements?: ProtocolNodeJSON[]; // compound only
|
|
128
|
+
chainType?: ProtocolChainType; // compound only
|
|
129
|
+
// Conditional fields (v1.1)
|
|
130
|
+
thenBranch?: ProtocolNodeJSON[];
|
|
131
|
+
elseBranch?: ProtocolNodeJSON[];
|
|
132
|
+
// Loop fields (v1.1)
|
|
133
|
+
loopVariant?: 'forever' | 'times' | 'for' | 'while' | 'until';
|
|
134
|
+
loopBody?: ProtocolNodeJSON[];
|
|
135
|
+
loopVariable?: string;
|
|
136
|
+
indexVariable?: string;
|
|
137
|
+
// Type constraint diagnostics (v1.2)
|
|
138
|
+
diagnostics?: ProtocolDiagnosticJSON[];
|
|
139
|
+
// Metadata annotations (v1.2)
|
|
140
|
+
annotations?: AnnotationJSON[];
|
|
141
|
+
// Error handling: try/catch/finally (v1.2)
|
|
142
|
+
catchBranch?: ProtocolNodeJSON[];
|
|
143
|
+
finallyBranch?: ProtocolNodeJSON[];
|
|
144
|
+
// Async coordination: all/race (v1.2)
|
|
145
|
+
asyncVariant?: 'all' | 'race';
|
|
146
|
+
asyncBody?: ProtocolNodeJSON[];
|
|
147
|
+
// Pattern matching: match/arms (v1.2)
|
|
148
|
+
arms?: MatchArmJSON[];
|
|
149
|
+
defaultArm?: ProtocolNodeJSON[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// =============================================================================
|
|
153
|
+
// v1.2 Sub-types
|
|
154
|
+
// =============================================================================
|
|
155
|
+
|
|
156
|
+
/** A type constraint diagnostic in protocol wire format (v1.2). */
|
|
157
|
+
export interface ProtocolDiagnosticJSON {
|
|
158
|
+
level: 'error' | 'warning';
|
|
159
|
+
role: string;
|
|
160
|
+
message: string;
|
|
161
|
+
code: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** A metadata annotation in protocol wire format (v1.2). */
|
|
165
|
+
export interface AnnotationJSON {
|
|
166
|
+
name: string;
|
|
167
|
+
value?: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** A match arm in protocol wire format (v1.2). */
|
|
171
|
+
export interface MatchArmJSON {
|
|
172
|
+
pattern: ProtocolValueJSON;
|
|
173
|
+
body: ProtocolNodeJSON[];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Versioned envelope for multi-node LSE documents (v1.2). */
|
|
177
|
+
export interface LSEEnvelopeJSON {
|
|
178
|
+
lseVersion: string;
|
|
179
|
+
features?: string[];
|
|
180
|
+
nodes: ProtocolNodeJSON[];
|
|
181
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Prompt Generation
|
|
3
|
+
*
|
|
4
|
+
* Auto-generates system prompts from CommandSchema[] for teaching LLMs
|
|
5
|
+
* to output valid LokaScript Explicit Syntax.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
PromptGeneratorConfig,
|
|
10
|
+
GeneratedPrompt,
|
|
11
|
+
PromptSection,
|
|
12
|
+
PromptMetadata,
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
export { generatePrompt, generateExamples, generateProtocolReference } from './prompt-generator';
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for LLM Prompt Generator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { generatePrompt, generateExamples, generateProtocolReference } from './prompt-generator';
|
|
7
|
+
import type { PromptGeneratorConfig } from './types';
|
|
8
|
+
import { defineCommand, defineRole } from '../schema/command-schema';
|
|
9
|
+
import type { CommandSchema } from '../schema/command-schema';
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Test Schemas (mimics domain-flow patterns)
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
const fetchSchema: CommandSchema = defineCommand({
|
|
16
|
+
action: 'fetch',
|
|
17
|
+
description: 'Fetch data from a URL and deliver to a target element',
|
|
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 (json, html, text)',
|
|
30
|
+
required: false,
|
|
31
|
+
expectedTypes: ['expression'],
|
|
32
|
+
}),
|
|
33
|
+
defineRole({
|
|
34
|
+
role: 'destination',
|
|
35
|
+
description: 'Target element to deliver data to',
|
|
36
|
+
required: false,
|
|
37
|
+
expectedTypes: ['selector', 'expression'],
|
|
38
|
+
}),
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const submitSchema: CommandSchema = defineCommand({
|
|
43
|
+
action: 'submit',
|
|
44
|
+
description: 'Submit form data to a URL',
|
|
45
|
+
category: 'action',
|
|
46
|
+
primaryRole: 'patient',
|
|
47
|
+
roles: [
|
|
48
|
+
defineRole({
|
|
49
|
+
role: 'patient',
|
|
50
|
+
description: 'Form element to submit',
|
|
51
|
+
required: true,
|
|
52
|
+
expectedTypes: ['selector', 'expression'],
|
|
53
|
+
}),
|
|
54
|
+
defineRole({
|
|
55
|
+
role: 'destination',
|
|
56
|
+
description: 'URL to submit to',
|
|
57
|
+
required: true,
|
|
58
|
+
expectedTypes: ['expression'],
|
|
59
|
+
}),
|
|
60
|
+
defineRole({
|
|
61
|
+
role: 'style',
|
|
62
|
+
description: 'Request encoding (json, form, multipart)',
|
|
63
|
+
required: false,
|
|
64
|
+
expectedTypes: ['expression'],
|
|
65
|
+
}),
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const toggleSchema: CommandSchema = defineCommand({
|
|
70
|
+
action: 'toggle',
|
|
71
|
+
description: 'Toggle a class or attribute on an element',
|
|
72
|
+
category: 'dom',
|
|
73
|
+
primaryRole: 'patient',
|
|
74
|
+
roles: [
|
|
75
|
+
defineRole({
|
|
76
|
+
role: 'patient',
|
|
77
|
+
description: 'The CSS selector to toggle',
|
|
78
|
+
required: true,
|
|
79
|
+
expectedTypes: ['selector'],
|
|
80
|
+
}),
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const testSchemas: readonly CommandSchema[] = [fetchSchema, submitSchema, toggleSchema];
|
|
85
|
+
|
|
86
|
+
function makeConfig(overrides?: Partial<PromptGeneratorConfig>): PromptGeneratorConfig {
|
|
87
|
+
return {
|
|
88
|
+
domain: 'test',
|
|
89
|
+
description: 'Test domain for unit testing',
|
|
90
|
+
schemas: testSchemas,
|
|
91
|
+
...overrides,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// =============================================================================
|
|
96
|
+
// generatePrompt()
|
|
97
|
+
// =============================================================================
|
|
98
|
+
|
|
99
|
+
describe('generatePrompt', () => {
|
|
100
|
+
it('returns a GeneratedPrompt with all sections', () => {
|
|
101
|
+
const result = generatePrompt(makeConfig());
|
|
102
|
+
expect(result.text).toBeTruthy();
|
|
103
|
+
expect(result.sections.length).toBeGreaterThanOrEqual(4);
|
|
104
|
+
expect(result.metadata.domain).toBe('test');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('includes protocol section', () => {
|
|
108
|
+
const result = generatePrompt(makeConfig());
|
|
109
|
+
const protocol = result.sections.find(s => s.id === 'protocol');
|
|
110
|
+
expect(protocol).toBeDefined();
|
|
111
|
+
expect(protocol!.content).toContain('[command role1:value1');
|
|
112
|
+
expect(protocol!.content).toContain('lowercased');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('includes value types section', () => {
|
|
116
|
+
const result = generatePrompt(makeConfig());
|
|
117
|
+
const types = result.sections.find(s => s.id === 'value-types');
|
|
118
|
+
expect(types).toBeDefined();
|
|
119
|
+
expect(types!.content).toContain('Selector');
|
|
120
|
+
expect(types!.content).toContain('Duration');
|
|
121
|
+
expect(types!.content).toContain('Reference');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('includes commands section with all schemas', () => {
|
|
125
|
+
const result = generatePrompt(makeConfig());
|
|
126
|
+
const commands = result.sections.find(s => s.id === 'commands');
|
|
127
|
+
expect(commands).toBeDefined();
|
|
128
|
+
expect(commands!.content).toContain('`fetch`');
|
|
129
|
+
expect(commands!.content).toContain('`submit`');
|
|
130
|
+
expect(commands!.content).toContain('`toggle`');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('includes output format section', () => {
|
|
134
|
+
const result = generatePrompt(makeConfig());
|
|
135
|
+
const format = result.sections.find(s => s.id === 'output-format');
|
|
136
|
+
expect(format).toBeDefined();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('includes error recovery section', () => {
|
|
140
|
+
const result = generatePrompt(makeConfig());
|
|
141
|
+
const recovery = result.sections.find(s => s.id === 'error-recovery');
|
|
142
|
+
expect(recovery).toBeDefined();
|
|
143
|
+
expect(recovery!.content).toContain('Unknown command');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('counts commands and roles in metadata', () => {
|
|
147
|
+
const result = generatePrompt(makeConfig());
|
|
148
|
+
expect(result.metadata.commandCount).toBe(3);
|
|
149
|
+
// fetch: 3 roles, submit: 3 roles, toggle: 1 role = 7
|
|
150
|
+
expect(result.metadata.roleCount).toBe(7);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('estimates approximate tokens', () => {
|
|
154
|
+
const result = generatePrompt(makeConfig());
|
|
155
|
+
expect(result.metadata.approximateTokens).toBeGreaterThan(0);
|
|
156
|
+
// Roughly chars/4
|
|
157
|
+
expect(result.metadata.approximateTokens).toBe(Math.ceil(result.text.length / 4));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// --- Output Format Variants ---
|
|
161
|
+
|
|
162
|
+
it('generates explicit-only format', () => {
|
|
163
|
+
const result = generatePrompt(makeConfig({ outputFormat: 'explicit' }));
|
|
164
|
+
const format = result.sections.find(s => s.id === 'output-format');
|
|
165
|
+
expect(format!.content).toContain('bracket syntax');
|
|
166
|
+
expect(format!.content).toContain('Do NOT output JSON');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('generates json-only format', () => {
|
|
170
|
+
const result = generatePrompt(makeConfig({ outputFormat: 'json' }));
|
|
171
|
+
const format = result.sections.find(s => s.id === 'output-format');
|
|
172
|
+
expect(format!.content).toContain('JSON');
|
|
173
|
+
expect(format!.content).toContain('Do NOT output bracket syntax');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('generates both format', () => {
|
|
177
|
+
const result = generatePrompt(makeConfig({ outputFormat: 'both' }));
|
|
178
|
+
const format = result.sections.find(s => s.id === 'output-format');
|
|
179
|
+
expect(format!.content).toContain('EITHER format');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// --- Command Documentation ---
|
|
183
|
+
|
|
184
|
+
it('documents required and optional roles', () => {
|
|
185
|
+
const result = generatePrompt(makeConfig());
|
|
186
|
+
const commands = result.sections.find(s => s.id === 'commands')!;
|
|
187
|
+
// fetch has 1 required (source) and 2 optional (style, destination)
|
|
188
|
+
expect(commands.content).toContain('**Required roles:**');
|
|
189
|
+
expect(commands.content).toContain('**Optional roles:**');
|
|
190
|
+
expect(commands.content).toContain('`source`');
|
|
191
|
+
expect(commands.content).toContain('`style`');
|
|
192
|
+
expect(commands.content).toContain('`destination`');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('includes role type information', () => {
|
|
196
|
+
const result = generatePrompt(makeConfig());
|
|
197
|
+
const commands = result.sections.find(s => s.id === 'commands')!;
|
|
198
|
+
expect(commands.content).toContain('(type: expression)');
|
|
199
|
+
expect(commands.content).toContain('(type: selector)');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('includes bracket syntax examples', () => {
|
|
203
|
+
const result = generatePrompt(makeConfig({ outputFormat: 'explicit' }));
|
|
204
|
+
const commands = result.sections.find(s => s.id === 'commands')!;
|
|
205
|
+
expect(commands.content).toContain('[fetch');
|
|
206
|
+
expect(commands.content).toContain('[submit');
|
|
207
|
+
expect(commands.content).toContain('[toggle');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('includes JSON examples when format is json or both', () => {
|
|
211
|
+
const result = generatePrompt(makeConfig({ outputFormat: 'json' }));
|
|
212
|
+
const commands = result.sections.find(s => s.id === 'commands')!;
|
|
213
|
+
expect(commands.content).toContain('"action": "fetch"');
|
|
214
|
+
expect(commands.content).toContain('"roles"');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// --- Token Budget ---
|
|
218
|
+
|
|
219
|
+
it('truncates to maxTokens budget', () => {
|
|
220
|
+
const full = generatePrompt(makeConfig());
|
|
221
|
+
const truncated = generatePrompt(makeConfig({ maxTokens: 100 }));
|
|
222
|
+
|
|
223
|
+
expect(truncated.sections.length).toBeLessThanOrEqual(full.sections.length);
|
|
224
|
+
expect(truncated.text.length).toBeLessThan(full.text.length);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('includes truncation marker when budget is exceeded', () => {
|
|
228
|
+
const result = generatePrompt(makeConfig({ maxTokens: 100 }));
|
|
229
|
+
// Either fewer sections or truncation marker
|
|
230
|
+
const hasFewerSections = result.sections.length < 5;
|
|
231
|
+
const hasTruncationMarker = result.text.includes('*(truncated)*');
|
|
232
|
+
expect(hasFewerSections || hasTruncationMarker).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// --- Examples Per Command ---
|
|
236
|
+
|
|
237
|
+
it('respects examplesPerCommand setting', () => {
|
|
238
|
+
const result1 = generatePrompt(makeConfig({ examplesPerCommand: 1 }));
|
|
239
|
+
const result3 = generatePrompt(makeConfig({ examplesPerCommand: 3 }));
|
|
240
|
+
// More examples = longer text
|
|
241
|
+
expect(result3.text.length).toBeGreaterThan(result1.text.length);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// =============================================================================
|
|
246
|
+
// generateExamples()
|
|
247
|
+
// =============================================================================
|
|
248
|
+
|
|
249
|
+
describe('generateExamples', () => {
|
|
250
|
+
it('generates examples for a command with required + optional roles', () => {
|
|
251
|
+
const examples = generateExamples(fetchSchema, 3);
|
|
252
|
+
expect(examples.length).toBeGreaterThanOrEqual(2);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('generates valid bracket syntax', () => {
|
|
256
|
+
const examples = generateExamples(fetchSchema, 2);
|
|
257
|
+
for (const ex of examples) {
|
|
258
|
+
expect(ex.explicit).toMatch(/^\[fetch\s/);
|
|
259
|
+
expect(ex.explicit).toMatch(/\]$/);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('generates valid JSON format', () => {
|
|
264
|
+
const examples = generateExamples(fetchSchema, 2);
|
|
265
|
+
for (const ex of examples) {
|
|
266
|
+
expect(ex.json.action).toBe('fetch');
|
|
267
|
+
expect(ex.json.roles).toBeDefined();
|
|
268
|
+
expect(typeof ex.json.roles).toBe('object');
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('always includes required roles', () => {
|
|
273
|
+
const examples = generateExamples(fetchSchema, 3);
|
|
274
|
+
for (const ex of examples) {
|
|
275
|
+
// source is required
|
|
276
|
+
expect(ex.json.roles['source']).toBeDefined();
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('generates selector values for selector-typed roles', () => {
|
|
281
|
+
const examples = generateExamples(toggleSchema, 1);
|
|
282
|
+
const patientValue = examples[0].json.roles['patient'];
|
|
283
|
+
expect(patientValue).toBeDefined();
|
|
284
|
+
expect(patientValue.type).toBe('selector');
|
|
285
|
+
expect(String(patientValue.value)).toMatch(/^[#.@*\[]/);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('generates expression values for expression-typed roles', () => {
|
|
289
|
+
const examples = generateExamples(fetchSchema, 1);
|
|
290
|
+
const sourceValue = examples[0].json.roles['source'];
|
|
291
|
+
expect(sourceValue).toBeDefined();
|
|
292
|
+
expect(sourceValue.type).toBe('expression');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('limits output to requested count', () => {
|
|
296
|
+
const one = generateExamples(fetchSchema, 1);
|
|
297
|
+
expect(one.length).toBe(1);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('handles schema with no optional roles', () => {
|
|
301
|
+
const examples = generateExamples(toggleSchema, 3);
|
|
302
|
+
// toggle has only 1 required role, so only 1 combination
|
|
303
|
+
expect(examples.length).toBe(1);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('handles schema with all required roles', () => {
|
|
307
|
+
const examples = generateExamples(submitSchema, 3);
|
|
308
|
+
// submit: 2 required + 1 optional = combos: [req], [req+opt1], total 2
|
|
309
|
+
expect(examples.length).toBeGreaterThanOrEqual(2);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('generates unique combinations', () => {
|
|
313
|
+
const examples = generateExamples(fetchSchema, 4);
|
|
314
|
+
const explicits = examples.map(e => e.explicit);
|
|
315
|
+
const unique = new Set(explicits);
|
|
316
|
+
expect(unique.size).toBe(examples.length);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// =============================================================================
|
|
321
|
+
// generateProtocolReference()
|
|
322
|
+
// =============================================================================
|
|
323
|
+
|
|
324
|
+
describe('generateProtocolReference', () => {
|
|
325
|
+
it('returns a markdown string with protocol info', () => {
|
|
326
|
+
const ref = generateProtocolReference();
|
|
327
|
+
expect(ref).toContain('LokaScript Explicit Syntax');
|
|
328
|
+
expect(ref).toContain('[command');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('includes value type information', () => {
|
|
332
|
+
const ref = generateProtocolReference();
|
|
333
|
+
expect(ref).toContain('Selector');
|
|
334
|
+
expect(ref).toContain('Duration');
|
|
335
|
+
expect(ref).toContain('Boolean');
|
|
336
|
+
expect(ref).toContain('Reference');
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('returns non-empty string', () => {
|
|
340
|
+
const ref = generateProtocolReference();
|
|
341
|
+
expect(ref.length).toBeGreaterThan(100);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// =============================================================================
|
|
346
|
+
// Edge Cases
|
|
347
|
+
// =============================================================================
|
|
348
|
+
|
|
349
|
+
describe('edge cases', () => {
|
|
350
|
+
it('handles empty schemas array', () => {
|
|
351
|
+
const result = generatePrompt(makeConfig({ schemas: [] }));
|
|
352
|
+
expect(result.metadata.commandCount).toBe(0);
|
|
353
|
+
expect(result.metadata.roleCount).toBe(0);
|
|
354
|
+
expect(result.text).toBeTruthy();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('handles schema with no roles', () => {
|
|
358
|
+
const noRoleSchema = defineCommand({
|
|
359
|
+
action: 'noop',
|
|
360
|
+
roles: [],
|
|
361
|
+
primaryRole: 'patient',
|
|
362
|
+
});
|
|
363
|
+
const examples = generateExamples(noRoleSchema, 2);
|
|
364
|
+
expect(examples.length).toBeGreaterThanOrEqual(0);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('handles schema with only optional roles', () => {
|
|
368
|
+
const optionalOnlySchema = defineCommand({
|
|
369
|
+
action: 'log',
|
|
370
|
+
roles: [
|
|
371
|
+
defineRole({ role: 'patient', required: false, expectedTypes: ['expression'] }),
|
|
372
|
+
defineRole({ role: 'style', required: false, expectedTypes: ['literal'] }),
|
|
373
|
+
],
|
|
374
|
+
primaryRole: 'patient',
|
|
375
|
+
});
|
|
376
|
+
const examples = generateExamples(optionalOnlySchema, 3);
|
|
377
|
+
expect(examples.length).toBeGreaterThan(0);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('handles very small maxTokens', () => {
|
|
381
|
+
const result = generatePrompt(makeConfig({ maxTokens: 10 }));
|
|
382
|
+
// Should still produce something
|
|
383
|
+
expect(result.text.length).toBeGreaterThan(0);
|
|
384
|
+
expect(result.sections.length).toBeGreaterThanOrEqual(1);
|
|
385
|
+
});
|
|
386
|
+
});
|