@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,650 @@
1
+ // Tests for context modes: forget, verbose, compress
2
+ // These tests verify that context is correctly managed on loop/function exit
3
+
4
+ import { describe, test, expect } from 'bun:test';
5
+ import { parse } from '../../parser/parse';
6
+ import { createInitialState, resumeWithCompressResult } from '../state';
7
+ import { step, stepN } from '../step';
8
+ import { buildLocalContext, formatEntriesForSummarization } from '../context';
9
+ import { formatContextForAI } from '../context';
10
+ import type { RuntimeState } from '../types';
11
+
12
+ // Helper to run until pause or completion
13
+ function runUntilPause(state: RuntimeState, maxSteps = 1000): RuntimeState {
14
+ let current = state;
15
+ let steps = 0;
16
+ while (current.status === 'running' && steps < maxSteps) {
17
+ current = step(current);
18
+ steps++;
19
+ }
20
+ return current;
21
+ }
22
+
23
+ describe('Context Modes - Parsing', () => {
24
+ test('for loop with forget keyword parses correctly', () => {
25
+ const ast = parse(`
26
+ for i in [1, 2, 3] {
27
+ let x = i
28
+ } forget
29
+ `);
30
+ expect(ast.body[0].type).toBe('ForInStatement');
31
+ const forStmt = ast.body[0] as { type: 'ForInStatement'; contextMode?: string };
32
+ expect(forStmt.contextMode).toBe('forget');
33
+ });
34
+
35
+ test('for loop with verbose keyword parses correctly', () => {
36
+ const ast = parse(`
37
+ for i in [1, 2, 3] {
38
+ let x = i
39
+ } verbose
40
+ `);
41
+ const forStmt = ast.body[0] as { type: 'ForInStatement'; contextMode?: string };
42
+ expect(forStmt.contextMode).toBe('verbose');
43
+ });
44
+
45
+ test('for loop with compress keyword parses correctly', () => {
46
+ const ast = parse(`
47
+ for i in [1, 2, 3] {
48
+ let x = i
49
+ } compress
50
+ `);
51
+ const forStmt = ast.body[0] as { type: 'ForInStatement'; contextMode?: unknown };
52
+ expect(forStmt.contextMode).toEqual({ compress: { arg1: null, arg2: null } });
53
+ });
54
+
55
+ test('for loop with compress and prompt parses correctly', () => {
56
+ const ast = parse(`
57
+ for i in [1, 2, 3] {
58
+ let x = i
59
+ } compress("summarize the results")
60
+ `);
61
+ const forStmt = ast.body[0] as { type: 'ForInStatement'; contextMode?: unknown };
62
+ expect(forStmt.contextMode).toEqual({ compress: { arg1: { kind: 'literal', value: 'summarize the results' }, arg2: null } });
63
+ });
64
+
65
+ test('while loop with forget keyword parses correctly', () => {
66
+ const ast = parse(`
67
+ let i = 0
68
+ while (i < 3) {
69
+ i = i + 1
70
+ } forget
71
+ `);
72
+ const whileStmt = ast.body[1] as { type: 'WhileStatement'; contextMode?: string };
73
+ expect(whileStmt.contextMode).toBe('forget');
74
+ });
75
+
76
+ test('while loop with verbose keyword parses correctly', () => {
77
+ const ast = parse(`
78
+ let i = 0
79
+ while (i < 3) {
80
+ i = i + 1
81
+ } verbose
82
+ `);
83
+ const whileStmt = ast.body[1] as { type: 'WhileStatement'; contextMode?: string };
84
+ expect(whileStmt.contextMode).toBe('verbose');
85
+ });
86
+
87
+ test('while loop with compress keyword parses correctly', () => {
88
+ const ast = parse(`
89
+ let i = 0
90
+ while (i < 3) {
91
+ i = i + 1
92
+ } compress("summarize iterations")
93
+ `);
94
+ const whileStmt = ast.body[1] as { type: 'WhileStatement'; contextMode?: unknown };
95
+ expect(whileStmt.contextMode).toEqual({ compress: { arg1: { kind: 'literal', value: 'summarize iterations' }, arg2: null } });
96
+ });
97
+
98
+ test('compress with model identifier parses correctly', () => {
99
+ const ast = parse(`
100
+ for i in [1, 2, 3] {
101
+ let x = i
102
+ } compress(myModel)
103
+ `);
104
+ const forStmt = ast.body[0] as { type: 'ForInStatement'; contextMode?: unknown };
105
+ expect(forStmt.contextMode).toEqual({ compress: { arg1: { kind: 'identifier', name: 'myModel' }, arg2: null } });
106
+ });
107
+
108
+ test('compress with prompt literal and model identifier parses correctly', () => {
109
+ const ast = parse(`
110
+ for i in [1, 2, 3] {
111
+ let x = i
112
+ } compress("summarize", myModel)
113
+ `);
114
+ const forStmt = ast.body[0] as { type: 'ForInStatement'; contextMode?: unknown };
115
+ expect(forStmt.contextMode).toEqual({
116
+ compress: {
117
+ arg1: { kind: 'literal', value: 'summarize' },
118
+ arg2: { kind: 'identifier', name: 'myModel' },
119
+ },
120
+ });
121
+ });
122
+
123
+ test('compress with two identifiers parses correctly', () => {
124
+ const ast = parse(`
125
+ for i in [1, 2, 3] {
126
+ let x = i
127
+ } compress(SUMMARY_PROMPT, myModel)
128
+ `);
129
+ const forStmt = ast.body[0] as { type: 'ForInStatement'; contextMode?: unknown };
130
+ expect(forStmt.contextMode).toEqual({
131
+ compress: {
132
+ arg1: { kind: 'identifier', name: 'SUMMARY_PROMPT' },
133
+ arg2: { kind: 'identifier', name: 'myModel' },
134
+ },
135
+ });
136
+ });
137
+
138
+ test('loop without context mode defaults to verbose', () => {
139
+ const ast = parse(`
140
+ for i in [1, 2, 3] {
141
+ let x = i
142
+ }
143
+ `);
144
+ const forStmt = ast.body[0] as { type: 'ForInStatement'; contextMode?: unknown };
145
+ expect(forStmt.contextMode).toBe('verbose');
146
+ });
147
+ });
148
+
149
+ describe('Context Modes - Scope Markers', () => {
150
+ test('for loop adds scope-enter marker at start', () => {
151
+ const ast = parse(`
152
+ let outer = "before"
153
+ for i in [1, 2] {
154
+ let x = i
155
+ }
156
+ `);
157
+ let state = createInitialState(ast);
158
+
159
+ // Run until we're inside the loop (after first iteration starts)
160
+ state = stepN(state, 20);
161
+
162
+ const context = buildLocalContext(state);
163
+ const scopeEnter = context.find(e => e.kind === 'scope-enter');
164
+ expect(scopeEnter).toBeDefined();
165
+ expect(scopeEnter?.kind).toBe('scope-enter');
166
+ if (scopeEnter?.kind === 'scope-enter') {
167
+ expect(scopeEnter.scopeType).toBe('for');
168
+ }
169
+ });
170
+
171
+ test('for loop with verbose adds scope-exit marker at end', () => {
172
+ const ast = parse(`
173
+ for i in [1, 2] {
174
+ let x = i
175
+ } verbose
176
+ let after = "done"
177
+ `);
178
+ let state = createInitialState(ast);
179
+ state = runUntilPause(state);
180
+
181
+ const context = buildLocalContext(state);
182
+ const scopeExit = context.find(e => e.kind === 'scope-exit');
183
+ expect(scopeExit).toBeDefined();
184
+ expect(scopeExit?.kind).toBe('scope-exit');
185
+ if (scopeExit?.kind === 'scope-exit') {
186
+ expect(scopeExit.scopeType).toBe('for');
187
+ }
188
+ });
189
+ });
190
+
191
+ describe('Context Modes - Forget Mode', () => {
192
+ test('for loop with forget clears all loop entries on exit', () => {
193
+ const ast = parse(`
194
+ let outer = "before"
195
+ for i in [1, 2] {
196
+ let x = i
197
+ } forget
198
+ let after = "done"
199
+ `);
200
+ let state = createInitialState(ast);
201
+ state = runUntilPause(state);
202
+
203
+ const context = buildLocalContext(state);
204
+
205
+ // Should have outer and after, but no scope markers or loop variables
206
+ const varNames = context.filter(e => e.kind === 'variable').map(e => (e as { name: string }).name);
207
+ expect(varNames).toContain('outer');
208
+ expect(varNames).toContain('after');
209
+
210
+ // Should NOT have scope-enter or scope-exit markers (forget removes them)
211
+ const scopeMarkers = context.filter(e => e.kind === 'scope-enter' || e.kind === 'scope-exit');
212
+ expect(scopeMarkers).toHaveLength(0);
213
+ });
214
+ });
215
+
216
+ describe('Context Modes - Verbose Mode', () => {
217
+ test('for loop with verbose preserves all history', () => {
218
+ const ast = parse(`
219
+ let outer = "before"
220
+ for i in [1, 2] {
221
+ let x = i
222
+ } verbose
223
+ let after = "done"
224
+ `);
225
+ let state = createInitialState(ast);
226
+ state = runUntilPause(state);
227
+
228
+ const context = buildLocalContext(state);
229
+
230
+ // Should have scope markers
231
+ const scopeEnter = context.find(e => e.kind === 'scope-enter');
232
+ const scopeExit = context.find(e => e.kind === 'scope-exit');
233
+ expect(scopeEnter).toBeDefined();
234
+ expect(scopeExit).toBeDefined();
235
+
236
+ // Should have multiple entries for i and x (each iteration)
237
+ const iEntries = context.filter(e => e.kind === 'variable' && (e as { name: string }).name === 'i');
238
+ const xEntries = context.filter(e => e.kind === 'variable' && (e as { name: string }).name === 'x');
239
+ expect(iEntries.length).toBeGreaterThanOrEqual(2);
240
+ expect(xEntries.length).toBeGreaterThanOrEqual(2);
241
+ });
242
+ });
243
+
244
+ describe('Context Modes - Value Snapshotting', () => {
245
+ test('loop iterations preserve snapshotted values', () => {
246
+ const ast = parse(`
247
+ for i in [10, 20, 30] {
248
+ let x = i
249
+ } verbose
250
+ `);
251
+ let state = createInitialState(ast);
252
+ state = runUntilPause(state);
253
+
254
+ const context = buildLocalContext(state);
255
+ const iEntries = context.filter(e => e.kind === 'variable' && (e as { name: string }).name === 'i');
256
+
257
+ // Each iteration should have snapshotted the value at that time
258
+ const iValues = iEntries.map(e => (e as { value: unknown }).value);
259
+ expect(iValues).toContain(10);
260
+ expect(iValues).toContain(20);
261
+ expect(iValues).toContain(30);
262
+ });
263
+
264
+ test('variable reassignment creates new entry with snapshotted value', () => {
265
+ const ast = parse(`
266
+ let x = 1
267
+ x = 2
268
+ x = 3
269
+ `);
270
+ let state = createInitialState(ast);
271
+ state = runUntilPause(state);
272
+
273
+ const context = buildLocalContext(state);
274
+ const xEntries = context.filter(e => e.kind === 'variable' && (e as { name: string }).name === 'x');
275
+
276
+ // Should have 3 entries: 1, 2, 3
277
+ expect(xEntries).toHaveLength(3);
278
+ const xValues = xEntries.map(e => (e as { value: unknown }).value);
279
+ expect(xValues).toEqual([1, 2, 3]);
280
+ });
281
+ });
282
+
283
+ describe('Context Modes - Default Behavior', () => {
284
+ test('for loop without explicit mode defaults to verbose', () => {
285
+ const ast = parse(`
286
+ for i in [1, 2] {
287
+ let x = i
288
+ }
289
+ let after = "done"
290
+ `);
291
+ let state = createInitialState(ast);
292
+ state = runUntilPause(state);
293
+
294
+ const context = buildLocalContext(state);
295
+
296
+ // Default should be verbose, so scope markers should be present
297
+ const scopeExit = context.find(e => e.kind === 'scope-exit');
298
+ expect(scopeExit).toBeDefined();
299
+ });
300
+
301
+ test('while loop without explicit mode defaults to verbose', () => {
302
+ const ast = parse(`
303
+ let i = 0
304
+ while (i < 2) {
305
+ i = i + 1
306
+ }
307
+ let after = "done"
308
+ `);
309
+ let state = createInitialState(ast);
310
+ state = runUntilPause(state);
311
+
312
+ const context = buildLocalContext(state);
313
+
314
+ // Default should be verbose, so scope markers should be present
315
+ const scopeExit = context.find(e => e.kind === 'scope-exit');
316
+ expect(scopeExit).toBeDefined();
317
+ });
318
+ });
319
+
320
+ describe('Context Modes - While Loop Runtime', () => {
321
+ test('while loop with forget clears all loop entries on exit', () => {
322
+ const ast = parse(`
323
+ let outer = "before"
324
+ let i = 0
325
+ while (i < 2) {
326
+ let x = i
327
+ i = i + 1
328
+ } forget
329
+ let after = "done"
330
+ `);
331
+ let state = createInitialState(ast);
332
+ state = runUntilPause(state);
333
+
334
+ const context = buildLocalContext(state);
335
+
336
+ // Should have outer, i (initial), after - but no scope markers
337
+ const varNames = context.filter(e => e.kind === 'variable').map(e => (e as { name: string }).name);
338
+ expect(varNames).toContain('outer');
339
+ expect(varNames).toContain('after');
340
+
341
+ // Should NOT have scope-enter or scope-exit markers (forget removes them)
342
+ const scopeMarkers = context.filter(e => e.kind === 'scope-enter' || e.kind === 'scope-exit');
343
+ expect(scopeMarkers).toHaveLength(0);
344
+ });
345
+
346
+ test('while loop with verbose preserves all history', () => {
347
+ const ast = parse(`
348
+ let outer = "before"
349
+ let i = 0
350
+ while (i < 2) {
351
+ i = i + 1
352
+ } verbose
353
+ let after = "done"
354
+ `);
355
+ let state = createInitialState(ast);
356
+ state = runUntilPause(state);
357
+
358
+ const context = buildLocalContext(state);
359
+
360
+ // Should have scope markers
361
+ const scopeEnter = context.find(e => e.kind === 'scope-enter');
362
+ const scopeExit = context.find(e => e.kind === 'scope-exit');
363
+ expect(scopeEnter).toBeDefined();
364
+ expect(scopeExit).toBeDefined();
365
+
366
+ if (scopeEnter?.kind === 'scope-enter') {
367
+ expect(scopeEnter.scopeType).toBe('while');
368
+ }
369
+ if (scopeExit?.kind === 'scope-exit') {
370
+ expect(scopeExit.scopeType).toBe('while');
371
+ }
372
+
373
+ // Should have multiple entries for i (each iteration assignment)
374
+ const iEntries = context.filter(e => e.kind === 'variable' && (e as { name: string }).name === 'i');
375
+ expect(iEntries.length).toBeGreaterThanOrEqual(2);
376
+ });
377
+
378
+ test('while loop with compress pauses for AI summarization', () => {
379
+ const ast = parse(`
380
+ model m = { name: "test", apiKey: "key", url: "http://test" }
381
+ let i = 0
382
+ while (i < 2) {
383
+ i = i + 1
384
+ } compress("summarize")
385
+ let after = "done"
386
+ `);
387
+ let state = createInitialState(ast);
388
+ state = runUntilPause(state);
389
+
390
+ // Compress pauses for AI summarization
391
+ expect(state.status).toBe('awaiting_compress');
392
+ expect(state.pendingCompress).toBeDefined();
393
+ expect(state.pendingCompress?.prompt).toBe('summarize');
394
+ expect(state.pendingCompress?.model).toBe('m');
395
+ expect(state.pendingCompress?.scopeType).toBe('while');
396
+ });
397
+ });
398
+
399
+ describe('Context Modes - For Loop All Modes', () => {
400
+ test('for loop with compress pauses for AI summarization', () => {
401
+ const ast = parse(`
402
+ model m = { name: "test", apiKey: "key", url: "http://test" }
403
+ for i in [1, 2] {
404
+ let x = i
405
+ } compress
406
+ let after = "done"
407
+ `);
408
+ let state = createInitialState(ast);
409
+ state = runUntilPause(state);
410
+
411
+ // Compress pauses for AI summarization (uses lastUsedModel from model declaration)
412
+ expect(state.status).toBe('awaiting_compress');
413
+ expect(state.pendingCompress).toBeDefined();
414
+ expect(state.pendingCompress?.prompt).toBeNull(); // No custom prompt
415
+ expect(state.pendingCompress?.model).toBe('m');
416
+ expect(state.pendingCompress?.scopeType).toBe('for');
417
+ });
418
+
419
+ test('for loop with compress and explicit model', () => {
420
+ const ast = parse(`
421
+ model gpt = { name: "gpt-4", apiKey: "key1", url: "http://test1" }
422
+ model claude = { name: "claude", apiKey: "key2", url: "http://test2" }
423
+ for i in [1, 2] {
424
+ let x = i
425
+ } compress(claude)
426
+ let after = "done"
427
+ `);
428
+ let state = createInitialState(ast);
429
+ state = runUntilPause(state);
430
+
431
+ // Compress uses explicit model
432
+ expect(state.status).toBe('awaiting_compress');
433
+ expect(state.pendingCompress?.model).toBe('claude');
434
+ });
435
+
436
+ test('for loop with compress prompt and model', () => {
437
+ const ast = parse(`
438
+ model m = { name: "test", apiKey: "key", url: "http://test" }
439
+ for i in [1, 2] {
440
+ let x = i
441
+ } compress("summarize results", m)
442
+ let after = "done"
443
+ `);
444
+ let state = createInitialState(ast);
445
+ state = runUntilPause(state);
446
+
447
+ expect(state.status).toBe('awaiting_compress');
448
+ expect(state.pendingCompress?.prompt).toBe('summarize results');
449
+ expect(state.pendingCompress?.model).toBe('m');
450
+ });
451
+ });
452
+
453
+ // Note: Function context modes are parsed but not yet applied at runtime
454
+ // because function scope handling differs from loop scope handling.
455
+ // The context mode field is available on FunctionDeclaration for future implementation.
456
+
457
+ describe('Context Modes - Formatted Output', () => {
458
+ test('for loop verbose shows scope markers and all iterations in formatted output', () => {
459
+ const ast = parse(`
460
+ let outer = "before"
461
+ for i in [1, 2] {
462
+ let x = i
463
+ } verbose
464
+ let after = "done"
465
+ `);
466
+ let state = createInitialState(ast);
467
+ state = runUntilPause(state);
468
+
469
+ const context = buildLocalContext(state);
470
+ const formatted = formatContextForAI(context, { includeInstructions: false });
471
+
472
+ expect(formatted.text).toBe(
473
+ ` <entry> (current scope)
474
+ - outer (text): before
475
+ ==> for i
476
+ - i (number): 1
477
+ - x (number): 1
478
+ - i (number): 2
479
+ - x (number): 2
480
+ <== for i
481
+ - after (text): done`
482
+ );
483
+ });
484
+
485
+ test('for loop forget shows no loop entries in formatted output', () => {
486
+ const ast = parse(`
487
+ let outer = "before"
488
+ for i in [1, 2] {
489
+ let x = i
490
+ } forget
491
+ let after = "done"
492
+ `);
493
+ let state = createInitialState(ast);
494
+ state = runUntilPause(state);
495
+
496
+ const context = buildLocalContext(state);
497
+ const formatted = formatContextForAI(context, { includeInstructions: false });
498
+
499
+ // Should only have outer and after - no loop entries or scope markers
500
+ expect(formatted.text).toBe(
501
+ ` <entry> (current scope)
502
+ - outer (text): before
503
+ - after (text): done`
504
+ );
505
+ });
506
+
507
+ test('while loop verbose shows scope markers in formatted output', () => {
508
+ const ast = parse(`
509
+ let count = 0
510
+ while (count < 2) {
511
+ count = count + 1
512
+ } verbose
513
+ let done = "yes"
514
+ `);
515
+ let state = createInitialState(ast);
516
+ state = runUntilPause(state);
517
+
518
+ const context = buildLocalContext(state);
519
+ const formatted = formatContextForAI(context, { includeInstructions: false });
520
+
521
+ expect(formatted.text).toBe(
522
+ ` <entry> (current scope)
523
+ - count (number): 0
524
+ ==> while
525
+ - count (number): 1
526
+ - count (number): 2
527
+ <== while
528
+ - done (text): yes`
529
+ );
530
+ });
531
+
532
+ test('while loop forget shows no loop entries in formatted output', () => {
533
+ const ast = parse(`
534
+ let count = 0
535
+ while (count < 2) {
536
+ count = count + 1
537
+ } forget
538
+ let done = "yes"
539
+ `);
540
+ let state = createInitialState(ast);
541
+ state = runUntilPause(state);
542
+
543
+ const context = buildLocalContext(state);
544
+ const formatted = formatContextForAI(context, { includeInstructions: false });
545
+
546
+ // Should only have initial count and done - no loop iterations or scope markers
547
+ expect(formatted.text).toBe(
548
+ ` <entry> (current scope)
549
+ - count (number): 0
550
+ - done (text): yes`
551
+ );
552
+ });
553
+
554
+ test('nested loops with different modes show correct formatted output', () => {
555
+ const ast = parse(`
556
+ let result = 0
557
+ for i in [1, 2] {
558
+ for j in [10, 20] {
559
+ result = result + 1
560
+ } forget
561
+ } verbose
562
+ let final = result
563
+ `);
564
+ let state = createInitialState(ast);
565
+ state = runUntilPause(state);
566
+
567
+ const context = buildLocalContext(state);
568
+ const formatted = formatContextForAI(context, { includeInstructions: false });
569
+
570
+ // Outer loop (verbose) should show markers and i values
571
+ // Inner loop (forget) should not show j values or its markers
572
+ expect(formatted.text).toContain('==> for i');
573
+ expect(formatted.text).toContain('<== for i');
574
+ expect(formatted.text).toContain('- i (number): 1');
575
+ expect(formatted.text).toContain('- i (number): 2');
576
+ // Inner loop entries should be forgotten
577
+ expect(formatted.text).not.toContain('- j');
578
+ });
579
+ });
580
+
581
+ describe('Compress Resume Flow', () => {
582
+ test('resumeWithCompressResult replaces entries with summary', () => {
583
+ const ast = parse(`
584
+ model m = { name: "test", apiKey: "key", url: "http://test" }
585
+ for i in [1, 2, 3] {
586
+ let x = i
587
+ } compress
588
+ let after = "done"
589
+ `);
590
+ let state = createInitialState(ast);
591
+ state = runUntilPause(state);
592
+
593
+ // Should be awaiting_compress
594
+ expect(state.status).toBe('awaiting_compress');
595
+ expect(state.pendingCompress).toBeDefined();
596
+
597
+ // Resume with a summary
598
+ state = resumeWithCompressResult(state, 'Loop processed items 1, 2, 3');
599
+
600
+ // Should be running again
601
+ expect(state.status).toBe('running');
602
+ expect(state.pendingCompress).toBeNull();
603
+
604
+ // Run to completion
605
+ state = runUntilPause(state);
606
+ expect(state.status).toBe('completed');
607
+
608
+ // Context should have the summary
609
+ const context = buildLocalContext(state);
610
+ const summaryEntry = context.find(e => e.kind === 'summary');
611
+ expect(summaryEntry).toBeDefined();
612
+ expect((summaryEntry as { text: string }).text).toBe('Loop processed items 1, 2, 3');
613
+ });
614
+
615
+ test('formatEntriesForSummarization formats entries correctly', () => {
616
+ const ast = parse(`
617
+ model m = { name: "test", apiKey: "key", url: "http://test" }
618
+ for i in [1, 2] {
619
+ let x = i * 10
620
+ } compress
621
+ `);
622
+ let state = createInitialState(ast);
623
+ state = runUntilPause(state);
624
+
625
+ expect(state.status).toBe('awaiting_compress');
626
+ const entries = state.pendingCompress?.entriesToSummarize ?? [];
627
+
628
+ const formatted = formatEntriesForSummarization(entries);
629
+
630
+ // Should include loop entries
631
+ expect(formatted).toContain('for (i) started');
632
+ expect(formatted).toContain('Variable i');
633
+ expect(formatted).toContain('Variable x');
634
+ });
635
+
636
+ test('compress with empty loop skips summarization', () => {
637
+ const ast = parse(`
638
+ model m = { name: "test", apiKey: "key", url: "http://test" }
639
+ for i in [] {
640
+ let x = i
641
+ } compress
642
+ let after = "done"
643
+ `);
644
+ let state = createInitialState(ast);
645
+ state = runUntilPause(state);
646
+
647
+ // Empty loop should complete without awaiting compress
648
+ expect(state.status).toBe('completed');
649
+ });
650
+ });