@vibe-lang/runtime 0.2.5

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.
Files changed (250) hide show
  1. package/package.json +46 -0
  2. package/src/ast/index.ts +375 -0
  3. package/src/ast.ts +2 -0
  4. package/src/debug/advanced-features.ts +482 -0
  5. package/src/debug/bun-inspector.ts +424 -0
  6. package/src/debug/handoff-manager.ts +283 -0
  7. package/src/debug/index.ts +150 -0
  8. package/src/debug/runner.ts +365 -0
  9. package/src/debug/server.ts +565 -0
  10. package/src/debug/stack-merger.ts +267 -0
  11. package/src/debug/state.ts +581 -0
  12. package/src/debug/test/advanced-features.test.ts +300 -0
  13. package/src/debug/test/e2e.test.ts +218 -0
  14. package/src/debug/test/handoff-manager.test.ts +256 -0
  15. package/src/debug/test/runner.test.ts +256 -0
  16. package/src/debug/test/stack-merger.test.ts +163 -0
  17. package/src/debug/test/state.test.ts +400 -0
  18. package/src/debug/test/ts-debug-integration.test.ts +374 -0
  19. package/src/debug/test/ts-import-tracker.test.ts +125 -0
  20. package/src/debug/test/ts-source-map.test.ts +169 -0
  21. package/src/debug/ts-import-tracker.ts +151 -0
  22. package/src/debug/ts-source-map.ts +171 -0
  23. package/src/errors/index.ts +124 -0
  24. package/src/index.ts +358 -0
  25. package/src/lexer/index.ts +348 -0
  26. package/src/lexer.ts +2 -0
  27. package/src/parser/index.ts +792 -0
  28. package/src/parser/parse.ts +45 -0
  29. package/src/parser/test/async.test.ts +248 -0
  30. package/src/parser/test/destructuring.test.ts +167 -0
  31. package/src/parser/test/do-expression.test.ts +486 -0
  32. package/src/parser/test/errors/do-expression.test.ts +95 -0
  33. package/src/parser/test/errors/error-locations.test.ts +230 -0
  34. package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
  35. package/src/parser/test/errors/missing-tokens.test.ts +126 -0
  36. package/src/parser/test/errors/model-declaration.test.ts +185 -0
  37. package/src/parser/test/errors/nested-blocks.test.ts +226 -0
  38. package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
  39. package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
  40. package/src/parser/test/import-export.test.ts +143 -0
  41. package/src/parser/test/literals.test.ts +404 -0
  42. package/src/parser/test/model-declaration.test.ts +161 -0
  43. package/src/parser/test/nested-blocks.test.ts +402 -0
  44. package/src/parser/test/parser.test.ts +743 -0
  45. package/src/parser/test/private.test.ts +136 -0
  46. package/src/parser/test/template-literal.test.ts +127 -0
  47. package/src/parser/test/tool-declaration.test.ts +302 -0
  48. package/src/parser/test/ts-block.test.ts +252 -0
  49. package/src/parser/test/type-annotations.test.ts +254 -0
  50. package/src/parser/visitor/helpers.ts +330 -0
  51. package/src/parser/visitor.ts +794 -0
  52. package/src/parser.ts +2 -0
  53. package/src/runtime/ai/cache-chunking.test.ts +69 -0
  54. package/src/runtime/ai/cache-chunking.ts +73 -0
  55. package/src/runtime/ai/client.ts +109 -0
  56. package/src/runtime/ai/context.ts +168 -0
  57. package/src/runtime/ai/formatters.ts +316 -0
  58. package/src/runtime/ai/index.ts +38 -0
  59. package/src/runtime/ai/language-ref.ts +38 -0
  60. package/src/runtime/ai/providers/anthropic.ts +253 -0
  61. package/src/runtime/ai/providers/google.ts +201 -0
  62. package/src/runtime/ai/providers/openai.ts +156 -0
  63. package/src/runtime/ai/retry.ts +100 -0
  64. package/src/runtime/ai/return-tools.ts +301 -0
  65. package/src/runtime/ai/test/client.test.ts +83 -0
  66. package/src/runtime/ai/test/formatters.test.ts +485 -0
  67. package/src/runtime/ai/test/retry.test.ts +137 -0
  68. package/src/runtime/ai/test/return-tools.test.ts +450 -0
  69. package/src/runtime/ai/test/tool-loop.test.ts +319 -0
  70. package/src/runtime/ai/test/tool-schema.test.ts +241 -0
  71. package/src/runtime/ai/tool-loop.ts +203 -0
  72. package/src/runtime/ai/tool-schema.ts +151 -0
  73. package/src/runtime/ai/types.ts +113 -0
  74. package/src/runtime/ai-logger.ts +255 -0
  75. package/src/runtime/ai-provider.ts +347 -0
  76. package/src/runtime/async/dependencies.ts +276 -0
  77. package/src/runtime/async/executor.ts +293 -0
  78. package/src/runtime/async/index.ts +43 -0
  79. package/src/runtime/async/scheduling.ts +163 -0
  80. package/src/runtime/async/test/dependencies.test.ts +284 -0
  81. package/src/runtime/async/test/executor.test.ts +388 -0
  82. package/src/runtime/context.ts +357 -0
  83. package/src/runtime/exec/ai.ts +139 -0
  84. package/src/runtime/exec/expressions.ts +475 -0
  85. package/src/runtime/exec/frames.ts +26 -0
  86. package/src/runtime/exec/functions.ts +305 -0
  87. package/src/runtime/exec/interpolation.ts +312 -0
  88. package/src/runtime/exec/statements.ts +604 -0
  89. package/src/runtime/exec/tools.ts +129 -0
  90. package/src/runtime/exec/typescript.ts +215 -0
  91. package/src/runtime/exec/variables.ts +279 -0
  92. package/src/runtime/index.ts +975 -0
  93. package/src/runtime/modules.ts +452 -0
  94. package/src/runtime/serialize.ts +103 -0
  95. package/src/runtime/state.ts +489 -0
  96. package/src/runtime/stdlib/core.ts +45 -0
  97. package/src/runtime/stdlib/directory.test.ts +156 -0
  98. package/src/runtime/stdlib/edit.test.ts +154 -0
  99. package/src/runtime/stdlib/fastEdit.test.ts +201 -0
  100. package/src/runtime/stdlib/glob.test.ts +106 -0
  101. package/src/runtime/stdlib/grep.test.ts +144 -0
  102. package/src/runtime/stdlib/index.ts +16 -0
  103. package/src/runtime/stdlib/readFile.test.ts +123 -0
  104. package/src/runtime/stdlib/tools/index.ts +707 -0
  105. package/src/runtime/stdlib/writeFile.test.ts +157 -0
  106. package/src/runtime/step.ts +969 -0
  107. package/src/runtime/test/ai-context.test.ts +1086 -0
  108. package/src/runtime/test/ai-result-object.test.ts +419 -0
  109. package/src/runtime/test/ai-tool-flow.test.ts +859 -0
  110. package/src/runtime/test/async-execution-order.test.ts +618 -0
  111. package/src/runtime/test/async-execution.test.ts +344 -0
  112. package/src/runtime/test/async-nested.test.ts +660 -0
  113. package/src/runtime/test/async-parallel-timing.test.ts +546 -0
  114. package/src/runtime/test/basic1.test.ts +154 -0
  115. package/src/runtime/test/binary-operators.test.ts +431 -0
  116. package/src/runtime/test/break-statement.test.ts +257 -0
  117. package/src/runtime/test/context-modes.test.ts +650 -0
  118. package/src/runtime/test/context.test.ts +466 -0
  119. package/src/runtime/test/core-functions.test.ts +228 -0
  120. package/src/runtime/test/e2e.test.ts +88 -0
  121. package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
  122. package/src/runtime/test/error-locations/main-error.vibe +4 -0
  123. package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
  124. package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
  125. package/src/runtime/test/for-in.test.ts +312 -0
  126. package/src/runtime/test/helpers.ts +69 -0
  127. package/src/runtime/test/imports.test.ts +334 -0
  128. package/src/runtime/test/json-expressions.test.ts +232 -0
  129. package/src/runtime/test/literals.test.ts +372 -0
  130. package/src/runtime/test/logical-indexing.test.ts +478 -0
  131. package/src/runtime/test/member-methods.test.ts +324 -0
  132. package/src/runtime/test/model-config.test.ts +338 -0
  133. package/src/runtime/test/null-handling.test.ts +342 -0
  134. package/src/runtime/test/private-visibility.test.ts +332 -0
  135. package/src/runtime/test/runtime-state.test.ts +514 -0
  136. package/src/runtime/test/scoping.test.ts +370 -0
  137. package/src/runtime/test/string-interpolation.test.ts +354 -0
  138. package/src/runtime/test/template-literal.test.ts +181 -0
  139. package/src/runtime/test/tool-execution.test.ts +467 -0
  140. package/src/runtime/test/tool-schema-generation.test.ts +477 -0
  141. package/src/runtime/test/tostring.test.ts +210 -0
  142. package/src/runtime/test/ts-block.test.ts +594 -0
  143. package/src/runtime/test/ts-error-location.test.ts +231 -0
  144. package/src/runtime/test/types.test.ts +732 -0
  145. package/src/runtime/test/verbose-logger.test.ts +710 -0
  146. package/src/runtime/test/vibe-expression.test.ts +54 -0
  147. package/src/runtime/test/vibe-value-errors.test.ts +541 -0
  148. package/src/runtime/test/while.test.ts +232 -0
  149. package/src/runtime/tools/builtin.ts +30 -0
  150. package/src/runtime/tools/directory-tools.ts +70 -0
  151. package/src/runtime/tools/file-tools.ts +228 -0
  152. package/src/runtime/tools/index.ts +5 -0
  153. package/src/runtime/tools/registry.ts +48 -0
  154. package/src/runtime/tools/search-tools.ts +134 -0
  155. package/src/runtime/tools/security.ts +36 -0
  156. package/src/runtime/tools/system-tools.ts +312 -0
  157. package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
  158. package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
  159. package/src/runtime/tools/test/registry.test.ts +713 -0
  160. package/src/runtime/tools/test/security.test.ts +86 -0
  161. package/src/runtime/tools/test/system-tools.test.ts +679 -0
  162. package/src/runtime/tools/test/ts-schema.test.ts +357 -0
  163. package/src/runtime/tools/ts-schema.ts +341 -0
  164. package/src/runtime/tools/types.ts +89 -0
  165. package/src/runtime/tools/utility-tools.ts +198 -0
  166. package/src/runtime/ts-eval.ts +126 -0
  167. package/src/runtime/types.ts +797 -0
  168. package/src/runtime/validation.ts +160 -0
  169. package/src/runtime/verbose-logger.ts +459 -0
  170. package/src/runtime.ts +2 -0
  171. package/src/semantic/analyzer-context.ts +62 -0
  172. package/src/semantic/analyzer-validators.ts +575 -0
  173. package/src/semantic/analyzer-visitors.ts +534 -0
  174. package/src/semantic/analyzer.ts +83 -0
  175. package/src/semantic/index.ts +11 -0
  176. package/src/semantic/symbol-table.ts +58 -0
  177. package/src/semantic/test/async-validation.test.ts +301 -0
  178. package/src/semantic/test/compress-validation.test.ts +179 -0
  179. package/src/semantic/test/const-reassignment.test.ts +111 -0
  180. package/src/semantic/test/control-flow.test.ts +346 -0
  181. package/src/semantic/test/destructuring.test.ts +185 -0
  182. package/src/semantic/test/duplicate-declarations.test.ts +168 -0
  183. package/src/semantic/test/export-validation.test.ts +111 -0
  184. package/src/semantic/test/fixtures/math.ts +31 -0
  185. package/src/semantic/test/imports.test.ts +148 -0
  186. package/src/semantic/test/json-type.test.ts +68 -0
  187. package/src/semantic/test/literals.test.ts +127 -0
  188. package/src/semantic/test/model-validation.test.ts +179 -0
  189. package/src/semantic/test/prompt-validation.test.ts +343 -0
  190. package/src/semantic/test/scoping.test.ts +312 -0
  191. package/src/semantic/test/tool-validation.test.ts +306 -0
  192. package/src/semantic/test/ts-type-checking.test.ts +563 -0
  193. package/src/semantic/test/type-constraints.test.ts +111 -0
  194. package/src/semantic/test/type-inference.test.ts +87 -0
  195. package/src/semantic/test/type-validation.test.ts +552 -0
  196. package/src/semantic/test/undefined-variables.test.ts +163 -0
  197. package/src/semantic/ts-block-checker.ts +204 -0
  198. package/src/semantic/ts-signatures.ts +194 -0
  199. package/src/semantic/ts-types.ts +170 -0
  200. package/src/semantic/types.ts +58 -0
  201. package/tests/fixtures/conditional-logic.vibe +14 -0
  202. package/tests/fixtures/function-call.vibe +16 -0
  203. package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
  204. package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
  205. package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
  206. package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
  207. package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
  208. package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
  209. package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
  210. package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
  211. package/tests/fixtures/imports/nested-import/main.vibe +3 -0
  212. package/tests/fixtures/imports/nested-import/utils.ts +3 -0
  213. package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
  214. package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
  215. package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
  216. package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
  217. package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
  218. package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
  219. package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
  220. package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
  221. package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
  222. package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
  223. package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
  224. package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
  225. package/tests/fixtures/imports/ts-import/main.vibe +4 -0
  226. package/tests/fixtures/imports/ts-import/math.ts +9 -0
  227. package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
  228. package/tests/fixtures/imports/ts-variables/data.ts +10 -0
  229. package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
  230. package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
  231. package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
  232. package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
  233. package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
  234. package/tests/fixtures/multiple-ai-calls.vibe +10 -0
  235. package/tests/fixtures/simple-greeting.vibe +6 -0
  236. package/tests/fixtures/template-literals.vibe +11 -0
  237. package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
  238. package/tests/integration/basic-ai/basic-ai.vibe +12 -0
  239. package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
  240. package/tests/integration/bug-fix/buggy-code.ts +22 -0
  241. package/tests/integration/bug-fix/fix-bug.vibe +21 -0
  242. package/tests/integration/compress/compress.integration.test.ts +206 -0
  243. package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
  244. package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
  245. package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
  246. package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
  247. package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
  248. package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
  249. package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
  250. package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
