@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.
- package/package.json +46 -0
- package/src/ast/index.ts +375 -0
- package/src/ast.ts +2 -0
- package/src/debug/advanced-features.ts +482 -0
- package/src/debug/bun-inspector.ts +424 -0
- package/src/debug/handoff-manager.ts +283 -0
- package/src/debug/index.ts +150 -0
- package/src/debug/runner.ts +365 -0
- package/src/debug/server.ts +565 -0
- package/src/debug/stack-merger.ts +267 -0
- package/src/debug/state.ts +581 -0
- package/src/debug/test/advanced-features.test.ts +300 -0
- package/src/debug/test/e2e.test.ts +218 -0
- package/src/debug/test/handoff-manager.test.ts +256 -0
- package/src/debug/test/runner.test.ts +256 -0
- package/src/debug/test/stack-merger.test.ts +163 -0
- package/src/debug/test/state.test.ts +400 -0
- package/src/debug/test/ts-debug-integration.test.ts +374 -0
- package/src/debug/test/ts-import-tracker.test.ts +125 -0
- package/src/debug/test/ts-source-map.test.ts +169 -0
- package/src/debug/ts-import-tracker.ts +151 -0
- package/src/debug/ts-source-map.ts +171 -0
- package/src/errors/index.ts +124 -0
- package/src/index.ts +358 -0
- package/src/lexer/index.ts +348 -0
- package/src/lexer.ts +2 -0
- package/src/parser/index.ts +792 -0
- package/src/parser/parse.ts +45 -0
- package/src/parser/test/async.test.ts +248 -0
- package/src/parser/test/destructuring.test.ts +167 -0
- package/src/parser/test/do-expression.test.ts +486 -0
- package/src/parser/test/errors/do-expression.test.ts +95 -0
- package/src/parser/test/errors/error-locations.test.ts +230 -0
- package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
- package/src/parser/test/errors/missing-tokens.test.ts +126 -0
- package/src/parser/test/errors/model-declaration.test.ts +185 -0
- package/src/parser/test/errors/nested-blocks.test.ts +226 -0
- package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
- package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
- package/src/parser/test/import-export.test.ts +143 -0
- package/src/parser/test/literals.test.ts +404 -0
- package/src/parser/test/model-declaration.test.ts +161 -0
- package/src/parser/test/nested-blocks.test.ts +402 -0
- package/src/parser/test/parser.test.ts +743 -0
- package/src/parser/test/private.test.ts +136 -0
- package/src/parser/test/template-literal.test.ts +127 -0
- package/src/parser/test/tool-declaration.test.ts +302 -0
- package/src/parser/test/ts-block.test.ts +252 -0
- package/src/parser/test/type-annotations.test.ts +254 -0
- package/src/parser/visitor/helpers.ts +330 -0
- package/src/parser/visitor.ts +794 -0
- package/src/parser.ts +2 -0
- package/src/runtime/ai/cache-chunking.test.ts +69 -0
- package/src/runtime/ai/cache-chunking.ts +73 -0
- package/src/runtime/ai/client.ts +109 -0
- package/src/runtime/ai/context.ts +168 -0
- package/src/runtime/ai/formatters.ts +316 -0
- package/src/runtime/ai/index.ts +38 -0
- package/src/runtime/ai/language-ref.ts +38 -0
- package/src/runtime/ai/providers/anthropic.ts +253 -0
- package/src/runtime/ai/providers/google.ts +201 -0
- package/src/runtime/ai/providers/openai.ts +156 -0
- package/src/runtime/ai/retry.ts +100 -0
- package/src/runtime/ai/return-tools.ts +301 -0
- package/src/runtime/ai/test/client.test.ts +83 -0
- package/src/runtime/ai/test/formatters.test.ts +485 -0
- package/src/runtime/ai/test/retry.test.ts +137 -0
- package/src/runtime/ai/test/return-tools.test.ts +450 -0
- package/src/runtime/ai/test/tool-loop.test.ts +319 -0
- package/src/runtime/ai/test/tool-schema.test.ts +241 -0
- package/src/runtime/ai/tool-loop.ts +203 -0
- package/src/runtime/ai/tool-schema.ts +151 -0
- package/src/runtime/ai/types.ts +113 -0
- package/src/runtime/ai-logger.ts +255 -0
- package/src/runtime/ai-provider.ts +347 -0
- package/src/runtime/async/dependencies.ts +276 -0
- package/src/runtime/async/executor.ts +293 -0
- package/src/runtime/async/index.ts +43 -0
- package/src/runtime/async/scheduling.ts +163 -0
- package/src/runtime/async/test/dependencies.test.ts +284 -0
- package/src/runtime/async/test/executor.test.ts +388 -0
- package/src/runtime/context.ts +357 -0
- package/src/runtime/exec/ai.ts +139 -0
- package/src/runtime/exec/expressions.ts +475 -0
- package/src/runtime/exec/frames.ts +26 -0
- package/src/runtime/exec/functions.ts +305 -0
- package/src/runtime/exec/interpolation.ts +312 -0
- package/src/runtime/exec/statements.ts +604 -0
- package/src/runtime/exec/tools.ts +129 -0
- package/src/runtime/exec/typescript.ts +215 -0
- package/src/runtime/exec/variables.ts +279 -0
- package/src/runtime/index.ts +975 -0
- package/src/runtime/modules.ts +452 -0
- package/src/runtime/serialize.ts +103 -0
- package/src/runtime/state.ts +489 -0
- package/src/runtime/stdlib/core.ts +45 -0
- package/src/runtime/stdlib/directory.test.ts +156 -0
- package/src/runtime/stdlib/edit.test.ts +154 -0
- package/src/runtime/stdlib/fastEdit.test.ts +201 -0
- package/src/runtime/stdlib/glob.test.ts +106 -0
- package/src/runtime/stdlib/grep.test.ts +144 -0
- package/src/runtime/stdlib/index.ts +16 -0
- package/src/runtime/stdlib/readFile.test.ts +123 -0
- package/src/runtime/stdlib/tools/index.ts +707 -0
- package/src/runtime/stdlib/writeFile.test.ts +157 -0
- package/src/runtime/step.ts +969 -0
- package/src/runtime/test/ai-context.test.ts +1086 -0
- package/src/runtime/test/ai-result-object.test.ts +419 -0
- package/src/runtime/test/ai-tool-flow.test.ts +859 -0
- package/src/runtime/test/async-execution-order.test.ts +618 -0
- package/src/runtime/test/async-execution.test.ts +344 -0
- package/src/runtime/test/async-nested.test.ts +660 -0
- package/src/runtime/test/async-parallel-timing.test.ts +546 -0
- package/src/runtime/test/basic1.test.ts +154 -0
- package/src/runtime/test/binary-operators.test.ts +431 -0
- package/src/runtime/test/break-statement.test.ts +257 -0
- package/src/runtime/test/context-modes.test.ts +650 -0
- package/src/runtime/test/context.test.ts +466 -0
- package/src/runtime/test/core-functions.test.ts +228 -0
- package/src/runtime/test/e2e.test.ts +88 -0
- package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
- package/src/runtime/test/error-locations/main-error.vibe +4 -0
- package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
- package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
- package/src/runtime/test/for-in.test.ts +312 -0
- package/src/runtime/test/helpers.ts +69 -0
- package/src/runtime/test/imports.test.ts +334 -0
- package/src/runtime/test/json-expressions.test.ts +232 -0
- package/src/runtime/test/literals.test.ts +372 -0
- package/src/runtime/test/logical-indexing.test.ts +478 -0
- package/src/runtime/test/member-methods.test.ts +324 -0
- package/src/runtime/test/model-config.test.ts +338 -0
- package/src/runtime/test/null-handling.test.ts +342 -0
- package/src/runtime/test/private-visibility.test.ts +332 -0
- package/src/runtime/test/runtime-state.test.ts +514 -0
- package/src/runtime/test/scoping.test.ts +370 -0
- package/src/runtime/test/string-interpolation.test.ts +354 -0
- package/src/runtime/test/template-literal.test.ts +181 -0
- package/src/runtime/test/tool-execution.test.ts +467 -0
- package/src/runtime/test/tool-schema-generation.test.ts +477 -0
- package/src/runtime/test/tostring.test.ts +210 -0
- package/src/runtime/test/ts-block.test.ts +594 -0
- package/src/runtime/test/ts-error-location.test.ts +231 -0
- package/src/runtime/test/types.test.ts +732 -0
- package/src/runtime/test/verbose-logger.test.ts +710 -0
- package/src/runtime/test/vibe-expression.test.ts +54 -0
- package/src/runtime/test/vibe-value-errors.test.ts +541 -0
- package/src/runtime/test/while.test.ts +232 -0
- package/src/runtime/tools/builtin.ts +30 -0
- package/src/runtime/tools/directory-tools.ts +70 -0
- package/src/runtime/tools/file-tools.ts +228 -0
- package/src/runtime/tools/index.ts +5 -0
- package/src/runtime/tools/registry.ts +48 -0
- package/src/runtime/tools/search-tools.ts +134 -0
- package/src/runtime/tools/security.ts +36 -0
- package/src/runtime/tools/system-tools.ts +312 -0
- package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
- package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
- package/src/runtime/tools/test/registry.test.ts +713 -0
- package/src/runtime/tools/test/security.test.ts +86 -0
- package/src/runtime/tools/test/system-tools.test.ts +679 -0
- package/src/runtime/tools/test/ts-schema.test.ts +357 -0
- package/src/runtime/tools/ts-schema.ts +341 -0
- package/src/runtime/tools/types.ts +89 -0
- package/src/runtime/tools/utility-tools.ts +198 -0
- package/src/runtime/ts-eval.ts +126 -0
- package/src/runtime/types.ts +797 -0
- package/src/runtime/validation.ts +160 -0
- package/src/runtime/verbose-logger.ts +459 -0
- package/src/runtime.ts +2 -0
- package/src/semantic/analyzer-context.ts +62 -0
- package/src/semantic/analyzer-validators.ts +575 -0
- package/src/semantic/analyzer-visitors.ts +534 -0
- package/src/semantic/analyzer.ts +83 -0
- package/src/semantic/index.ts +11 -0
- package/src/semantic/symbol-table.ts +58 -0
- package/src/semantic/test/async-validation.test.ts +301 -0
- package/src/semantic/test/compress-validation.test.ts +179 -0
- package/src/semantic/test/const-reassignment.test.ts +111 -0
- package/src/semantic/test/control-flow.test.ts +346 -0
- package/src/semantic/test/destructuring.test.ts +185 -0
- package/src/semantic/test/duplicate-declarations.test.ts +168 -0
- package/src/semantic/test/export-validation.test.ts +111 -0
- package/src/semantic/test/fixtures/math.ts +31 -0
- package/src/semantic/test/imports.test.ts +148 -0
- package/src/semantic/test/json-type.test.ts +68 -0
- package/src/semantic/test/literals.test.ts +127 -0
- package/src/semantic/test/model-validation.test.ts +179 -0
- package/src/semantic/test/prompt-validation.test.ts +343 -0
- package/src/semantic/test/scoping.test.ts +312 -0
- package/src/semantic/test/tool-validation.test.ts +306 -0
- package/src/semantic/test/ts-type-checking.test.ts +563 -0
- package/src/semantic/test/type-constraints.test.ts +111 -0
- package/src/semantic/test/type-inference.test.ts +87 -0
- package/src/semantic/test/type-validation.test.ts +552 -0
- package/src/semantic/test/undefined-variables.test.ts +163 -0
- package/src/semantic/ts-block-checker.ts +204 -0
- package/src/semantic/ts-signatures.ts +194 -0
- package/src/semantic/ts-types.ts +170 -0
- package/src/semantic/types.ts +58 -0
- package/tests/fixtures/conditional-logic.vibe +14 -0
- package/tests/fixtures/function-call.vibe +16 -0
- package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
- package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
- package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
- package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
- package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
- package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
- package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
- package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
- package/tests/fixtures/imports/nested-import/main.vibe +3 -0
- package/tests/fixtures/imports/nested-import/utils.ts +3 -0
- package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
- package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
- package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
- package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
- package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
- package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
- package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
- package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
- package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
- package/tests/fixtures/imports/ts-import/main.vibe +4 -0
- package/tests/fixtures/imports/ts-import/math.ts +9 -0
- package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/data.ts +10 -0
- package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
- package/tests/fixtures/multiple-ai-calls.vibe +10 -0
- package/tests/fixtures/simple-greeting.vibe +6 -0
- package/tests/fixtures/template-literals.vibe +11 -0
- package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
- package/tests/integration/basic-ai/basic-ai.vibe +12 -0
- package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
- package/tests/integration/bug-fix/buggy-code.ts +22 -0
- package/tests/integration/bug-fix/fix-bug.vibe +21 -0
- package/tests/integration/compress/compress.integration.test.ts +206 -0
- package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
- package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
- package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
- package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
- package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
- package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
- package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
- 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
|
+
});
|