@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,154 @@
|
|
|
1
|
+
// Tests for the edit tool
|
|
2
|
+
|
|
3
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
4
|
+
import { edit } from './tools';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
|
|
8
|
+
const TEST_DIR = path.join(__dirname, '.test-workspace-edit');
|
|
9
|
+
|
|
10
|
+
describe('edit tool', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
if (fs.existsSync(TEST_DIR)) {
|
|
17
|
+
fs.rmSync(TEST_DIR, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('basic replacement - oldText replaced with newText', async () => {
|
|
22
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
23
|
+
fs.writeFileSync(filePath, 'const x = 1;\nconst y = 2;');
|
|
24
|
+
|
|
25
|
+
await edit.executor(
|
|
26
|
+
{
|
|
27
|
+
path: filePath,
|
|
28
|
+
oldText: 'const x = 1;',
|
|
29
|
+
newText: 'const x = 42;',
|
|
30
|
+
},
|
|
31
|
+
{ rootDir: TEST_DIR }
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const result = fs.readFileSync(filePath, 'utf-8');
|
|
35
|
+
expect(result).toBe('const x = 42;\nconst y = 2;');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('multiline replacement', async () => {
|
|
39
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
40
|
+
fs.writeFileSync(
|
|
41
|
+
filePath,
|
|
42
|
+
`function foo() {
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function bar() {
|
|
47
|
+
return 2;
|
|
48
|
+
}`
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
await edit.executor(
|
|
52
|
+
{
|
|
53
|
+
path: filePath,
|
|
54
|
+
oldText: `function foo() {
|
|
55
|
+
return 1;
|
|
56
|
+
}`,
|
|
57
|
+
newText: `function foo() {
|
|
58
|
+
return 42;
|
|
59
|
+
}`,
|
|
60
|
+
},
|
|
61
|
+
{ rootDir: TEST_DIR }
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const result = fs.readFileSync(filePath, 'utf-8');
|
|
65
|
+
expect(result).toContain('return 42;');
|
|
66
|
+
expect(result).toContain('function bar()');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('error: oldText not found', async () => {
|
|
70
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
71
|
+
fs.writeFileSync(filePath, 'const x = 1;');
|
|
72
|
+
|
|
73
|
+
await expect(
|
|
74
|
+
edit.executor(
|
|
75
|
+
{
|
|
76
|
+
path: filePath,
|
|
77
|
+
oldText: 'NOT_FOUND',
|
|
78
|
+
newText: 'replacement',
|
|
79
|
+
},
|
|
80
|
+
{ rootDir: TEST_DIR }
|
|
81
|
+
)
|
|
82
|
+
).rejects.toThrow('oldText not found in file');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('error: oldText matches multiple times', async () => {
|
|
86
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
87
|
+
fs.writeFileSync(filePath, 'const x = 1;\nconst x = 1;');
|
|
88
|
+
|
|
89
|
+
await expect(
|
|
90
|
+
edit.executor(
|
|
91
|
+
{
|
|
92
|
+
path: filePath,
|
|
93
|
+
oldText: 'const x = 1;',
|
|
94
|
+
newText: 'const x = 42;',
|
|
95
|
+
},
|
|
96
|
+
{ rootDir: TEST_DIR }
|
|
97
|
+
)
|
|
98
|
+
).rejects.toThrow('oldText matches 2 times, must match exactly once');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('replaces empty string with content (prepend)', async () => {
|
|
102
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
103
|
+
fs.writeFileSync(filePath, 'content');
|
|
104
|
+
|
|
105
|
+
// This actually replaces the first empty string match
|
|
106
|
+
// which prepends to the file
|
|
107
|
+
await edit.executor(
|
|
108
|
+
{
|
|
109
|
+
path: filePath,
|
|
110
|
+
oldText: 'content',
|
|
111
|
+
newText: 'new content',
|
|
112
|
+
},
|
|
113
|
+
{ rootDir: TEST_DIR }
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const result = fs.readFileSync(filePath, 'utf-8');
|
|
117
|
+
expect(result).toBe('new content');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('handles special characters in oldText', async () => {
|
|
121
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
122
|
+
fs.writeFileSync(filePath, 'const regex = /test.*end/;');
|
|
123
|
+
|
|
124
|
+
await edit.executor(
|
|
125
|
+
{
|
|
126
|
+
path: filePath,
|
|
127
|
+
oldText: 'const regex = /test.*end/;',
|
|
128
|
+
newText: 'const regex = /new/;',
|
|
129
|
+
},
|
|
130
|
+
{ rootDir: TEST_DIR }
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const result = fs.readFileSync(filePath, 'utf-8');
|
|
134
|
+
expect(result).toBe('const regex = /new/;');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('preserves file encoding and line endings', async () => {
|
|
138
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
139
|
+
const content = 'line1\r\nline2\r\nline3';
|
|
140
|
+
fs.writeFileSync(filePath, content);
|
|
141
|
+
|
|
142
|
+
await edit.executor(
|
|
143
|
+
{
|
|
144
|
+
path: filePath,
|
|
145
|
+
oldText: 'line2',
|
|
146
|
+
newText: 'modified',
|
|
147
|
+
},
|
|
148
|
+
{ rootDir: TEST_DIR }
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const result = fs.readFileSync(filePath, 'utf-8');
|
|
152
|
+
expect(result).toBe('line1\r\nmodified\r\nline3');
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Tests for stdlib tools
|
|
2
|
+
|
|
3
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
4
|
+
import { fastEdit } from './tools';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
|
|
8
|
+
const TEST_DIR = path.join(__dirname, '.test-workspace');
|
|
9
|
+
|
|
10
|
+
describe('fastEdit tool', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
if (fs.existsSync(TEST_DIR)) {
|
|
17
|
+
fs.rmSync(TEST_DIR, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('basic replacement - prefix...suffix replaced with newText', async () => {
|
|
22
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
23
|
+
fs.writeFileSync(
|
|
24
|
+
filePath,
|
|
25
|
+
`function foo() {
|
|
26
|
+
console.log("old");
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function bar() {
|
|
31
|
+
return 2;
|
|
32
|
+
}`
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
await fastEdit.executor(
|
|
36
|
+
{
|
|
37
|
+
path: filePath,
|
|
38
|
+
prefix: 'function foo() {',
|
|
39
|
+
suffix: 'return 1;\n}',
|
|
40
|
+
newText: `function foo() {
|
|
41
|
+
console.log("new");
|
|
42
|
+
return 42;
|
|
43
|
+
}`,
|
|
44
|
+
},
|
|
45
|
+
{ rootDir: TEST_DIR }
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const result = fs.readFileSync(filePath, 'utf-8');
|
|
49
|
+
expect(result).toContain('console.log("new")');
|
|
50
|
+
expect(result).toContain('return 42;');
|
|
51
|
+
expect(result).toContain('function bar()'); // Other function unchanged
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('non-greedy matching - stops at first suffix after prefix', async () => {
|
|
55
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
56
|
+
// Two functions that end with "return result;"
|
|
57
|
+
fs.writeFileSync(
|
|
58
|
+
filePath,
|
|
59
|
+
`function first() {
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function second() {
|
|
64
|
+
return result;
|
|
65
|
+
}`
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
await fastEdit.executor(
|
|
69
|
+
{
|
|
70
|
+
path: filePath,
|
|
71
|
+
prefix: 'function first() {',
|
|
72
|
+
suffix: 'return result;\n}',
|
|
73
|
+
newText: `function first() {
|
|
74
|
+
return "replaced";
|
|
75
|
+
}`,
|
|
76
|
+
},
|
|
77
|
+
{ rootDir: TEST_DIR }
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const result = fs.readFileSync(filePath, 'utf-8');
|
|
81
|
+
expect(result).toContain('return "replaced"');
|
|
82
|
+
expect(result).toContain('function second()'); // Second function unchanged
|
|
83
|
+
// Second function should still have original return
|
|
84
|
+
expect(result).toMatch(/function second\(\) \{\s+return result;/);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('error: prefix not found', async () => {
|
|
88
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
89
|
+
fs.writeFileSync(filePath, 'const x = 1;');
|
|
90
|
+
|
|
91
|
+
await expect(
|
|
92
|
+
fastEdit.executor(
|
|
93
|
+
{
|
|
94
|
+
path: filePath,
|
|
95
|
+
prefix: 'function notFound() {',
|
|
96
|
+
suffix: '}',
|
|
97
|
+
newText: 'replacement',
|
|
98
|
+
},
|
|
99
|
+
{ rootDir: TEST_DIR }
|
|
100
|
+
)
|
|
101
|
+
).rejects.toThrow('no region found matching prefix...suffix');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('error: suffix not found after prefix', async () => {
|
|
105
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
106
|
+
fs.writeFileSync(filePath, 'function foo() { return 1; }');
|
|
107
|
+
|
|
108
|
+
await expect(
|
|
109
|
+
fastEdit.executor(
|
|
110
|
+
{
|
|
111
|
+
path: filePath,
|
|
112
|
+
prefix: 'function foo() {',
|
|
113
|
+
suffix: 'NOT_FOUND_SUFFIX',
|
|
114
|
+
newText: 'replacement',
|
|
115
|
+
},
|
|
116
|
+
{ rootDir: TEST_DIR }
|
|
117
|
+
)
|
|
118
|
+
).rejects.toThrow('no region found matching prefix...suffix');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('error: multiple matches (ambiguous anchors)', async () => {
|
|
122
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
123
|
+
// Two identical functions - ambiguous!
|
|
124
|
+
fs.writeFileSync(
|
|
125
|
+
filePath,
|
|
126
|
+
`function foo() {
|
|
127
|
+
return 1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function foo() {
|
|
131
|
+
return 1;
|
|
132
|
+
}`
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
await expect(
|
|
136
|
+
fastEdit.executor(
|
|
137
|
+
{
|
|
138
|
+
path: filePath,
|
|
139
|
+
prefix: 'function foo() {',
|
|
140
|
+
suffix: 'return 1;\n}',
|
|
141
|
+
newText: 'replacement',
|
|
142
|
+
},
|
|
143
|
+
{ rootDir: TEST_DIR }
|
|
144
|
+
)
|
|
145
|
+
).rejects.toThrow('2 regions match, must match exactly once');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('special regex characters in prefix/suffix are escaped', async () => {
|
|
149
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
150
|
+
// Content with regex special characters
|
|
151
|
+
fs.writeFileSync(filePath, 'const pattern = /test.*?end/; // comment');
|
|
152
|
+
|
|
153
|
+
await fastEdit.executor(
|
|
154
|
+
{
|
|
155
|
+
path: filePath,
|
|
156
|
+
prefix: 'const pattern = /test.*?end/',
|
|
157
|
+
suffix: '// comment',
|
|
158
|
+
newText: 'const pattern = /new/; // updated',
|
|
159
|
+
},
|
|
160
|
+
{ rootDir: TEST_DIR }
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const result = fs.readFileSync(filePath, 'utf-8');
|
|
164
|
+
expect(result).toBe('const pattern = /new/; // updated');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('handles multiline content correctly', async () => {
|
|
168
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
169
|
+
fs.writeFileSync(
|
|
170
|
+
filePath,
|
|
171
|
+
`class MyClass {
|
|
172
|
+
constructor() {
|
|
173
|
+
this.value = 1;
|
|
174
|
+
this.name = "test";
|
|
175
|
+
this.items = [];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
method() {
|
|
179
|
+
return this.value;
|
|
180
|
+
}
|
|
181
|
+
}`
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
await fastEdit.executor(
|
|
185
|
+
{
|
|
186
|
+
path: filePath,
|
|
187
|
+
prefix: 'constructor() {',
|
|
188
|
+
suffix: '}',
|
|
189
|
+
newText: `constructor() {
|
|
190
|
+
this.value = 42;
|
|
191
|
+
}`,
|
|
192
|
+
},
|
|
193
|
+
{ rootDir: TEST_DIR }
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const result = fs.readFileSync(filePath, 'utf-8');
|
|
197
|
+
expect(result).toContain('this.value = 42');
|
|
198
|
+
expect(result).not.toContain('this.name = "test"');
|
|
199
|
+
expect(result).toContain('method()'); // Other method unchanged
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Tests for the glob tool
|
|
2
|
+
|
|
3
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
4
|
+
import { glob } from './tools';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
|
|
8
|
+
const TEST_DIR = path.join(__dirname, '.test-workspace-glob');
|
|
9
|
+
|
|
10
|
+
describe('glob tool', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
if (fs.existsSync(TEST_DIR)) {
|
|
17
|
+
fs.rmSync(TEST_DIR, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('matches files with simple pattern', async () => {
|
|
22
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file1.ts'), '');
|
|
23
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file2.ts'), '');
|
|
24
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file3.js'), '');
|
|
25
|
+
|
|
26
|
+
const result = (await glob.executor({ pattern: '*.ts' }, { rootDir: TEST_DIR })) as string[];
|
|
27
|
+
|
|
28
|
+
expect(result).toHaveLength(2);
|
|
29
|
+
expect(result).toContain('file1.ts');
|
|
30
|
+
expect(result).toContain('file2.ts');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('matches files recursively with **', async () => {
|
|
34
|
+
fs.mkdirSync(path.join(TEST_DIR, 'src'), { recursive: true });
|
|
35
|
+
fs.mkdirSync(path.join(TEST_DIR, 'src', 'utils'), { recursive: true });
|
|
36
|
+
fs.writeFileSync(path.join(TEST_DIR, 'root.ts'), '');
|
|
37
|
+
fs.writeFileSync(path.join(TEST_DIR, 'src', 'index.ts'), '');
|
|
38
|
+
fs.writeFileSync(path.join(TEST_DIR, 'src', 'utils', 'helper.ts'), '');
|
|
39
|
+
|
|
40
|
+
const result = (await glob.executor({ pattern: '**/*.ts' }, { rootDir: TEST_DIR })) as string[];
|
|
41
|
+
|
|
42
|
+
expect(result).toHaveLength(3);
|
|
43
|
+
expect(result).toContain('root.ts');
|
|
44
|
+
expect(result.some((f) => f.includes('index.ts'))).toBe(true);
|
|
45
|
+
expect(result.some((f) => f.includes('helper.ts'))).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('matches with custom cwd', async () => {
|
|
49
|
+
fs.mkdirSync(path.join(TEST_DIR, 'src'), { recursive: true });
|
|
50
|
+
fs.writeFileSync(path.join(TEST_DIR, 'root.ts'), '');
|
|
51
|
+
fs.writeFileSync(path.join(TEST_DIR, 'src', 'index.ts'), '');
|
|
52
|
+
|
|
53
|
+
const result = (await glob.executor({ pattern: '*.ts', cwd: 'src' }, { rootDir: TEST_DIR })) as string[];
|
|
54
|
+
|
|
55
|
+
expect(result).toHaveLength(1);
|
|
56
|
+
expect(result).toContain('index.ts');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('returns empty array when no matches', async () => {
|
|
60
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file.js'), '');
|
|
61
|
+
|
|
62
|
+
const result = (await glob.executor({ pattern: '*.ts' }, { rootDir: TEST_DIR })) as string[];
|
|
63
|
+
|
|
64
|
+
expect(result).toEqual([]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('matches multiple extensions with braces', async () => {
|
|
68
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file1.ts'), '');
|
|
69
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file2.tsx'), '');
|
|
70
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file3.js'), '');
|
|
71
|
+
|
|
72
|
+
const result = (await glob.executor(
|
|
73
|
+
{ pattern: '*.{ts,tsx}' },
|
|
74
|
+
{ rootDir: TEST_DIR }
|
|
75
|
+
)) as string[];
|
|
76
|
+
|
|
77
|
+
expect(result).toHaveLength(2);
|
|
78
|
+
expect(result).toContain('file1.ts');
|
|
79
|
+
expect(result).toContain('file2.tsx');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('matches files starting with specific prefix', async () => {
|
|
83
|
+
fs.writeFileSync(path.join(TEST_DIR, 'test-one.ts'), '');
|
|
84
|
+
fs.writeFileSync(path.join(TEST_DIR, 'test-two.ts'), '');
|
|
85
|
+
fs.writeFileSync(path.join(TEST_DIR, 'other.ts'), '');
|
|
86
|
+
|
|
87
|
+
const result = (await glob.executor({ pattern: 'test-*.ts' }, { rootDir: TEST_DIR })) as string[];
|
|
88
|
+
|
|
89
|
+
expect(result).toHaveLength(2);
|
|
90
|
+
expect(result).toContain('test-one.ts');
|
|
91
|
+
expect(result).toContain('test-two.ts');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('matches single character with ?', async () => {
|
|
95
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file1.ts'), '');
|
|
96
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file2.ts'), '');
|
|
97
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file10.ts'), '');
|
|
98
|
+
|
|
99
|
+
const result = (await glob.executor({ pattern: 'file?.ts' }, { rootDir: TEST_DIR })) as string[];
|
|
100
|
+
|
|
101
|
+
expect(result).toHaveLength(2);
|
|
102
|
+
expect(result).toContain('file1.ts');
|
|
103
|
+
expect(result).toContain('file2.ts');
|
|
104
|
+
expect(result).not.toContain('file10.ts');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Tests for the grep tool
|
|
2
|
+
|
|
3
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
4
|
+
import { grep } from './tools';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
|
|
8
|
+
const TEST_DIR = path.join(__dirname, '.test-workspace-grep');
|
|
9
|
+
|
|
10
|
+
interface GrepMatch {
|
|
11
|
+
file: string;
|
|
12
|
+
line: number;
|
|
13
|
+
match: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('grep tool', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
if (fs.existsSync(TEST_DIR)) {
|
|
23
|
+
fs.rmSync(TEST_DIR, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('finds matches in single file', async () => {
|
|
28
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
29
|
+
fs.writeFileSync(filePath, 'const foo = 1;\nconst bar = 2;\nconst foobar = 3;');
|
|
30
|
+
|
|
31
|
+
const result = (await grep.executor(
|
|
32
|
+
{ pattern: 'foo', path: 'test.ts' },
|
|
33
|
+
{ rootDir: TEST_DIR }
|
|
34
|
+
)) as GrepMatch[];
|
|
35
|
+
|
|
36
|
+
expect(result).toHaveLength(2);
|
|
37
|
+
expect(result[0]).toEqual({ file: 'test.ts', line: 1, match: 'foo' });
|
|
38
|
+
expect(result[1]).toEqual({ file: 'test.ts', line: 3, match: 'foo' });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('searches directory recursively', async () => {
|
|
42
|
+
fs.mkdirSync(path.join(TEST_DIR, 'src'), { recursive: true });
|
|
43
|
+
fs.writeFileSync(path.join(TEST_DIR, 'root.ts'), 'const target = 1;');
|
|
44
|
+
fs.writeFileSync(path.join(TEST_DIR, 'src', 'index.ts'), 'const target = 2;');
|
|
45
|
+
|
|
46
|
+
const result = (await grep.executor({ pattern: 'target', path: '.' }, { rootDir: TEST_DIR })) as GrepMatch[];
|
|
47
|
+
|
|
48
|
+
expect(result).toHaveLength(2);
|
|
49
|
+
expect(result.some((m) => m.file === 'root.ts')).toBe(true);
|
|
50
|
+
expect(result.some((m) => m.file.includes('index.ts'))).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('supports regex patterns', async () => {
|
|
54
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
55
|
+
fs.writeFileSync(filePath, 'const foo123 = 1;\nconst bar = 2;\nconst foo456 = 3;');
|
|
56
|
+
|
|
57
|
+
const result = (await grep.executor(
|
|
58
|
+
{ pattern: 'foo\\d+', path: 'test.ts' },
|
|
59
|
+
{ rootDir: TEST_DIR }
|
|
60
|
+
)) as GrepMatch[];
|
|
61
|
+
|
|
62
|
+
expect(result).toHaveLength(2);
|
|
63
|
+
expect(result[0].match).toBe('foo123');
|
|
64
|
+
expect(result[1].match).toBe('foo456');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('case insensitive search with ignoreCase', async () => {
|
|
68
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
69
|
+
fs.writeFileSync(filePath, 'const FOO = 1;\nconst foo = 2;\nconst Foo = 3;');
|
|
70
|
+
|
|
71
|
+
const result = (await grep.executor(
|
|
72
|
+
{ pattern: 'foo', path: 'test.ts', ignoreCase: true },
|
|
73
|
+
{ rootDir: TEST_DIR }
|
|
74
|
+
)) as GrepMatch[];
|
|
75
|
+
|
|
76
|
+
expect(result).toHaveLength(3);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('case sensitive by default', async () => {
|
|
80
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
81
|
+
fs.writeFileSync(filePath, 'const FOO = 1;\nconst foo = 2;\nconst Foo = 3;');
|
|
82
|
+
|
|
83
|
+
const result = (await grep.executor(
|
|
84
|
+
{ pattern: 'foo', path: 'test.ts' },
|
|
85
|
+
{ rootDir: TEST_DIR }
|
|
86
|
+
)) as GrepMatch[];
|
|
87
|
+
|
|
88
|
+
expect(result).toHaveLength(1);
|
|
89
|
+
expect(result[0].line).toBe(2);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('returns empty array when no matches', async () => {
|
|
93
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
94
|
+
fs.writeFileSync(filePath, 'const bar = 1;');
|
|
95
|
+
|
|
96
|
+
const result = (await grep.executor(
|
|
97
|
+
{ pattern: 'foo', path: 'test.ts' },
|
|
98
|
+
{ rootDir: TEST_DIR }
|
|
99
|
+
)) as GrepMatch[];
|
|
100
|
+
|
|
101
|
+
expect(result).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('finds multiple matches on same line', async () => {
|
|
105
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
106
|
+
fs.writeFileSync(filePath, 'foo bar foo baz foo');
|
|
107
|
+
|
|
108
|
+
const result = (await grep.executor(
|
|
109
|
+
{ pattern: 'foo', path: 'test.ts' },
|
|
110
|
+
{ rootDir: TEST_DIR }
|
|
111
|
+
)) as GrepMatch[];
|
|
112
|
+
|
|
113
|
+
expect(result).toHaveLength(3);
|
|
114
|
+
expect(result.every((m) => m.line === 1)).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('handles special regex characters when escaped', async () => {
|
|
118
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
119
|
+
fs.writeFileSync(filePath, 'const x = a.b;\nconst y = a*b;');
|
|
120
|
+
|
|
121
|
+
// Search for literal "a.b" (escaped dot)
|
|
122
|
+
const result = (await grep.executor(
|
|
123
|
+
{ pattern: 'a\\.b', path: 'test.ts' },
|
|
124
|
+
{ rootDir: TEST_DIR }
|
|
125
|
+
)) as GrepMatch[];
|
|
126
|
+
|
|
127
|
+
expect(result).toHaveLength(1);
|
|
128
|
+
expect(result[0].match).toBe('a.b');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('returns correct line numbers (1-based)', async () => {
|
|
132
|
+
const filePath = path.join(TEST_DIR, 'test.ts');
|
|
133
|
+
fs.writeFileSync(filePath, 'line1\nline2\ntarget\nline4\ntarget');
|
|
134
|
+
|
|
135
|
+
const result = (await grep.executor(
|
|
136
|
+
{ pattern: 'target', path: 'test.ts' },
|
|
137
|
+
{ rootDir: TEST_DIR }
|
|
138
|
+
)) as GrepMatch[];
|
|
139
|
+
|
|
140
|
+
expect(result).toHaveLength(2);
|
|
141
|
+
expect(result[0].line).toBe(3);
|
|
142
|
+
expect(result[1].line).toBe(5);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Standard library functions for Vibe scripts
|
|
2
|
+
// Import with: import { uuid } from "system"
|
|
3
|
+
//
|
|
4
|
+
// These are TypeScript functions that can be called directly from Vibe scripts.
|
|
5
|
+
// For AI tools, use: import { allTools } from "system/tools"
|
|
6
|
+
//
|
|
7
|
+
// NOTE: print() and env() are auto-imported core functions.
|
|
8
|
+
// They are available everywhere without import and CANNOT be imported from "system".
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate a UUID v4.
|
|
12
|
+
* @returns A new UUID string
|
|
13
|
+
*/
|
|
14
|
+
export function uuid(): string {
|
|
15
|
+
return crypto.randomUUID();
|
|
16
|
+
}
|