@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,232 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { parse } from '../../parser/parse';
|
|
3
|
+
import { createInitialState, runUntilPause } from '../index';
|
|
4
|
+
|
|
5
|
+
describe('Runtime While Loop', () => {
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Basic while loop
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
test('while loop executes while condition is true', () => {
|
|
11
|
+
const ast = parse(`
|
|
12
|
+
let keepGoing = true
|
|
13
|
+
let executed = false
|
|
14
|
+
while keepGoing {
|
|
15
|
+
executed = true
|
|
16
|
+
keepGoing = false
|
|
17
|
+
}
|
|
18
|
+
`);
|
|
19
|
+
let state = createInitialState(ast);
|
|
20
|
+
state = runUntilPause(state);
|
|
21
|
+
|
|
22
|
+
expect(state.status).toBe('completed');
|
|
23
|
+
const frame = state.callStack[0];
|
|
24
|
+
expect(frame.locals['executed'].value).toBe(true);
|
|
25
|
+
expect(frame.locals['keepGoing'].value).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('while loop with false condition never executes', () => {
|
|
29
|
+
const ast = parse(`
|
|
30
|
+
let executed = false
|
|
31
|
+
while false {
|
|
32
|
+
executed = true
|
|
33
|
+
}
|
|
34
|
+
`);
|
|
35
|
+
let state = createInitialState(ast);
|
|
36
|
+
state = runUntilPause(state);
|
|
37
|
+
|
|
38
|
+
expect(state.status).toBe('completed');
|
|
39
|
+
const frame = state.callStack[0];
|
|
40
|
+
expect(frame.locals['executed'].value).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('while loop with true initial condition executes once then exits', () => {
|
|
44
|
+
const ast = parse(`
|
|
45
|
+
let counter = true
|
|
46
|
+
let loopRan = false
|
|
47
|
+
while counter {
|
|
48
|
+
loopRan = true
|
|
49
|
+
counter = false
|
|
50
|
+
}
|
|
51
|
+
`);
|
|
52
|
+
let state = createInitialState(ast);
|
|
53
|
+
state = runUntilPause(state);
|
|
54
|
+
|
|
55
|
+
expect(state.status).toBe('completed');
|
|
56
|
+
const frame = state.callStack[0];
|
|
57
|
+
expect(frame.locals['loopRan'].value).toBe(true);
|
|
58
|
+
expect(frame.locals['counter'].value).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Scoping
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
65
|
+
test('variables declared in while body are cleaned up each iteration', () => {
|
|
66
|
+
// This tests that body-scoped variables don't persist
|
|
67
|
+
const ast = parse(`
|
|
68
|
+
let flag = true
|
|
69
|
+
let outsideVar = false
|
|
70
|
+
while flag {
|
|
71
|
+
let insideVar = true
|
|
72
|
+
outsideVar = insideVar
|
|
73
|
+
flag = false
|
|
74
|
+
}
|
|
75
|
+
`);
|
|
76
|
+
let state = createInitialState(ast);
|
|
77
|
+
state = runUntilPause(state);
|
|
78
|
+
|
|
79
|
+
expect(state.status).toBe('completed');
|
|
80
|
+
const frame = state.callStack[0];
|
|
81
|
+
expect(frame.locals['outsideVar'].value).toBe(true);
|
|
82
|
+
// insideVar should not exist in the frame after loop completes
|
|
83
|
+
expect(frame.locals['insideVar']).toBeUndefined();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('while loop does not leak scope to outer context', () => {
|
|
87
|
+
const ast = parse(`
|
|
88
|
+
let preLoop = true
|
|
89
|
+
while false {
|
|
90
|
+
let loopVar = true
|
|
91
|
+
}
|
|
92
|
+
`);
|
|
93
|
+
let state = createInitialState(ast);
|
|
94
|
+
state = runUntilPause(state);
|
|
95
|
+
|
|
96
|
+
expect(state.status).toBe('completed');
|
|
97
|
+
const frame = state.callStack[0];
|
|
98
|
+
expect(frame.locals['preLoop'].value).toBe(true);
|
|
99
|
+
expect(frame.locals['loopVar']).toBeUndefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// Nested while loops
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
test('nested while loops work correctly', () => {
|
|
107
|
+
const ast = parse(`
|
|
108
|
+
let outer = true
|
|
109
|
+
let inner = true
|
|
110
|
+
let outerRan = false
|
|
111
|
+
let innerRan = false
|
|
112
|
+
while outer {
|
|
113
|
+
outerRan = true
|
|
114
|
+
while inner {
|
|
115
|
+
innerRan = true
|
|
116
|
+
inner = false
|
|
117
|
+
}
|
|
118
|
+
outer = false
|
|
119
|
+
}
|
|
120
|
+
`);
|
|
121
|
+
let state = createInitialState(ast);
|
|
122
|
+
state = runUntilPause(state);
|
|
123
|
+
|
|
124
|
+
expect(state.status).toBe('completed');
|
|
125
|
+
const frame = state.callStack[0];
|
|
126
|
+
expect(frame.locals['outerRan'].value).toBe(true);
|
|
127
|
+
expect(frame.locals['innerRan'].value).toBe(true);
|
|
128
|
+
expect(frame.locals['outer'].value).toBe(false);
|
|
129
|
+
expect(frame.locals['inner'].value).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// Error cases
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
test('while with non-boolean condition throws error', () => {
|
|
137
|
+
const ast = parse(`
|
|
138
|
+
while "not a boolean" {
|
|
139
|
+
let x = 1
|
|
140
|
+
}
|
|
141
|
+
`);
|
|
142
|
+
let state = createInitialState(ast);
|
|
143
|
+
state = runUntilPause(state);
|
|
144
|
+
|
|
145
|
+
expect(state.status).toBe('error');
|
|
146
|
+
expect(state.error).toContain('boolean');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('while with number condition throws error', () => {
|
|
150
|
+
const ast = parse(`
|
|
151
|
+
while 1 {
|
|
152
|
+
let x = 1
|
|
153
|
+
}
|
|
154
|
+
`);
|
|
155
|
+
let state = createInitialState(ast);
|
|
156
|
+
state = runUntilPause(state);
|
|
157
|
+
|
|
158
|
+
expect(state.status).toBe('error');
|
|
159
|
+
expect(state.error).toContain('boolean');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('while with string variable condition throws error', () => {
|
|
163
|
+
const ast = parse(`
|
|
164
|
+
const x = "test"
|
|
165
|
+
while x {
|
|
166
|
+
let y = 1
|
|
167
|
+
}
|
|
168
|
+
`);
|
|
169
|
+
let state = createInitialState(ast);
|
|
170
|
+
state = runUntilPause(state);
|
|
171
|
+
|
|
172
|
+
expect(state.status).toBe('error');
|
|
173
|
+
expect(state.error).toContain('boolean');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('while with number variable condition throws error', () => {
|
|
177
|
+
const ast = parse(`
|
|
178
|
+
let count = 5
|
|
179
|
+
while count {
|
|
180
|
+
count = 0
|
|
181
|
+
}
|
|
182
|
+
`);
|
|
183
|
+
let state = createInitialState(ast);
|
|
184
|
+
state = runUntilPause(state);
|
|
185
|
+
|
|
186
|
+
expect(state.status).toBe('error');
|
|
187
|
+
expect(state.error).toContain('boolean');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// ============================================================================
|
|
191
|
+
// Integration with other constructs
|
|
192
|
+
// ============================================================================
|
|
193
|
+
|
|
194
|
+
test('while loop with if statement inside', () => {
|
|
195
|
+
const ast = parse(`
|
|
196
|
+
let flag = true
|
|
197
|
+
let tookBranch = false
|
|
198
|
+
while flag {
|
|
199
|
+
if true {
|
|
200
|
+
tookBranch = true
|
|
201
|
+
}
|
|
202
|
+
flag = false
|
|
203
|
+
}
|
|
204
|
+
`);
|
|
205
|
+
let state = createInitialState(ast);
|
|
206
|
+
state = runUntilPause(state);
|
|
207
|
+
|
|
208
|
+
expect(state.status).toBe('completed');
|
|
209
|
+
const frame = state.callStack[0];
|
|
210
|
+
expect(frame.locals['tookBranch'].value).toBe(true);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('while loop inside function', () => {
|
|
214
|
+
const ast = parse(`
|
|
215
|
+
function loopOnce(): boolean {
|
|
216
|
+
let done = false
|
|
217
|
+
while true {
|
|
218
|
+
done = true
|
|
219
|
+
return done
|
|
220
|
+
}
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
223
|
+
let result = loopOnce()
|
|
224
|
+
`);
|
|
225
|
+
let state = createInitialState(ast);
|
|
226
|
+
state = runUntilPause(state);
|
|
227
|
+
|
|
228
|
+
expect(state.status).toBe('completed');
|
|
229
|
+
const frame = state.callStack[0];
|
|
230
|
+
expect(frame.locals['result'].value).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { RegisteredTool } from './types';
|
|
2
|
+
import { fileTools } from './file-tools';
|
|
3
|
+
import { searchTools } from './search-tools';
|
|
4
|
+
import { directoryTools } from './directory-tools';
|
|
5
|
+
import { utilityTools } from './utility-tools';
|
|
6
|
+
import { systemTools } from './system-tools';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Standard tools available in the Vibe runtime.
|
|
10
|
+
* Combines all tool categories into a single array.
|
|
11
|
+
*
|
|
12
|
+
* Categories:
|
|
13
|
+
* - File operations: readFile, writeFile, appendFile, fileExists, listDir, edit
|
|
14
|
+
* - File search: glob, grep
|
|
15
|
+
* - Directory operations: mkdir, dirExists
|
|
16
|
+
* - Utilities: env, sleep, now, jsonParse, jsonStringify, print, random, uuid
|
|
17
|
+
* - System: bash, runCode
|
|
18
|
+
*
|
|
19
|
+
* Total: 20 tools
|
|
20
|
+
*/
|
|
21
|
+
export const allTools: RegisteredTool[] = [
|
|
22
|
+
...fileTools,
|
|
23
|
+
...searchTools,
|
|
24
|
+
...directoryTools,
|
|
25
|
+
...utilityTools,
|
|
26
|
+
...systemTools,
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/** @deprecated Use allTools instead */
|
|
30
|
+
export const builtinTools = allTools;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { RegisteredTool, ToolContext } from './types';
|
|
2
|
+
import { validatePathInSandbox } from './security';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Directory operation tools: mkdir, dirExists
|
|
6
|
+
*/
|
|
7
|
+
export const directoryTools = [
|
|
8
|
+
{
|
|
9
|
+
name: 'mkdir',
|
|
10
|
+
kind: 'builtin',
|
|
11
|
+
schema: {
|
|
12
|
+
name: 'mkdir',
|
|
13
|
+
description: 'Create a directory.',
|
|
14
|
+
parameters: [
|
|
15
|
+
{
|
|
16
|
+
name: 'path',
|
|
17
|
+
type: { type: 'string' },
|
|
18
|
+
description: 'The directory path to create',
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'recursive',
|
|
23
|
+
type: { type: 'boolean' },
|
|
24
|
+
description: 'Create parent directories as needed (default: false)',
|
|
25
|
+
required: false,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
returns: { type: 'boolean' },
|
|
29
|
+
},
|
|
30
|
+
executor: async (args: Record<string, unknown>, context?: ToolContext) => {
|
|
31
|
+
const inputPath = args.path as string;
|
|
32
|
+
const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
|
|
33
|
+
const recursive = args.recursive as boolean | undefined;
|
|
34
|
+
|
|
35
|
+
const fs = await import('fs/promises');
|
|
36
|
+
await fs.mkdir(safePath, { recursive: recursive ?? false });
|
|
37
|
+
return true;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
name: 'dirExists',
|
|
43
|
+
kind: 'builtin',
|
|
44
|
+
schema: {
|
|
45
|
+
name: 'dirExists',
|
|
46
|
+
description: 'Check if a directory exists.',
|
|
47
|
+
parameters: [
|
|
48
|
+
{
|
|
49
|
+
name: 'path',
|
|
50
|
+
type: { type: 'string' },
|
|
51
|
+
description: 'The directory path to check',
|
|
52
|
+
required: true,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
returns: { type: 'boolean' },
|
|
56
|
+
},
|
|
57
|
+
executor: async (args: Record<string, unknown>, context?: ToolContext) => {
|
|
58
|
+
const inputPath = args.path as string;
|
|
59
|
+
const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
|
|
60
|
+
|
|
61
|
+
const fs = await import('fs/promises');
|
|
62
|
+
try {
|
|
63
|
+
const stats = await fs.stat(safePath);
|
|
64
|
+
return stats.isDirectory();
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
] satisfies RegisteredTool[];
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type { RegisteredTool, ToolContext } from './types';
|
|
2
|
+
import { validatePathInSandbox } from './security';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* File operation tools: read, write, append, exists, list, edit
|
|
6
|
+
*/
|
|
7
|
+
export const fileTools = [
|
|
8
|
+
{
|
|
9
|
+
name: 'readFile',
|
|
10
|
+
kind: 'builtin',
|
|
11
|
+
schema: {
|
|
12
|
+
name: 'readFile',
|
|
13
|
+
description: 'Read the contents of a file as text. Optionally read a range of lines.',
|
|
14
|
+
parameters: [
|
|
15
|
+
{
|
|
16
|
+
name: 'path',
|
|
17
|
+
type: { type: 'string' },
|
|
18
|
+
description: 'The file path to read',
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'startLine',
|
|
23
|
+
type: { type: 'number' },
|
|
24
|
+
description: 'First line to read (1-based, inclusive). If omitted, starts from beginning.',
|
|
25
|
+
required: false,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'endLine',
|
|
29
|
+
type: { type: 'number' },
|
|
30
|
+
description: 'Last line to read (1-based, inclusive). If omitted, reads to end of file.',
|
|
31
|
+
required: false,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
returns: { type: 'string' },
|
|
35
|
+
},
|
|
36
|
+
executor: async (args: Record<string, unknown>, context?: ToolContext) => {
|
|
37
|
+
const inputPath = args.path as string;
|
|
38
|
+
const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
|
|
39
|
+
const startLine = args.startLine as number | undefined;
|
|
40
|
+
const endLine = args.endLine as number | undefined;
|
|
41
|
+
|
|
42
|
+
const file = Bun.file(safePath);
|
|
43
|
+
const content = await file.text();
|
|
44
|
+
|
|
45
|
+
// If no line range specified, return entire file
|
|
46
|
+
if (startLine === undefined && endLine === undefined) {
|
|
47
|
+
return content;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Split into lines and extract the requested range
|
|
51
|
+
const lines = content.split('\n');
|
|
52
|
+
const start = startLine !== undefined ? Math.max(1, startLine) - 1 : 0; // Convert to 0-based
|
|
53
|
+
const end = endLine !== undefined ? Math.min(lines.length, endLine) : lines.length;
|
|
54
|
+
|
|
55
|
+
return lines.slice(start, end).join('\n');
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
{
|
|
60
|
+
name: 'writeFile',
|
|
61
|
+
kind: 'builtin',
|
|
62
|
+
schema: {
|
|
63
|
+
name: 'writeFile',
|
|
64
|
+
description: 'Write content to a file.',
|
|
65
|
+
parameters: [
|
|
66
|
+
{
|
|
67
|
+
name: 'path',
|
|
68
|
+
type: { type: 'string' },
|
|
69
|
+
description: 'The file path to write to',
|
|
70
|
+
required: true,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'content',
|
|
74
|
+
type: { type: 'string' },
|
|
75
|
+
description: 'The content to write',
|
|
76
|
+
required: true,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
returns: { type: 'boolean' },
|
|
80
|
+
},
|
|
81
|
+
executor: async (args: Record<string, unknown>, context?: ToolContext) => {
|
|
82
|
+
const inputPath = args.path as string;
|
|
83
|
+
const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
|
|
84
|
+
const content = args.content as string;
|
|
85
|
+
await Bun.write(safePath, content);
|
|
86
|
+
return true;
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
{
|
|
91
|
+
name: 'appendFile',
|
|
92
|
+
kind: 'builtin',
|
|
93
|
+
schema: {
|
|
94
|
+
name: 'appendFile',
|
|
95
|
+
description: 'Append content to a file.',
|
|
96
|
+
parameters: [
|
|
97
|
+
{
|
|
98
|
+
name: 'path',
|
|
99
|
+
type: { type: 'string' },
|
|
100
|
+
description: 'The file path to append to',
|
|
101
|
+
required: true,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'content',
|
|
105
|
+
type: { type: 'string' },
|
|
106
|
+
description: 'The content to append',
|
|
107
|
+
required: true,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
returns: { type: 'boolean' },
|
|
111
|
+
},
|
|
112
|
+
executor: async (args: Record<string, unknown>, context?: ToolContext) => {
|
|
113
|
+
const inputPath = args.path as string;
|
|
114
|
+
const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
|
|
115
|
+
const content = args.content as string;
|
|
116
|
+
const file = Bun.file(safePath);
|
|
117
|
+
const existing = (await file.exists()) ? await file.text() : '';
|
|
118
|
+
await Bun.write(safePath, existing + content);
|
|
119
|
+
return true;
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
{
|
|
124
|
+
name: 'fileExists',
|
|
125
|
+
kind: 'builtin',
|
|
126
|
+
schema: {
|
|
127
|
+
name: 'fileExists',
|
|
128
|
+
description: 'Check if a file exists.',
|
|
129
|
+
parameters: [
|
|
130
|
+
{
|
|
131
|
+
name: 'path',
|
|
132
|
+
type: { type: 'string' },
|
|
133
|
+
description: 'The file path to check',
|
|
134
|
+
required: true,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
returns: { type: 'boolean' },
|
|
138
|
+
},
|
|
139
|
+
executor: async (args: Record<string, unknown>, context?: ToolContext) => {
|
|
140
|
+
const inputPath = args.path as string;
|
|
141
|
+
const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
|
|
142
|
+
const file = Bun.file(safePath);
|
|
143
|
+
return await file.exists();
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
{
|
|
148
|
+
name: 'listDir',
|
|
149
|
+
kind: 'builtin',
|
|
150
|
+
schema: {
|
|
151
|
+
name: 'listDir',
|
|
152
|
+
description: 'List files in a directory.',
|
|
153
|
+
parameters: [
|
|
154
|
+
{
|
|
155
|
+
name: 'path',
|
|
156
|
+
type: { type: 'string' },
|
|
157
|
+
description: 'The directory path to list',
|
|
158
|
+
required: true,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
returns: { type: 'array', items: { type: 'string' } },
|
|
162
|
+
},
|
|
163
|
+
executor: async (args: Record<string, unknown>, context?: ToolContext) => {
|
|
164
|
+
const inputPath = args.path as string;
|
|
165
|
+
const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
|
|
166
|
+
const fs = await import('fs/promises');
|
|
167
|
+
return await fs.readdir(safePath);
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
{
|
|
172
|
+
name: 'edit',
|
|
173
|
+
kind: 'builtin',
|
|
174
|
+
schema: {
|
|
175
|
+
name: 'edit',
|
|
176
|
+
description:
|
|
177
|
+
'Find and replace text in a file. The oldText must match exactly once in the file.',
|
|
178
|
+
parameters: [
|
|
179
|
+
{
|
|
180
|
+
name: 'path',
|
|
181
|
+
type: { type: 'string' },
|
|
182
|
+
description: 'The file path to edit',
|
|
183
|
+
required: true,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'oldText',
|
|
187
|
+
type: { type: 'string' },
|
|
188
|
+
description: 'The text to find (must match exactly once)',
|
|
189
|
+
required: true,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'newText',
|
|
193
|
+
type: { type: 'string' },
|
|
194
|
+
description: 'The text to replace with',
|
|
195
|
+
required: true,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
returns: { type: 'boolean' },
|
|
199
|
+
},
|
|
200
|
+
executor: async (args: Record<string, unknown>, context?: ToolContext) => {
|
|
201
|
+
const inputPath = args.path as string;
|
|
202
|
+
const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
|
|
203
|
+
const oldText = args.oldText as string;
|
|
204
|
+
const newText = args.newText as string;
|
|
205
|
+
|
|
206
|
+
const file = Bun.file(safePath);
|
|
207
|
+
const content = await file.text();
|
|
208
|
+
|
|
209
|
+
// Count occurrences of oldText
|
|
210
|
+
const matches = content.split(oldText).length - 1;
|
|
211
|
+
|
|
212
|
+
if (matches === 0) {
|
|
213
|
+
throw new Error(`edit failed: oldText not found in file`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (matches > 1) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`edit failed: oldText matches ${matches} times, must match exactly once. Provide more context to make the match unique.`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Exactly one match - safe to replace
|
|
223
|
+
const newContent = content.replace(oldText, newText);
|
|
224
|
+
await Bun.write(safePath, newContent);
|
|
225
|
+
return true;
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
] satisfies RegisteredTool[];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Tool system types and utilities
|
|
2
|
+
export * from './types';
|
|
3
|
+
export { extractTypeSchema, vibeTypeToJsonSchema, clearSchemaCache, createTypeExtractor } from './ts-schema';
|
|
4
|
+
export type { TypeExtractor } from './ts-schema';
|
|
5
|
+
export { validatePathInSandbox } from './security';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { RegisteredTool, ToolRegistry, ToolSchema } from './types';
|
|
2
|
+
import { allTools } from './builtin';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a new tool registry.
|
|
6
|
+
* Tools are stored in a Map for O(1) lookup.
|
|
7
|
+
*/
|
|
8
|
+
export function createToolRegistry(): ToolRegistry {
|
|
9
|
+
const tools = new Map<string, RegisteredTool>();
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
register(tool: RegisteredTool): void {
|
|
13
|
+
// Allow overwriting existing tools (user tools can shadow builtins)
|
|
14
|
+
tools.set(tool.name, tool);
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
registerAll(toolsToAdd: RegisteredTool[]): void {
|
|
18
|
+
for (const tool of toolsToAdd) {
|
|
19
|
+
tools.set(tool.name, tool);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
get(name: string): RegisteredTool | undefined {
|
|
24
|
+
return tools.get(name);
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
has(name: string): boolean {
|
|
28
|
+
return tools.has(name);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
list(): RegisteredTool[] {
|
|
32
|
+
return Array.from(tools.values());
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
getSchemas(): ToolSchema[] {
|
|
36
|
+
return Array.from(tools.values()).map((t) => t.schema);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create a tool registry pre-populated with standard tools.
|
|
43
|
+
*/
|
|
44
|
+
export function createToolRegistryWithBuiltins(): ToolRegistry {
|
|
45
|
+
const registry = createToolRegistry();
|
|
46
|
+
registry.registerAll(allTools);
|
|
47
|
+
return registry;
|
|
48
|
+
}
|