dirac-lang 0.1.9 → 0.1.10

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.
@@ -0,0 +1,128 @@
1
+ # LLM Tag Validation
2
+
3
+ The `<llm>` tag now supports automatic validation and correction of generated Dirac code when using `execute="true"`.
4
+
5
+ ## Attributes
6
+
7
+ ### Execution Mode
8
+ - `execute="true"` - Parse and execute the LLM response as Dirac code (existing feature)
9
+
10
+ ### Tag Validation (New)
11
+ - `validate="true"` - Enable tag validation for LLM-generated code
12
+ - `autocorrect="true"` - Automatically correct similar tag names using semantic matching
13
+ - `max-retries="N"` - Maximum number of retry attempts if validation fails (default: 0)
14
+
15
+ ## How It Works
16
+
17
+ When `validate="true"` is enabled:
18
+
19
+ 1. **Parse**: LLM response is parsed as Dirac XML
20
+ 2. **Validate**: Each tag is checked against available subroutines
21
+ - Verifies tag names exist
22
+ - Checks required parameters are present
23
+ - Warns about unknown attributes
24
+ 3. **Semantic Matching**: If a tag doesn't exist, finds the closest match using embeddings
25
+ 4. **Auto-correct**: If `autocorrect="true"` and similarity >= 0.75, replaces tag with best match
26
+ 5. **Retry**: If validation fails and `max-retries > 0`, sends error feedback to LLM and retries
27
+ 6. **Execute**: Once validation passes, executes the (possibly corrected) code
28
+
29
+ ## Examples
30
+
31
+ ### Basic Validation
32
+
33
+ ```xml
34
+ <dirac>
35
+ <subroutine name="greet" param-name="string:required">
36
+ <output>Hello, <variable name="name" />!</output>
37
+ </subroutine>
38
+
39
+ <llm execute="true" validate="true">
40
+ Greet Alice
41
+ </llm>
42
+ </dirac>
43
+ ```
44
+
45
+ If the LLM generates `<greeting name="Alice" />` instead of `<greet name="Alice" />`, validation will fail with an error.
46
+
47
+ ### With Auto-correction
48
+
49
+ ```xml
50
+ <dirac>
51
+ <subroutine name="greet" param-name="string:required">
52
+ <output>Hello, <variable name="name" />!</output>
53
+ </subroutine>
54
+
55
+ <llm execute="true" validate="true" autocorrect="true">
56
+ Greet Alice
57
+ </llm>
58
+ </dirac>
59
+ ```
60
+
61
+ If the LLM generates `<greeting name="Alice" />`, and `greeting` is semantically similar to `greet` (similarity >= 0.75), it will be auto-corrected to `<greet name="Alice" />`.
62
+
63
+ ### With Retry
64
+
65
+ ```xml
66
+ <dirac>
67
+ <subroutine name="calculate" param-expression="string:required">
68
+ <eval><variable name="expression" /></eval>
69
+ </subroutine>
70
+
71
+ <llm execute="true" validate="true" max-retries="3">
72
+ Calculate 2 + 2
73
+ </llm>
74
+ </dirac>
75
+ ```
76
+
77
+ If validation fails:
78
+ 1. LLM receives feedback: "Your previous response had the following errors: <compute>: Missing required parameter: expression"
79
+ 2. LLM generates a new response
80
+ 3. Process repeats up to 3 times until validation passes
81
+
82
+ ### Combined Approach
83
+
84
+ ```xml
85
+ <llm execute="true" validate="true" autocorrect="true" max-retries="2">
86
+ Generate a greeting for Bob
87
+ </llm>
88
+ ```
89
+
90
+ This combines auto-correction with retry:
91
+ - First tries to auto-correct similar tag names
92
+ - If that doesn't fix all errors, retries with LLM feedback
93
+ - Maximum 2 retry attempts
94
+
95
+ ## Error Messages
96
+
97
+ Validation can detect:
98
+
99
+ - **Missing tags**: `Tag <xyz> does not exist and no similar tag was found.`
100
+ - **Similar tags**: `Tag <greeting> does not exist. Did you mean <greet>? (similarity: 0.85)`
101
+ - **Missing parameters**: `<greet>: Missing required parameter: name`
102
+ - **Unknown attributes**: `<greet>: Unknown attribute: person`
103
+
104
+ ## Requirements
105
+
106
+ - Requires embedding server for semantic matching (Ollama with embeddinggemma model)
107
+ - Configure in `config.yml`:
108
+ ```yaml
109
+ embeddingServer:
110
+ host: localhost
111
+ port: 11435
112
+ ```
113
+
114
+ ## Performance Notes
115
+
116
+ - Validation adds latency due to embedding calls
117
+ - Each tag requires an embedding API call
118
+ - Consider using `validate="true"` only when necessary
119
+ - Auto-correction is faster than retries
120
+
121
+ ## Best Practices
122
+
123
+ 1. **Start without validation** for simple prompts
124
+ 2. **Add validation** when LLM frequently generates incorrect tags
125
+ 3. **Use autocorrect** for typos and similar names
126
+ 4. **Use retry** for more complex validation errors
127
+ 5. **Limit retries** to 2-3 to avoid excessive API calls
128
+ 6. **Monitor debug output** with `DIRAC_DEBUG=1` to see validation details
package/TESTING.md ADDED
@@ -0,0 +1,162 @@
1
+ # Dirac Testing System
2
+
3
+ A simple, lightweight testing framework for Dirac language files.
4
+
5
+ ## Running Tests
6
+
7
+ ```bash
8
+ npm test
9
+ ```
10
+
11
+ This will:
12
+ 1. Build the project
13
+ 2. Run all `*.test.di` files in the `tests/` directory
14
+ 3. Report results
15
+
16
+ ## Writing Tests
17
+
18
+ Tests are standard Dirac `.di` files with special comments that define expectations.
19
+
20
+ ### Test File Format
21
+
22
+ ```xml
23
+ <!-- TEST: test_name -->
24
+ <!-- EXPECT: expected output -->
25
+ <dirac>
26
+ <!-- your test code here -->
27
+ </dirac>
28
+ ```
29
+
30
+ ### Test Metadata Comments
31
+
32
+ - `<!-- TEST: name -->` - Name of the test (optional, defaults to filename)
33
+ - `<!-- EXPECT: output -->` - Expected output (optional)
34
+ - `<!-- EXPECT_ERROR: error message -->` - Expected error message (optional)
35
+
36
+ ### Example Tests
37
+
38
+ #### Basic Output Test
39
+
40
+ ```xml
41
+ <!-- TEST: hello_world -->
42
+ <!-- EXPECT: Hello, World! -->
43
+ <dirac>
44
+ <output>Hello, World!</output>
45
+ </dirac>
46
+ ```
47
+
48
+ #### Variable Test
49
+
50
+ ```xml
51
+ <!-- TEST: variables -->
52
+ <!-- EXPECT: Value is: test123 -->
53
+ <dirac>
54
+ <defvar name="myvar" value="test123" />
55
+ <output>Value is: <variable name="myvar" /></output>
56
+ </dirac>
57
+ ```
58
+
59
+ #### Error Test
60
+
61
+ ```xml
62
+ <!-- TEST: missing_variable -->
63
+ <!-- EXPECT_ERROR: Variable 'missing' not found -->
64
+ <dirac>
65
+ <variable name="missing" />
66
+ </dirac>
67
+ ```
68
+
69
+ #### No Expectation (Just Run)
70
+
71
+ ```xml
72
+ <!-- TEST: runs_without_error -->
73
+ <dirac>
74
+ <defvar name="x" value="10" />
75
+ <!-- Test passes if it runs without throwing an error -->
76
+ </dirac>
77
+ ```
78
+
79
+ ## Test Organization
80
+
81
+ Tests should be placed in the `tests/` directory with the `.test.di` extension:
82
+
83
+ ```
84
+ tests/
85
+ ├── basic-output.test.di
86
+ ├── variable-basic.test.di
87
+ ├── subroutine-basic.test.di
88
+ ├── if-conditional.test.di
89
+ └── ...
90
+ ```
91
+
92
+ You can organize tests into subdirectories:
93
+
94
+ ```
95
+ tests/
96
+ ├── core/
97
+ │ ├── output.test.di
98
+ │ └── variables.test.di
99
+ ├── control-flow/
100
+ │ ├── if.test.di
101
+ │ └── loop.test.di
102
+ └── llm/
103
+ └── basic.test.di
104
+ ```
105
+
106
+ ## Output Matching
107
+
108
+ The test runner normalizes whitespace when comparing output:
109
+ - Multiple spaces/newlines are collapsed to single spaces
110
+ - Leading/trailing whitespace is trimmed
111
+ - This allows tests to ignore XML formatting whitespace
112
+
113
+ For example, these are equivalent:
114
+ - Expected: `Hello World`
115
+ - Actual: ` Hello World ` → matches ✓
116
+ - Actual: `Hello\n World` → matches ✓
117
+
118
+ ## Exit Codes
119
+
120
+ - `0` - All tests passed
121
+ - `1` - One or more tests failed
122
+
123
+ ## Continuous Integration
124
+
125
+ Add to your CI pipeline:
126
+
127
+ ```yaml
128
+ # .github/workflows/test.yml
129
+ - name: Run tests
130
+ run: npm test
131
+ ```
132
+
133
+ ## Best Practices
134
+
135
+ 1. **One concept per test** - Each test should verify one specific behavior
136
+ 2. **Descriptive names** - Use clear test names that describe what's being tested
137
+ 3. **Test edge cases** - Include tests for error conditions and boundary cases
138
+ 4. **Keep tests fast** - Avoid LLM calls in unit tests (use mocks or separate integration tests)
139
+ 5. **Test regressions** - When fixing bugs, add a test to prevent regression
140
+
141
+ ## Example Test Suite
142
+
143
+ ```
144
+ tests/
145
+ ├── basic-output.test.di # Basic output functionality
146
+ ├── variable-basic.test.di # Variable definition and output
147
+ ├── subroutine-basic.test.di # Subroutine calls
148
+ ├── if-conditional.test.di # Conditional execution
149
+ ├── loop-basic.test.di # Loop functionality
150
+ ├── exception-basic.test.di # Exception handling
151
+ └── test-if-basic.test.di # test-if conditional
152
+ ```
153
+
154
+ ## Future Enhancements
155
+
156
+ Potential improvements:
157
+ - Watch mode for TDD workflow
158
+ - Code coverage reporting
159
+ - Performance benchmarks
160
+ - Parallel test execution
161
+ - Test fixtures/setup/teardown
162
+ - Snapshot testing for complex outputs
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DiracParser,
3
3
  integrate