@@ -0,0 +1,466 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { parse } from '../../parser/parse';
3
+ import {
4
+ createInitialState,
5
+ step,
6
+ runUntilPause,
7
+ buildLocalContext,
8
+ buildGlobalContext,
9
+ stepUntilCondition,
10
+ type ContextVariable,
11
+ type ContextEntry,
12
+ } from '../index';
13
+
14
+ // Helper to filter context entries to only variables
15
+ function getVariables(entries: ContextEntry[]): ContextVariable[] {
16
+ return entries.filter((e): e is ContextVariable => e.kind === 'variable');
17
+ }
18
+
19
+ describe('Context Building Functions', () => {
20
+ test('buildLocalContext returns empty array for initial state', () => {
21
+ const ast = parse('let x = "hello"');
22
+ const state = createInitialState(ast);
23
+
24
+ const context = buildLocalContext(state);
25
+ expect(context).toEqual([]);
26
+ });
27
+
28
+ test('buildLocalContext returns variables from current frame with frame info', () => {
29
+ const ast = parse(`
30
+ let x = "hello"
31
+ const y: json = { key: "value" }
32
+ `);
33
+ let state = createInitialState(ast);
34
+ state = runUntilPause(state);
35
+
36
+ const context = buildLocalContext(state);
37
+ const variables = getVariables(context);
38
+ expect(variables).toHaveLength(2);
39
+
40
+ const xVar = variables.find((v) => v.name === 'x');
41
+ expect(xVar).toBeDefined();
42
+ expect(xVar?.value).toBe('hello');
43
+ expect(xVar?.type).toBe('text'); // Inferred from string value
44
+ expect(xVar?.frameName).toBe('<entry>');
45
+ expect(xVar?.frameDepth).toBe(0);
46
+
47
+ const yVar = variables.find((v) => v.name === 'y');
48
+ expect(yVar).toBeDefined();
49
+ expect(yVar?.value).toEqual({ key: 'value' });
50
+ expect(yVar?.type).toBe('json');
51
+ expect(yVar?.frameName).toBe('<entry>');
52
+ expect(yVar?.frameDepth).toBe(0);
53
+ });
54
+
55
+ test('buildGlobalContext returns empty array for initial state', () => {
56
+ const ast = parse('let x = "hello"');
57
+ const state = createInitialState(ast);
58
+
59
+ const context = buildGlobalContext(state);
60
+ expect(context).toEqual([]);
61
+ });
62
+
63
+ test('buildGlobalContext returns variables from all frames with frame info', () => {
64
+ const ast = parse(`
65
+ let outer = "outer value"
66
+ function inner(): text {
67
+ let innerVar = "inner value"
68
+ return innerVar
69
+ }
70
+ let result = inner()
71
+ `);
72
+ let state = createInitialState(ast);
73
+ state = runUntilPause(state);
74
+
75
+ // After completion, only main frame remains
76
+ const context = buildGlobalContext(state);
77
+ const variables = getVariables(context);
78
+
79
+ const outerVar = variables.find((v) => v.name === 'outer');
80
+ expect(outerVar).toBeDefined();
81
+ expect(outerVar?.frameName).toBe('<entry>');
82
+ expect(outerVar?.frameDepth).toBe(0);
83
+
84
+ const resultVar = variables.find((v) => v.name === 'result');
85
+ expect(resultVar).toBeDefined();
86
+ expect(resultVar?.frameName).toBe('<entry>');
87
+ expect(resultVar?.frameDepth).toBe(0);
88
+ });
89
+
90
+ test('context is rebuilt before each instruction in step()', () => {
91
+ const ast = parse(`
92
+ let a = "first"
93
+ let b = "second"
94
+ `);
95
+ let state = createInitialState(ast);
96
+
97
+ // Initial state - context should be empty
98
+ expect(state.localContext).toEqual([]);
99
+ expect(state.globalContext).toEqual([]);
100
+
101
+ // Step through until first variable is declared
102
+ while (state.status === 'running' && !state.callStack[0]?.locals['a']) {
103
+ state = step(state);
104
+ }
105
+
106
+ // After declaring 'a', step once more and context should reflect it
107
+ state = step(state);
108
+ expect(getVariables(state.localContext).some((v) => v.name === 'a')).toBe(true);
109
+ });
110
+
111
+ test('context includes correct type annotations', () => {
112
+ const ast = parse(`
113
+ let textVar = "hello"
114
+ let jsonVar: json = { data: "value" }
115
+ `);
116
+ let state = createInitialState(ast);
117
+ state = runUntilPause(state);
118
+
119
+ const context = buildLocalContext(state);
120
+ const variables = getVariables(context);
121
+
122
+ const textVar = variables.find((v) => v.name === 'textVar');
123
+ expect(textVar?.type).toBe('text'); // Inferred from string value
124
+
125
+ const jsonVar = variables.find((v) => v.name === 'jsonVar');
126
+ expect(jsonVar?.type).toBe('json');
127
+ });
128
+
129
+ test('context updates correctly after variable reassignment', () => {
130
+ const ast = parse(`
131
+ let x = "initial"
132
+ x = "updated"
133
+ `);
134
+ let state = createInitialState(ast);
135
+ state = runUntilPause(state);
136
+
137
+ // With snapshotting, context preserves history: both 'initial' and 'updated' entries
138
+ const context = buildLocalContext(state);
139
+ const xEntries = getVariables(context).filter((v) => v.name === 'x');
140
+ expect(xEntries).toHaveLength(2);
141
+ expect(xEntries[0]?.value).toBe('initial');
142
+ expect(xEntries[1]?.value).toBe('updated');
143
+ });
144
+
145
+ test('context works with nested function calls and shows frame depth', () => {
146
+ const ast = parse(`
147
+ let outer = "outer"
148
+ function getInner(): text {
149
+ let inner = "inner"
150
+ return inner
151
+ }
152
+ let result = getInner()
153
+ `);
154
+ let state = createInitialState(ast);
155
+
156
+ // Run until we're inside the function (have 2 frames)
157
+ while (state.status === 'running' && state.callStack.length < 2) {
158
+ state = step(state);
159
+ }
160
+
161
+ // If we got inside the function, check global context with frame depths
162
+ if (state.callStack.length >= 2) {
163
+ const globalCtx = buildGlobalContext(state);
164
+ const globalVars = getVariables(globalCtx);
165
+
166
+ // Outer should be from <entry> frame (depth 0 = entry)
167
+ const outerVar = globalVars.find((v) => v.name === 'outer');
168
+ expect(outerVar).toBeDefined();
169
+ expect(outerVar?.frameName).toBe('<entry>');
170
+ expect(outerVar?.frameDepth).toBe(0); // Entry frame is always depth 0
171
+ }
172
+
173
+ // Complete execution
174
+ state = runUntilPause(state);
175
+
176
+ // After completion, result should be set with correct frame info
177
+ const finalContext = buildLocalContext(state);
178
+ const resultVar = getVariables(finalContext).find((v) => v.name === 'result');
179
+ expect(resultVar?.value).toBe('inner');
180
+ expect(resultVar?.frameName).toBe('<entry>');
181
+ expect(resultVar?.frameDepth).toBe(0);
182
+ });
183
+
184
+ test('localContext and globalContext are stored in state', () => {
185
+ const ast = parse(`
186
+ let x = "value"
187
+ `);
188
+ let state = createInitialState(ast);
189
+
190
+ // Step through execution
191
+ while (state.status === 'running') {
192
+ state = step(state);
193
+ }
194
+
195
+ // State should have context properties
196
+ expect(Array.isArray(state.localContext)).toBe(true);
197
+ expect(Array.isArray(state.globalContext)).toBe(true);
198
+ });
199
+
200
+ test('context filters out model declarations', () => {
201
+ const ast = parse(`
202
+ model m = { name: "test", apiKey: "key", url: "http://test" }
203
+ let x = "hello"
204
+ `);
205
+ let state = createInitialState(ast);
206
+ state = runUntilPause(state);
207
+
208
+ const context = buildLocalContext(state);
209
+ const variables = getVariables(context);
210
+
211
+ // Model should be filtered out of context (it's config, not data for AI)
212
+ const modelVar = variables.find((v) => v.name === 'm');
213
+ expect(modelVar).toBeUndefined();
214
+
215
+ // Regular variable should still be in context
216
+ const xVar = variables.find((v) => v.name === 'x');
217
+ expect(xVar?.value).toBe('hello');
218
+ });
219
+
220
+ test('context filters out prompt type variables', () => {
221
+ const ast = parse(`
222
+ let systemPrompt: prompt = "You are a helpful assistant"
223
+ const question: prompt = "What is 2+2?"
224
+ let regularVar = "hello"
225
+ `);
226
+ let state = createInitialState(ast);
227
+ state = runUntilPause(state);
228
+
229
+ const context = buildLocalContext(state);
230
+ const variables = getVariables(context);
231
+
232
+ // Prompt variables should be filtered out (they are instructions, not data)
233
+ const promptVar = variables.find((v) => v.name === 'systemPrompt');
234
+ expect(promptVar).toBeUndefined();
235
+
236
+ const questionVar = variables.find((v) => v.name === 'question');
237
+ expect(questionVar).toBeUndefined();
238
+
239
+ // Regular variable should still be in context
240
+ const regularVarCtx = variables.find((v) => v.name === 'regularVar');
241
+ expect(regularVarCtx?.value).toBe('hello');
242
+
243
+ // Only the regular variable should be in context
244
+ expect(variables).toHaveLength(1);
245
+ });
246
+
247
+ test('full context array verification with nested blocks and function calls', () => {
248
+ const ast = parse(`
249
+ let outer = "outer_value"
250
+ const outerConst: json = { key: "json_value" }
251
+
252
+ function processData(input: text): text {
253
+ let funcLocal = "func_local"
254
+ if true {
255
+ let blockVar = "block_value"
256
+ blockVar = "updated_block"
257
+ }
258
+ return input
259
+ }
260
+
261
+ let result = processData("arg_value")
262
+ `);
263
+
264
+ let state = createInitialState(ast);
265
+
266
+ // Helper to normalize context for comparison (sort by name)
267
+ const normalizeContext = (ctx: ContextEntry[]) =>
268
+ [...getVariables(ctx)].sort((a, b) => a.name.localeCompare(b.name));
269
+
270
+ // Step until we're inside the function (2 frames)
271
+ state = stepUntilCondition(state, (s) => s.callStack.length >= 2);
272
+
273
+ // Continue stepping until funcLocal is declared
274
+ while (
275
+ state.status === 'running' &&
276
+ state.callStack.length >= 2 &&
277
+ !state.callStack[1]?.locals['funcLocal']
278
+ ) {
279
+ state = step(state);
280
+ }
281
+
282
+ // Take snapshot inside function, after funcLocal declared
283
+ if (state.callStack.length >= 2 && state.callStack[1]?.locals['funcLocal']) {
284
+ state = step(state); // Step once more to rebuild context
285
+
286
+ const localCtx = normalizeContext(buildLocalContext(state));
287
+ const globalCtx = normalizeContext(buildGlobalContext(state));
288
+
289
+ // Local context should only have function frame variables (depth 1 = called from entry)
290
+ // Function parameters now have explicit type annotations
291
+ expect(localCtx).toEqual([
292
+ { kind: 'variable', name: 'funcLocal', value: 'func_local', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
293
+ { kind: 'variable', name: 'input', value: 'arg_value', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
294
+ ]);
295
+
296
+ // Global context should have <entry> frame (depth 0) + function frame (depth 1)
297
+ expect(globalCtx).toEqual([
298
+ { kind: 'variable', name: 'funcLocal', value: 'func_local', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
299
+ { kind: 'variable', name: 'input', value: 'arg_value', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
300
+ { kind: 'variable', name: 'outer', value: 'outer_value', type: 'text', isConst: false, source: null, frameName: '<entry>', frameDepth: 0 },
301
+ { kind: 'variable', name: 'outerConst', value: { key: 'json_value' }, type: 'json', isConst: true, source: null, frameName: '<entry>', frameDepth: 0 },
302
+ ]);
303
+ }
304
+
305
+ // Continue stepping until we're inside the if block (blockVar declared)
306
+ while (
307
+ state.status === 'running' &&
308
+ state.callStack.length >= 2 &&
309
+ !state.callStack[1]?.locals['blockVar']
310
+ ) {
311
+ state = step(state);
312
+ }
313
+
314
+ // Take snapshot inside the nested block
315
+ if (state.callStack.length >= 2 && state.callStack[1]?.locals['blockVar']) {
316
+ state = step(state); // Step once more to rebuild context
317
+
318
+ const localCtxInBlock = normalizeContext(buildLocalContext(state));
319
+ const globalCtxInBlock = normalizeContext(buildGlobalContext(state));
320
+
321
+ // Local context now includes blockVar (blocks share frame with function)
322
+ // Function parameters now have explicit type annotations
323
+ expect(localCtxInBlock).toEqual([
324
+ { kind: 'variable', name: 'blockVar', value: 'block_value', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
325
+ { kind: 'variable', name: 'funcLocal', value: 'func_local', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
326
+ { kind: 'variable', name: 'input', value: 'arg_value', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
327
+ ]);
328
+
329
+ // Global context includes everything
330
+ expect(globalCtxInBlock).toEqual([
331
+ { kind: 'variable', name: 'blockVar', value: 'block_value', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
332
+ { kind: 'variable', name: 'funcLocal', value: 'func_local', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
333
+ { kind: 'variable', name: 'input', value: 'arg_value', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
334
+ { kind: 'variable', name: 'outer', value: 'outer_value', type: 'text', isConst: false, source: null, frameName: '<entry>', frameDepth: 0 },
335
+ { kind: 'variable', name: 'outerConst', value: { key: 'json_value' }, type: 'json', isConst: true, source: null, frameName: '<entry>', frameDepth: 0 },
336
+ ]);
337
+ }
338
+
339
+ // Continue until blockVar is updated
340
+ while (
341
+ state.status === 'running' &&
342
+ state.callStack.length >= 2 &&
343
+ state.callStack[1]?.locals['blockVar']?.value !== 'updated_block'
344
+ ) {
345
+ state = step(state);
346
+ }
347
+
348
+ // Verify updated value in context (check immediately, don't step again
349
+ // since next instruction might be exit_block which removes blockVar)
350
+ if (state.callStack[1]?.locals['blockVar']?.value === 'updated_block') {
351
+ const localCtxUpdated = normalizeContext(buildLocalContext(state));
352
+
353
+ // With snapshotting, blockVar has two entries - find the last one (updated value)
354
+ const blockVarEntries = localCtxUpdated.filter((v) => v.name === 'blockVar');
355
+ const lastBlockVar = blockVarEntries[blockVarEntries.length - 1];
356
+ expect(lastBlockVar?.value).toBe('updated_block');
357
+ }
358
+
359
+ // Run to completion
360
+ state = runUntilPause(state);
361
+ expect(state.status).toBe('completed');
362
+
363
+ // Final context - back to main frame only, block variables gone
364
+ const finalLocalCtx = normalizeContext(buildLocalContext(state));
365
+ const finalGlobalCtx = normalizeContext(buildGlobalContext(state));
366
+
367
+ // Should have outer, outerConst, and result (blockVar and funcLocal are gone)
368
+ // All in <entry> frame at depth 0 since it's the only frame
369
+ expect(finalLocalCtx).toEqual([
370
+ { kind: 'variable', name: 'outer', value: 'outer_value', type: 'text', isConst: false, source: null, frameName: '<entry>', frameDepth: 0 },
371
+ { kind: 'variable', name: 'outerConst', value: { key: 'json_value' }, type: 'json', isConst: true, source: null, frameName: '<entry>', frameDepth: 0 },
372
+ { kind: 'variable', name: 'result', value: 'arg_value', type: 'text', isConst: false, source: null, frameName: '<entry>', frameDepth: 0 },
373
+ ]);
374
+
375
+ // Local and global should be same when only one frame
376
+ expect(finalGlobalCtx).toEqual(finalLocalCtx);
377
+ });
378
+ });
379
+
380
+ describe('Tool Call Context Entries', () => {
381
+ test('tool-call entries in orderedEntries appear in context', () => {
382
+ const ast = parse('let x = "test"');
383
+ let state = createInitialState(ast);
384
+ state = runUntilPause(state);
385
+
386
+ // Add a tool-call entry directly to test buildLocalContext handles them
387
+ const frame = state.callStack[state.callStack.length - 1];
388
+ frame.orderedEntries.push({
389
+ kind: 'tool-call',
390
+ toolName: 'getWeather',
391
+ args: { city: 'Seattle' },
392
+ result: { temp: 55, condition: 'rainy' },
393
+ });
394
+
395
+ const context = buildLocalContext(state);
396
+
397
+ // Should have the variable and the tool call
398
+ expect(context).toHaveLength(2);
399
+
400
+ const toolCall = context.find((e) => e.kind === 'tool-call');
401
+ expect(toolCall).toBeDefined();
402
+ expect(toolCall?.kind).toBe('tool-call');
403
+ if (toolCall?.kind === 'tool-call') {
404
+ expect(toolCall.toolName).toBe('getWeather');
405
+ expect(toolCall.args).toEqual({ city: 'Seattle' });
406
+ expect(toolCall.result).toEqual({ temp: 55, condition: 'rainy' });
407
+ expect(toolCall.frameName).toBe('<entry>');
408
+ expect(toolCall.frameDepth).toBe(0);
409
+ }
410
+ });
411
+
412
+ test('tool-call entries with errors appear in context', () => {
413
+ const ast = parse('let x = "test"');
414
+ let state = createInitialState(ast);
415
+ state = runUntilPause(state);
416
+
417
+ // Add a failed tool call
418
+ const frame = state.callStack[state.callStack.length - 1];
419
+ frame.orderedEntries.push({
420
+ kind: 'tool-call',
421
+ toolName: 'readFile',
422
+ args: { path: '/nonexistent' },
423
+ error: 'File not found',
424
+ });
425
+
426
+ const context = buildLocalContext(state);
427
+
428
+ const toolCall = context.find((e) => e.kind === 'tool-call');
429
+ expect(toolCall).toBeDefined();
430
+ if (toolCall?.kind === 'tool-call') {
431
+ expect(toolCall.toolName).toBe('readFile');
432
+ expect(toolCall.error).toBe('File not found');
433
+ expect(toolCall.result).toBeUndefined();
434
+ }
435
+ });
436
+
437
+ test('multiple tool calls appear in order', () => {
438
+ const ast = parse('let x = "test"');
439
+ let state = createInitialState(ast);
440
+ state = runUntilPause(state);
441
+
442
+ // Add multiple tool calls
443
+ const frame = state.callStack[state.callStack.length - 1];
444
+ frame.orderedEntries.push(
445
+ {
446
+ kind: 'tool-call',
447
+ toolName: 'step1',
448
+ args: {},
449
+ result: 'done1',
450
+ },
451
+ {
452
+ kind: 'tool-call',
453
+ toolName: 'step2',
454
+ args: {},
455
+ result: 'done2',
456
+ }
457
+ );
458
+
459
+ const context = buildLocalContext(state);
460
+ const toolCalls = context.filter((e) => e.kind === 'tool-call');
461
+
462
+ expect(toolCalls).toHaveLength(2);
463
+ expect(toolCalls[0].kind === 'tool-call' && toolCalls[0].toolName).toBe('step1');
464
+ expect(toolCalls[1].kind === 'tool-call' && toolCalls[1].toolName).toBe('step2');
465
+ });
466
+ });
@@ -0,0 +1,228 @@
1
+ // Tests for auto-imported core functions (print, env)
2
+ // These functions are available everywhere without explicit import
3
+
4
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
5
+ import { parse } from '../../parser/parse';
6
+ import { Runtime } from '../index';
7
+ import type { AIProvider, AIResponse } from '../types';
8
+
9
+ // Mock provider for tests that don't need AI
10
+ function createMockProvider(): AIProvider {
11
+ return {
12
+ async chat(): Promise<AIResponse> {
13
+ return { content: 'mock response', toolCalls: [] };
14
+ },
15
+ };
16
+ }
17
+
18
+ describe('Core Functions - Auto-imported', () => {
19
+ describe('print()', () => {
20
+ test('print works at top level without import', async () => {
21
+ const ast = parse(`
22
+ let x = print("hello world")
23
+ let y = 42
24
+ `);
25
+ const runtime = new Runtime(ast, createMockProvider());
26
+ await runtime.run();
27
+ // print returns undefined, but shouldn't throw
28
+ expect(runtime.getValue('y')).toBe(42);
29
+ });
30
+
31
+ test('print works inside function without import', async () => {
32
+ const ast = parse(`
33
+ function logAndReturn(msg: text): text {
34
+ print(msg)
35
+ return msg
36
+ }
37
+ let result = logAndReturn("test message")
38
+ `);
39
+ const runtime = new Runtime(ast, createMockProvider());
40
+ await runtime.run();
41
+ expect(runtime.getValue('result')).toBe('test message');
42
+ });
43
+
44
+ test('print works with different value types', async () => {
45
+ const ast = parse(`
46
+ print("string")
47
+ print(42)
48
+ print(true)
49
+ print([1, 2, 3])
50
+ let done = true
51
+ `);
52
+ const runtime = new Runtime(ast, createMockProvider());
53
+ await runtime.run();
54
+ expect(runtime.getValue('done')).toBe(true);
55
+ });
56
+ });
57
+
58
+ describe('env()', () => {
59
+ beforeEach(() => {
60
+ process.env.CORE_TEST_VAR = 'test-value';
61
+ process.env.CORE_TEST_VAR2 = 'second-value';
62
+ });
63
+
64
+ afterEach(() => {
65
+ delete process.env.CORE_TEST_VAR;
66
+ delete process.env.CORE_TEST_VAR2;
67
+ });
68
+
69
+ test('env works at top level without import', async () => {
70
+ const ast = parse(`
71
+ let value = env("CORE_TEST_VAR")
72
+ `);
73
+ const runtime = new Runtime(ast, createMockProvider());
74
+ await runtime.run();
75
+ expect(runtime.getValue('value')).toBe('test-value');
76
+ });
77
+
78
+ test('env with default value when var not set', async () => {
79
+ const ast = parse(`
80
+ let value = env("NONEXISTENT_VAR_12345", "default-value")
81
+ `);
82
+ const runtime = new Runtime(ast, createMockProvider());
83
+ await runtime.run();
84
+ expect(runtime.getValue('value')).toBe('default-value');
85
+ });
86
+
87
+ test('env returns empty string for missing var without default', async () => {
88
+ const ast = parse(`
89
+ let value = env("NONEXISTENT_VAR_12345")
90
+ `);
91
+ const runtime = new Runtime(ast, createMockProvider());
92
+ await runtime.run();
93
+ expect(runtime.getValue('value')).toBe('');
94
+ });
95
+
96
+ test('env works inside function without import', async () => {
97
+ const ast = parse(`
98
+ function getEnvValue(name: text): text {
99
+ return env(name)
100
+ }
101
+ let value = getEnvValue("CORE_TEST_VAR")
102
+ `);
103
+ const runtime = new Runtime(ast, createMockProvider());
104
+ await runtime.run();
105
+ expect(runtime.getValue('value')).toBe('test-value');
106
+ });
107
+
108
+ test('env works in json object literal', async () => {
109
+ const ast = parse(`
110
+ let config: json = {
111
+ apiKey: env("CORE_TEST_VAR"),
112
+ other: env("CORE_TEST_VAR2")
113
+ }
114
+ `);
115
+ const runtime = new Runtime(ast, createMockProvider());
116
+ await runtime.run();
117
+ expect(runtime.getValue('config')).toEqual({
118
+ apiKey: 'test-value',
119
+ other: 'second-value',
120
+ });
121
+ });
122
+ });
123
+
124
+ describe('core functions in nested contexts', () => {
125
+ beforeEach(() => {
126
+ process.env.NESTED_TEST_VAR = 'nested-value';
127
+ });
128
+
129
+ afterEach(() => {
130
+ delete process.env.NESTED_TEST_VAR;
131
+ });
132
+
133
+ test('core functions work in chained function calls', async () => {
134
+ const ast = parse(`
135
+ function inner(): text {
136
+ return env("NESTED_TEST_VAR")
137
+ }
138
+
139
+ function outer(): text {
140
+ print("calling inner")
141
+ return inner()
142
+ }
143
+
144
+ let result = outer()
145
+ `);
146
+ const runtime = new Runtime(ast, createMockProvider());
147
+ await runtime.run();
148
+ expect(runtime.getValue('result')).toBe('nested-value');
149
+ });
150
+
151
+ test('core functions work in loops', async () => {
152
+ const ast = parse(`
153
+ let count = 0
154
+ for i in [1, 2, 3] {
155
+ print(env("NESTED_TEST_VAR"))
156
+ count = count + 1
157
+ }
158
+ `);
159
+ const runtime = new Runtime(ast, createMockProvider());
160
+ await runtime.run();
161
+ expect(runtime.getValue('count')).toBe(3);
162
+ });
163
+
164
+ test('core functions work in conditionals', async () => {
165
+ const ast = parse(`
166
+ let value = ""
167
+ if true {
168
+ value = env("NESTED_TEST_VAR")
169
+ print("got value: " + value)
170
+ }
171
+ `);
172
+ const runtime = new Runtime(ast, createMockProvider());
173
+ await runtime.run();
174
+ expect(runtime.getValue('value')).toBe('nested-value');
175
+ });
176
+ });
177
+
178
+ describe('core functions cannot be imported', () => {
179
+ test('importing env from system fails', async () => {
180
+ const ast = parse(`
181
+ import { env } from "system"
182
+ let x = 1
183
+ `);
184
+ const runtime = new Runtime(ast, createMockProvider());
185
+ await expect(runtime.run()).rejects.toThrow("'env' is not exported from 'system'");
186
+ });
187
+
188
+ test('importing print from system fails', async () => {
189
+ const ast = parse(`
190
+ import { print } from "system"
191
+ let x = 1
192
+ `);
193
+ const runtime = new Runtime(ast, createMockProvider());
194
+ await expect(runtime.run()).rejects.toThrow("'print' is not exported from 'system'");
195
+ });
196
+
197
+ test('importing from system/core is blocked', async () => {
198
+ const ast = parse(`
199
+ import { env } from "system/core"
200
+ let x = 1
201
+ `);
202
+ const runtime = new Runtime(ast, createMockProvider());
203
+ await expect(runtime.run()).rejects.toThrow("'system/core' cannot be imported");
204
+ });
205
+ });
206
+
207
+ describe('library functions still require import', () => {
208
+ test('uuid requires import from system', async () => {
209
+ const ast = parse(`
210
+ let id = uuid()
211
+ `);
212
+ const runtime = new Runtime(ast, createMockProvider());
213
+ await expect(runtime.run()).rejects.toThrow("'uuid' is not defined");
214
+ });
215
+
216
+ test('uuid works when imported from system', async () => {
217
+ const ast = parse(`
218
+ import { uuid } from "system"
219
+ let id = uuid()
220
+ `);
221
+ const runtime = new Runtime(ast, createMockProvider());
222
+ await runtime.run();
223
+ const id = runtime.getValue('id') as string;
224
+ expect(typeof id).toBe('string');
225
+ expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
226
+ });
227
+ });
228
+ });