4
- } from "./chunk-X3LQ626H.js";
4
+ } from "./chunk-UDA4H3GU.js";
5
5
  import {
6
6
  createSession,
7
7
  getAvailableSubroutines,
@@ -370,12 +370,12 @@ async function executeIf(session, element) {
370
370
  const condition = await evaluatePredicate(session, conditionElement);
371
371
  if (condition) {
372
372
  if (thenElement) {
373
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-46CAOCAZ.js");
373
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
374
374
  await integrateChildren2(session, thenElement);
375
375
  }
376
376
  } else {
377
377
  if (elseElement) {
378
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-46CAOCAZ.js");
378
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
379
379
  await integrateChildren2(session, elseElement);
380
380
  }
381
381
  }
@@ -388,7 +388,7 @@ async function evaluatePredicate(session, predicateElement) {
388
388
  return await evaluateCondition(session, predicateElement);
389
389
  }
390
390
  const outputLengthBefore = session.output.length;
391
- const { integrate: integrate2 } = await import("./interpreter-46CAOCAZ.js");
391
+ const { integrate: integrate2 } = await import("./interpreter-BYQIE2MI.js");
392
392
  await integrate2(session, predicateElement);
393
393
  const newOutputChunks = session.output.slice(outputLengthBefore);
394
394
  const result = newOutputChunks.join("").trim();
@@ -411,11 +411,11 @@ async function evaluateCondition(session, condElement) {
411
411
  }
412
412
  const outputLengthBefore = session.output.length;
413
413
  const args = [];
414
- const { integrate: integrate2 } = await import("./interpreter-46CAOCAZ.js");
414
+ const { integrate: integrate2 } = await import("./interpreter-BYQIE2MI.js");
415
415
  for (const child of condElement.children) {
416
416
  if (child.tag.toLowerCase() === "arg") {
417
417
  const argOutputStart = session.output.length;
418
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-46CAOCAZ.js");
418
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
419
419
  await integrateChildren2(session, child);
420
420
  const newChunks = session.output.slice(argOutputStart);
421
421
  const argValue = newChunks.join("");
@@ -724,10 +724,16 @@ then you call it like
724
724
  if (outputVar) {
725
725
  setVariable(session, outputVar, result, false);
726
726
  } else if (executeMode) {
727
+ const validateTags = element.attributes["validate"] === "true";
728
+ const autocorrect = element.attributes["autocorrect"] === "true";
729
+ const maxRetries = parseInt(element.attributes["max-retries"] || "0", 10);
727
730
  if (session.debug) {
728
731
  console.error(`[LLM] Executing response as Dirac code:
729
732
  ${result}
730
733
  `);
734
+ if (validateTags) {
735
+ console.error(`[LLM] Tag validation enabled (autocorrect: ${autocorrect}, max-retries: ${maxRetries})`);
736
+ }
731
737
  }
732
738
  const replaceTick = element.attributes["replace-tick"] === "true";
733
739
  let diracCode = result.trim();
@@ -743,7 +749,81 @@ ${result}
743
749
  }
744
750
  try {
745
751
  const parser = new DiracParser();
746
- const dynamicAST = parser.parse(diracCode);
752
+ let dynamicAST = parser.parse(diracCode);
753
+ if (validateTags) {
754
+ const { validateDiracCode, applyCorrectedTags } = await import("./tag-validator-I3GLCBVD.js");
755
+ let validation = await validateDiracCode(session, dynamicAST, { autocorrect });
756
+ let retryCount = 0;
757
+ while (!validation.valid && retryCount < maxRetries) {
758
+ retryCount++;
759
+ if (session.debug) {
760
+ console.error(`[LLM] Validation failed (attempt ${retryCount}/${maxRetries}):`, validation.errorMessages);
761
+ }
762
+ const errorFeedback = validation.errorMessages.join("\n");
763
+ const retryPrompt = `Your previous response had the following errors:
764
+ ${errorFeedback}
765
+
766
+ Please fix these errors and generate valid Dirac XML again. Remember to only use the allowed tags.`;
767
+ dialogHistory.push({ role: "user", content: retryPrompt });
768
+ if (isOpenAI) {
769
+ const response = await session.llmClient.chat.completions.create({
770
+ model,
771
+ max_tokens: maxTokens,
772
+ temperature,
773
+ messages: dialogHistory
774
+ });
775
+ result = response.choices[0]?.message?.content || "";
776
+ } else if (isOllama) {
777
+ const ollamaPrompt = dialogHistory.map((m) => `${m.role.charAt(0).toUpperCase() + m.role.slice(1)}: ${m.content}`).join("\n");
778
+ result = await session.llmClient.complete(ollamaPrompt, {
779
+ model,
780
+ temperature,
781
+ max_tokens: maxTokens
782
+ });
783
+ } else {
784
+ const response = await session.llmClient.messages.create({
785
+ model,
786
+ max_tokens: maxTokens,
787
+ temperature,
788
+ messages: dialogHistory
789
+ });
790
+ const content = response.content[0];
791
+ result = content.type === "text" ? content.text : "";
792
+ }
793
+ dialogHistory.push({ role: "assistant", content: result });
794
+ if (contextVar) {
795
+ setVariable(session, contextVar, dialogHistory, true);
796
+ }
797
+ if (session.debug) {
798
+ console.error(`[LLM] Retry ${retryCount} response:
799
+ ${result}
800
+ `);
801
+ }
802
+ diracCode = result.trim();
803
+ if (replaceTick && diracCode.startsWith("```")) {
804
+ const match = diracCode.match(/^```(\w+)?\n?/m);
805
+ if (match && match[1] === "bash") {
806
+ const endIdx = diracCode.indexOf("```", 3);
807
+ let bashContent = diracCode.slice(match[0].length, endIdx).trim();
808
+ diracCode = `<system>${bashContent}</system>`;
809
+ } else {
810
+ diracCode = diracCode.replace(/^```(?:xml|html|dirac)?\n?/m, "").replace(/\n?```$/m, "").trim();
811
+ }
812
+ }
813
+ dynamicAST = parser.parse(diracCode);
814
+ validation = await validateDiracCode(session, dynamicAST, { autocorrect });
815
+ }
816
+ if (!validation.valid) {
817
+ throw new Error(`Tag validation failed after ${maxRetries} retries:
818
+ ${validation.errorMessages.join("\n")}`);
819
+ }
820
+ if (autocorrect) {
821
+ dynamicAST = applyCorrectedTags(dynamicAST, validation.results);
822
+ if (session.debug) {
823
+ console.error("[LLM] Applied auto-corrections to tags");
824
+ }
825
+ }
826
+ }
747
827
  await integrate(session, dynamicAST);
748
828
  } catch (parseError) {
749
829
  if (session.debug) {
@@ -1162,7 +1242,7 @@ async function executeTagCheck(session, element) {
1162
1242
  const executeTag = correctedTag || tagName;
1163
1243
  console.error(`[tag-check] Executing <${executeTag}/> as all checks passed and execute=true.`);
1164
1244
  const elementToExecute = correctedTag ? { ...child, tag: correctedTag } : child;
1165
- const { integrate: integrate2 } = await import("./interpreter-46CAOCAZ.js");
1245
+ const { integrate: integrate2 } = await import("./interpreter-BYQIE2MI.js");
1166
1246
  await integrate2(session, elementToExecute);
1167
1247
  }
1168
1248
  }
@@ -1171,7 +1251,7 @@ async function executeTagCheck(session, element) {
1171
1251
  // src/tags/throw.ts
1172
1252
  async function executeThrow(session, element) {
1173
1253
  const exceptionName = element.attributes?.name || "exception";
1174
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-46CAOCAZ.js");
1254
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
1175
1255
  const exceptionDom = {
1176
1256
  tag: "exception-content",
1177
1257
  attributes: { name: exceptionName },
@@ -1184,7 +1264,7 @@ async function executeThrow(session, element) {
1184
1264
  // src/tags/try.ts
1185
1265
  async function executeTry(session, element) {
1186
1266
  setExceptionBoundary(session);
1187
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-46CAOCAZ.js");
1267
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
1188
1268
  await integrateChildren2(session, element);
1189
1269
  unsetExceptionBoundary(session);
1190
1270
  }
@@ -1194,7 +1274,7 @@ async function executeCatch(session, element) {
1194
1274
  const exceptionName = element.attributes?.name || "exception";
1195
1275
  const caughtCount = lookupException(session, exceptionName);
1196
1276
  if (caughtCount > 0) {
1197
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-46CAOCAZ.js");
1277
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
1198
1278
  await integrateChildren2(session, element);
1199
1279
  }
1200
1280
  flushCurrentException(session);
@@ -1203,7 +1283,7 @@ async function executeCatch(session, element) {
1203
1283
  // src/tags/exception.ts
1204
1284
  async function executeException(session, element) {
1205
1285
  const exceptions = getCurrentExceptions(session);
1206
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-46CAOCAZ.js");
1286
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
1207
1287
  for (const exceptionDom of exceptions) {
1208
1288
  await integrateChildren2(session, exceptionDom);
1209
1289
  }
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  execute
4
- } from "./chunk-VHP3G4BF.js";
5
- import "./chunk-X3LQ626H.js";
4
+ } from "./chunk-3LRJLSZC.js";
5
+ import "./chunk-UDA4H3GU.js";
6
6
  import "./chunk-E7IWGUE6.js";
7
7
 
8
8
  // src/cli.ts
@@ -11,7 +11,7 @@ import "dotenv/config";
11
11
  // package.json
12
12
  var package_default = {
13
13
  name: "dirac-lang",
14
- version: "0.1.9",
14
+ version: "0.1.10",
15
15
  description: "LLM-Augmented Declarative Execution",
16
16
  type: "module",
17
17
  main: "dist/index.js",
@@ -21,8 +21,9 @@ var package_default = {
21
21
  },
22
22
  scripts: {
23
23
  dev: "tsx src/cli.ts",
24
- build: "tsup src/index.ts src/cli.ts --format esm --dts --clean",
25
- test: "vitest",
24
+ build: "tsup src/index.ts src/cli.ts src/test-runner.ts --format esm --dts --clean",
25
+ test: "npm run build && node dist/test-runner.js tests",
26
+ "test:watch": "npm run build && node dist/test-runner.js tests --watch",
26
27
  typecheck: "tsc --noEmit"
27
28
  },
28
29
  keywords: [
package/dist/index.js CHANGED
@@ -2,11 +2,11 @@ import {
2
2
  createLLMAdapter,
3
3
  execute,
4
4
  executeUserCommand
5
- } from "./chunk-VHP3G4BF.js";
5
+ } from "./chunk-3LRJLSZC.js";
6
6
  import {
7
7
  DiracParser,
8
8
  integrate
9
- } from "./chunk-X3LQ626H.js";
9
+ } from "./chunk-UDA4H3GU.js";
10
10
  import {
11
11
  createSession,
12
12
  getAvailableSubroutines,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  integrate,
3
3
  integrateChildren
4
- } from "./chunk-X3LQ626H.js";
4
+ } from "./chunk-UDA4H3GU.js";
5
5
  import "./chunk-E7IWGUE6.js";
6
6
  export {
7
7
  integrate,
@@ -0,0 +1,152 @@
1
+ // src/utils/tag-validator.ts
2
+ import fs from "fs";
3
+ import yaml from "js-yaml";
4
+ var SIMILARITY_CUTOFF = 0.75;
5
+ function getEmbeddingServerConfig() {
6
+ try {
7
+ const config = yaml.load(fs.readFileSync("config.yml", "utf8"));
8
+ const host = config.embeddingServer?.host || "localhost";
9
+ const port = config.embeddingServer?.port || 11435;
10
+ return { host, port };
11
+ } catch {
12
+ return { host: "localhost", port: 11435 };
13
+ }
14
+ }
15
+ async function getEmbeddings(tags) {
16
+ const { host, port } = getEmbeddingServerConfig();
17
+ return await Promise.all(tags.map(async (tag) => {
18
+ const response = await fetch(`http://${host}:${port}/api/embeddings`, {
19
+ method: "POST",
20
+ headers: { "Content-Type": "application/json" },
21
+ body: JSON.stringify({ model: "embeddinggemma", prompt: tag })
22
+ });
23
+ const data = await response.json();
24
+ return data.embedding;
25
+ }));
26
+ }
27
+ function cosine(a, b) {
28
+ const dot = a.reduce((sum, v, i) => sum + v * b[i], 0);
29
+ const normA = Math.sqrt(a.reduce((sum, v) => sum + v * v, 0));
30
+ const normB = Math.sqrt(b.reduce((sum, v) => sum + v * v, 0));
31
+ return dot / (normA * normB);
32
+ }
33
+ async function getBestTagMatch(candidate, allowed) {
34
+ const tags = [candidate, ...allowed];
35
+ const embeddings = await getEmbeddings(tags);
36
+ const candidateVec = embeddings[0];
37
+ const allowedVecs = embeddings.slice(1);
38
+ let bestIdx = 0, bestScore = -1;
39
+ allowedVecs.forEach((vec, i) => {
40
+ const score = cosine(candidateVec, vec);
41
+ if (score > bestScore) {
42
+ bestScore = score;
43
+ bestIdx = i;
44
+ }
45
+ });
46
+ return { tag: allowed[bestIdx], score: bestScore };
47
+ }
48
+ async function validateTag(session, element, options = {}) {
49
+ const { autocorrect = false, similarityCutoff = SIMILARITY_CUTOFF } = options;
50
+ const { getAvailableSubroutines } = await import("./session-Z37PNJOE.js");
51
+ const subroutines = getAvailableSubroutines(session);
52
+ const allowed = new Set(subroutines.map((s) => s.name));
53
+ const tagName = element.tag;
54
+ const result = {
55
+ valid: false,
56
+ tagName,
57
+ originalTag: tagName,
58
+ corrected: false,
59
+ errors: [],
60
+ warnings: []
61
+ };
62
+ if (allowed.has(tagName)) {
63
+ const sub = subroutines.find((s) => s.name === tagName);
64
+ if (sub && Array.isArray(sub.parameters)) {
65
+ const paramNames = sub.parameters.map((p) => p.name);
66
+ for (const param of sub.parameters) {
67
+ if (param.required && !(param.name in element.attributes)) {
68
+ result.errors.push(`Missing required parameter: ${param.name}`);
69
+ }
70
+ }
71
+ for (const attr in element.attributes) {
72
+ if (!paramNames.includes(attr)) {
73
+ result.warnings.push(`Unknown attribute: ${attr}`);
74
+ }
75
+ }
76
+ }
77
+ result.valid = result.errors.length === 0;
78
+ } else {
79
+ const best = await getBestTagMatch(tagName, Array.from(allowed));
80
+ if (best.score >= similarityCutoff) {
81
+ result.similarity = best.score;
82
+ if (autocorrect) {
83
+ result.tagName = best.tag;
84
+ result.corrected = true;
85
+ result.warnings.push(`Auto-corrected from <${tagName}> to <${best.tag}> (similarity: ${best.score.toFixed(2)})`);
86
+ const sub = subroutines.find((s) => s.name === best.tag);
87
+ if (sub && Array.isArray(sub.parameters)) {
88
+ const paramNames = sub.parameters.map((p) => p.name);
89
+ for (const param of sub.parameters) {
90
+ if (param.required && !(param.name in element.attributes)) {
91
+ result.errors.push(`Missing required parameter: ${param.name}`);
92
+ }
93
+ }
94
+ for (const attr in element.attributes) {
95
+ if (!paramNames.includes(attr)) {
96
+ result.warnings.push(`Unknown attribute: ${attr}`);
97
+ }
98
+ }
99
+ }
100
+ result.valid = result.errors.length === 0;
101
+ } else {
102
+ result.errors.push(`Tag <${tagName}> does not exist. Did you mean <${best.tag}>? (similarity: ${best.score.toFixed(2)})`);
103
+ }
104
+ } else {
105
+ result.errors.push(`Tag <${tagName}> does not exist and no similar tag was found.`);
106
+ }
107
+ }
108
+ return result;
109
+ }
110
+ async function validateDiracCode(session, ast, options = {}) {
111
+ const results = [];
112
+ const errorMessages = [];
113
+ async function validateElement(element) {
114
+ if (element.tag && element.tag !== "dirac" && element.tag !== "") {
115
+ const result = await validateTag(session, element, options);
116
+ results.push(result);
117
+ if (!result.valid) {
118
+ errorMessages.push(`<${result.originalTag}>: ${result.errors.join(", ")}`);
119
+ }
120
+ }
121
+ for (const child of element.children) {
122
+ await validateElement(child);
123
+ }
124
+ }
125
+ await validateElement(ast);
126
+ return {
127
+ valid: errorMessages.length === 0,
128
+ results,
129
+ errorMessages
130
+ };
131
+ }
132
+ function applyCorrectedTags(ast, results) {
133
+ let resultIndex = 0;
134
+ function correctElement(element) {
135
+ if (element.tag && element.tag !== "dirac" && element.tag !== "") {
136
+ const result = results[resultIndex++];
137
+ if (result && result.corrected) {
138
+ element = { ...element, tag: result.tagName };
139
+ }
140
+ }
141
+ return {
142
+ ...element,
143
+ children: element.children.map((child) => correctElement(child))
144
+ };
145
+ }
146
+ return correctElement(ast);
147
+ }
148
+ export {
149
+ applyCorrectedTags,
150
+ validateDiracCode,
151
+ validateTag
152
+ };