@vibe-lang/runtime 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/package.json +46 -0
  2. package/src/ast/index.ts +375 -0
  3. package/src/ast.ts +2 -0
  4. package/src/debug/advanced-features.ts +482 -0
  5. package/src/debug/bun-inspector.ts +424 -0
  6. package/src/debug/handoff-manager.ts +283 -0
  7. package/src/debug/index.ts +150 -0
  8. package/src/debug/runner.ts +365 -0
  9. package/src/debug/server.ts +565 -0
  10. package/src/debug/stack-merger.ts +267 -0
  11. package/src/debug/state.ts +581 -0
  12. package/src/debug/test/advanced-features.test.ts +300 -0
  13. package/src/debug/test/e2e.test.ts +218 -0
  14. package/src/debug/test/handoff-manager.test.ts +256 -0
  15. package/src/debug/test/runner.test.ts +256 -0
  16. package/src/debug/test/stack-merger.test.ts +163 -0
  17. package/src/debug/test/state.test.ts +400 -0
  18. package/src/debug/test/ts-debug-integration.test.ts +374 -0
  19. package/src/debug/test/ts-import-tracker.test.ts +125 -0
  20. package/src/debug/test/ts-source-map.test.ts +169 -0
  21. package/src/debug/ts-import-tracker.ts +151 -0
  22. package/src/debug/ts-source-map.ts +171 -0
  23. package/src/errors/index.ts +124 -0
  24. package/src/index.ts +358 -0
  25. package/src/lexer/index.ts +348 -0
  26. package/src/lexer.ts +2 -0
  27. package/src/parser/index.ts +792 -0
  28. package/src/parser/parse.ts +45 -0
  29. package/src/parser/test/async.test.ts +248 -0
  30. package/src/parser/test/destructuring.test.ts +167 -0
  31. package/src/parser/test/do-expression.test.ts +486 -0
  32. package/src/parser/test/errors/do-expression.test.ts +95 -0
  33. package/src/parser/test/errors/error-locations.test.ts +230 -0
  34. package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
  35. package/src/parser/test/errors/missing-tokens.test.ts +126 -0
  36. package/src/parser/test/errors/model-declaration.test.ts +185 -0
  37. package/src/parser/test/errors/nested-blocks.test.ts +226 -0
  38. package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
  39. package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
  40. package/src/parser/test/import-export.test.ts +143 -0
  41. package/src/parser/test/literals.test.ts +404 -0
  42. package/src/parser/test/model-declaration.test.ts +161 -0
  43. package/src/parser/test/nested-blocks.test.ts +402 -0
  44. package/src/parser/test/parser.test.ts +743 -0
  45. package/src/parser/test/private.test.ts +136 -0
  46. package/src/parser/test/template-literal.test.ts +127 -0
  47. package/src/parser/test/tool-declaration.test.ts +302 -0
  48. package/src/parser/test/ts-block.test.ts +252 -0
  49. package/src/parser/test/type-annotations.test.ts +254 -0
  50. package/src/parser/visitor/helpers.ts +330 -0
  51. package/src/parser/visitor.ts +794 -0
  52. package/src/parser.ts +2 -0
  53. package/src/runtime/ai/cache-chunking.test.ts +69 -0
  54. package/src/runtime/ai/cache-chunking.ts +73 -0
  55. package/src/runtime/ai/client.ts +109 -0
  56. package/src/runtime/ai/context.ts +168 -0
  57. package/src/runtime/ai/formatters.ts +316 -0
  58. package/src/runtime/ai/index.ts +38 -0
  59. package/src/runtime/ai/language-ref.ts +38 -0
  60. package/src/runtime/ai/providers/anthropic.ts +253 -0
  61. package/src/runtime/ai/providers/google.ts +201 -0
  62. package/src/runtime/ai/providers/openai.ts +156 -0
  63. package/src/runtime/ai/retry.ts +100 -0
  64. package/src/runtime/ai/return-tools.ts +301 -0
  65. package/src/runtime/ai/test/client.test.ts +83 -0
  66. package/src/runtime/ai/test/formatters.test.ts +485 -0
  67. package/src/runtime/ai/test/retry.test.ts +137 -0
  68. package/src/runtime/ai/test/return-tools.test.ts +450 -0
  69. package/src/runtime/ai/test/tool-loop.test.ts +319 -0
  70. package/src/runtime/ai/test/tool-schema.test.ts +241 -0
  71. package/src/runtime/ai/tool-loop.ts +203 -0
  72. package/src/runtime/ai/tool-schema.ts +151 -0
  73. package/src/runtime/ai/types.ts +113 -0
  74. package/src/runtime/ai-logger.ts +255 -0
  75. package/src/runtime/ai-provider.ts +347 -0
  76. package/src/runtime/async/dependencies.ts +276 -0
  77. package/src/runtime/async/executor.ts +293 -0
  78. package/src/runtime/async/index.ts +43 -0
  79. package/src/runtime/async/scheduling.ts +163 -0
  80. package/src/runtime/async/test/dependencies.test.ts +284 -0
  81. package/src/runtime/async/test/executor.test.ts +388 -0
  82. package/src/runtime/context.ts +357 -0
  83. package/src/runtime/exec/ai.ts +139 -0
  84. package/src/runtime/exec/expressions.ts +475 -0
  85. package/src/runtime/exec/frames.ts +26 -0
  86. package/src/runtime/exec/functions.ts +305 -0
  87. package/src/runtime/exec/interpolation.ts +312 -0
  88. package/src/runtime/exec/statements.ts +604 -0
  89. package/src/runtime/exec/tools.ts +129 -0
  90. package/src/runtime/exec/typescript.ts +215 -0
  91. package/src/runtime/exec/variables.ts +279 -0
  92. package/src/runtime/index.ts +975 -0
  93. package/src/runtime/modules.ts +452 -0
  94. package/src/runtime/serialize.ts +103 -0
  95. package/src/runtime/state.ts +489 -0
  96. package/src/runtime/stdlib/core.ts +45 -0
  97. package/src/runtime/stdlib/directory.test.ts +156 -0
  98. package/src/runtime/stdlib/edit.test.ts +154 -0
  99. package/src/runtime/stdlib/fastEdit.test.ts +201 -0
  100. package/src/runtime/stdlib/glob.test.ts +106 -0
  101. package/src/runtime/stdlib/grep.test.ts +144 -0
  102. package/src/runtime/stdlib/index.ts +16 -0
  103. package/src/runtime/stdlib/readFile.test.ts +123 -0
  104. package/src/runtime/stdlib/tools/index.ts +707 -0
  105. package/src/runtime/stdlib/writeFile.test.ts +157 -0
  106. package/src/runtime/step.ts +969 -0
  107. package/src/runtime/test/ai-context.test.ts +1086 -0
  108. package/src/runtime/test/ai-result-object.test.ts +419 -0
  109. package/src/runtime/test/ai-tool-flow.test.ts +859 -0
  110. package/src/runtime/test/async-execution-order.test.ts +618 -0
  111. package/src/runtime/test/async-execution.test.ts +344 -0
  112. package/src/runtime/test/async-nested.test.ts +660 -0
  113. package/src/runtime/test/async-parallel-timing.test.ts +546 -0
  114. package/src/runtime/test/basic1.test.ts +154 -0
  115. package/src/runtime/test/binary-operators.test.ts +431 -0
  116. package/src/runtime/test/break-statement.test.ts +257 -0
  117. package/src/runtime/test/context-modes.test.ts +650 -0
  118. package/src/runtime/test/context.test.ts +466 -0
  119. package/src/runtime/test/core-functions.test.ts +228 -0
  120. package/src/runtime/test/e2e.test.ts +88 -0
  121. package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
  122. package/src/runtime/test/error-locations/main-error.vibe +4 -0
  123. package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
  124. package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
  125. package/src/runtime/test/for-in.test.ts +312 -0
  126. package/src/runtime/test/helpers.ts +69 -0
  127. package/src/runtime/test/imports.test.ts +334 -0
  128. package/src/runtime/test/json-expressions.test.ts +232 -0
  129. package/src/runtime/test/literals.test.ts +372 -0
  130. package/src/runtime/test/logical-indexing.test.ts +478 -0
  131. package/src/runtime/test/member-methods.test.ts +324 -0
  132. package/src/runtime/test/model-config.test.ts +338 -0
  133. package/src/runtime/test/null-handling.test.ts +342 -0
  134. package/src/runtime/test/private-visibility.test.ts +332 -0
  135. package/src/runtime/test/runtime-state.test.ts +514 -0
  136. package/src/runtime/test/scoping.test.ts +370 -0
  137. package/src/runtime/test/string-interpolation.test.ts +354 -0
  138. package/src/runtime/test/template-literal.test.ts +181 -0
  139. package/src/runtime/test/tool-execution.test.ts +467 -0
  140. package/src/runtime/test/tool-schema-generation.test.ts +477 -0
  141. package/src/runtime/test/tostring.test.ts +210 -0
  142. package/src/runtime/test/ts-block.test.ts +594 -0
  143. package/src/runtime/test/ts-error-location.test.ts +231 -0
  144. package/src/runtime/test/types.test.ts +732 -0
  145. package/src/runtime/test/verbose-logger.test.ts +710 -0
  146. package/src/runtime/test/vibe-expression.test.ts +54 -0
  147. package/src/runtime/test/vibe-value-errors.test.ts +541 -0
  148. package/src/runtime/test/while.test.ts +232 -0
  149. package/src/runtime/tools/builtin.ts +30 -0
  150. package/src/runtime/tools/directory-tools.ts +70 -0
  151. package/src/runtime/tools/file-tools.ts +228 -0
  152. package/src/runtime/tools/index.ts +5 -0
  153. package/src/runtime/tools/registry.ts +48 -0
  154. package/src/runtime/tools/search-tools.ts +134 -0
  155. package/src/runtime/tools/security.ts +36 -0
  156. package/src/runtime/tools/system-tools.ts +312 -0
  157. package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
  158. package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
  159. package/src/runtime/tools/test/registry.test.ts +713 -0
  160. package/src/runtime/tools/test/security.test.ts +86 -0
  161. package/src/runtime/tools/test/system-tools.test.ts +679 -0
  162. package/src/runtime/tools/test/ts-schema.test.ts +357 -0
  163. package/src/runtime/tools/ts-schema.ts +341 -0
  164. package/src/runtime/tools/types.ts +89 -0
  165. package/src/runtime/tools/utility-tools.ts +198 -0
  166. package/src/runtime/ts-eval.ts +126 -0
  167. package/src/runtime/types.ts +797 -0
  168. package/src/runtime/validation.ts +160 -0
  169. package/src/runtime/verbose-logger.ts +459 -0
  170. package/src/runtime.ts +2 -0
  171. package/src/semantic/analyzer-context.ts +62 -0
  172. package/src/semantic/analyzer-validators.ts +575 -0
  173. package/src/semantic/analyzer-visitors.ts +534 -0
  174. package/src/semantic/analyzer.ts +83 -0
  175. package/src/semantic/index.ts +11 -0
  176. package/src/semantic/symbol-table.ts +58 -0
  177. package/src/semantic/test/async-validation.test.ts +301 -0
  178. package/src/semantic/test/compress-validation.test.ts +179 -0
  179. package/src/semantic/test/const-reassignment.test.ts +111 -0
  180. package/src/semantic/test/control-flow.test.ts +346 -0
  181. package/src/semantic/test/destructuring.test.ts +185 -0
  182. package/src/semantic/test/duplicate-declarations.test.ts +168 -0
  183. package/src/semantic/test/export-validation.test.ts +111 -0
  184. package/src/semantic/test/fixtures/math.ts +31 -0
  185. package/src/semantic/test/imports.test.ts +148 -0
  186. package/src/semantic/test/json-type.test.ts +68 -0
  187. package/src/semantic/test/literals.test.ts +127 -0
  188. package/src/semantic/test/model-validation.test.ts +179 -0
  189. package/src/semantic/test/prompt-validation.test.ts +343 -0
  190. package/src/semantic/test/scoping.test.ts +312 -0
  191. package/src/semantic/test/tool-validation.test.ts +306 -0
  192. package/src/semantic/test/ts-type-checking.test.ts +563 -0
  193. package/src/semantic/test/type-constraints.test.ts +111 -0
  194. package/src/semantic/test/type-inference.test.ts +87 -0
  195. package/src/semantic/test/type-validation.test.ts +552 -0
  196. package/src/semantic/test/undefined-variables.test.ts +163 -0
  197. package/src/semantic/ts-block-checker.ts +204 -0
  198. package/src/semantic/ts-signatures.ts +194 -0
  199. package/src/semantic/ts-types.ts +170 -0
  200. package/src/semantic/types.ts +58 -0
  201. package/tests/fixtures/conditional-logic.vibe +14 -0
  202. package/tests/fixtures/function-call.vibe +16 -0
  203. package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
  204. package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
  205. package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
  206. package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
  207. package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
  208. package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
  209. package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
  210. package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
  211. package/tests/fixtures/imports/nested-import/main.vibe +3 -0
  212. package/tests/fixtures/imports/nested-import/utils.ts +3 -0
  213. package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
  214. package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
  215. package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
  216. package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
  217. package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
  218. package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
  219. package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
  220. package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
  221. package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
  222. package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
  223. package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
  224. package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
  225. package/tests/fixtures/imports/ts-import/main.vibe +4 -0
  226. package/tests/fixtures/imports/ts-import/math.ts +9 -0
  227. package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
  228. package/tests/fixtures/imports/ts-variables/data.ts +10 -0
  229. package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
  230. package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
  231. package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
  232. package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
  233. package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
  234. package/tests/fixtures/multiple-ai-calls.vibe +10 -0
  235. package/tests/fixtures/simple-greeting.vibe +6 -0
  236. package/tests/fixtures/template-literals.vibe +11 -0
  237. package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
  238. package/tests/integration/basic-ai/basic-ai.vibe +12 -0
  239. package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
  240. package/tests/integration/bug-fix/buggy-code.ts +22 -0
  241. package/tests/integration/bug-fix/fix-bug.vibe +21 -0
  242. package/tests/integration/compress/compress.integration.test.ts +206 -0
  243. package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
  244. package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
  245. package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
  246. package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
  247. package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
  248. package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
  249. package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
  250. package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
@@ -0,0 +1,134 @@
1
+ import type { RegisteredTool, ToolContext } from './types';
2
+ import { validatePathInSandbox } from './security';
3
+
4
+ /**
5
+ * File search tools: glob, grep
6
+ */
7
+ export const searchTools = [
8
+ {
9
+ name: 'glob',
10
+ kind: 'builtin',
11
+ schema: {
12
+ name: 'glob',
13
+ description: 'Find files matching a glob pattern.',
14
+ parameters: [
15
+ {
16
+ name: 'pattern',
17
+ type: { type: 'string' },
18
+ description: 'The glob pattern (e.g., "**/*.ts", "src/*.js")',
19
+ required: true,
20
+ },
21
+ {
22
+ name: 'cwd',
23
+ type: { type: 'string' },
24
+ description: 'Working directory for the search (default: root directory)',
25
+ required: false,
26
+ },
27
+ ],
28
+ returns: { type: 'array', items: { type: 'string' } },
29
+ },
30
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
31
+ const pattern = args.pattern as string;
32
+ const inputCwd = args.cwd as string | undefined;
33
+ const rootDir = context?.rootDir ?? process.cwd();
34
+ const cwd = inputCwd ? validatePathInSandbox(inputCwd, rootDir) : rootDir;
35
+
36
+ const glob = new Bun.Glob(pattern);
37
+ const matches: string[] = [];
38
+
39
+ for await (const file of glob.scan({ cwd })) {
40
+ matches.push(file);
41
+ }
42
+
43
+ return matches;
44
+ },
45
+ },
46
+
47
+ {
48
+ name: 'grep',
49
+ kind: 'builtin',
50
+ schema: {
51
+ name: 'grep',
52
+ description: 'Search file contents for a pattern.',
53
+ parameters: [
54
+ {
55
+ name: 'pattern',
56
+ type: { type: 'string' },
57
+ description: 'The search pattern (regex)',
58
+ required: true,
59
+ },
60
+ {
61
+ name: 'path',
62
+ type: { type: 'string' },
63
+ description: 'File or directory path to search',
64
+ required: true,
65
+ },
66
+ {
67
+ name: 'ignoreCase',
68
+ type: { type: 'boolean' },
69
+ description: 'Ignore case in pattern matching',
70
+ required: false,
71
+ },
72
+ ],
73
+ returns: {
74
+ type: 'array',
75
+ items: {
76
+ type: 'object',
77
+ properties: {
78
+ file: { type: 'string' },
79
+ line: { type: 'number' },
80
+ match: { type: 'string' },
81
+ },
82
+ },
83
+ },
84
+ },
85
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
86
+ const pattern = args.pattern as string;
87
+ const inputPath = args.path as string;
88
+ const ignoreCase = args.ignoreCase as boolean | undefined;
89
+ const rootDir = context?.rootDir ?? process.cwd();
90
+ const safePath = validatePathInSandbox(inputPath, rootDir);
91
+
92
+ const fs = await import('fs/promises');
93
+ const pathModule = await import('path');
94
+ const regex = new RegExp(pattern, ignoreCase ? 'gi' : 'g');
95
+
96
+ const results: Array<{ file: string; line: number; match: string }> = [];
97
+
98
+ async function searchFile(filePath: string) {
99
+ const content = await Bun.file(filePath).text();
100
+ const lines = content.split('\n');
101
+
102
+ for (let i = 0; i < lines.length; i++) {
103
+ const matches = lines[i].match(regex);
104
+ if (matches) {
105
+ for (const match of matches) {
106
+ results.push({
107
+ file: pathModule.relative(rootDir, filePath),
108
+ line: i + 1,
109
+ match,
110
+ });
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ const stats = await fs.stat(safePath);
117
+ if (stats.isDirectory()) {
118
+ // Search all files in directory recursively
119
+ const glob = new Bun.Glob('**/*');
120
+ for await (const file of glob.scan({ cwd: safePath })) {
121
+ const fullPath = pathModule.join(safePath, file);
122
+ const fileStats = await fs.stat(fullPath);
123
+ if (fileStats.isFile()) {
124
+ await searchFile(fullPath);
125
+ }
126
+ }
127
+ } else {
128
+ await searchFile(safePath);
129
+ }
130
+
131
+ return results;
132
+ },
133
+ },
134
+ ] satisfies RegisteredTool[];
@@ -0,0 +1,36 @@
1
+ import { resolve, normalize } from 'path';
2
+
3
+ /**
4
+ * Validates that a path is within the allowed root directory.
5
+ * Throws an error if the path escapes the sandbox.
6
+ *
7
+ * @param inputPath - The path to validate (can be relative or absolute)
8
+ * @param rootDir - The root directory that acts as the sandbox
9
+ * @returns The normalized absolute path within the sandbox
10
+ * @throws Error if the path is outside the allowed directory
11
+ */
12
+ export function validatePathInSandbox(inputPath: string, rootDir: string): string {
13
+ // Resolve the path relative to the root directory
14
+ const resolved = resolve(rootDir, inputPath);
15
+ const normalized = normalize(resolved);
16
+ const normalizedRoot = normalize(rootDir);
17
+
18
+ // Ensure the resolved path starts with the root directory
19
+ // Add path separator to prevent partial matches (e.g., /foo vs /foobar)
20
+ const rootWithSep = normalizedRoot.endsWith('/') || normalizedRoot.endsWith('\\')
21
+ ? normalizedRoot
22
+ : normalizedRoot + (process.platform === 'win32' ? '\\' : '/');
23
+
24
+ const pathWithSep = normalized.endsWith('/') || normalized.endsWith('\\')
25
+ ? normalized
26
+ : normalized + (process.platform === 'win32' ? '\\' : '/');
27
+
28
+ // Check if the path is exactly the root or starts with root + separator
29
+ const isWithinRoot = normalized === normalizedRoot || pathWithSep.startsWith(rootWithSep);
30
+
31
+ if (!isWithinRoot) {
32
+ throw new Error(`Path '${inputPath}' is outside the allowed directory`);
33
+ }
34
+
35
+ return normalized;
36
+ }
@@ -0,0 +1,312 @@
1
+ import { mkdir, writeFile, readdir, rm } from 'fs/promises';
2
+ import { join } from 'path';
3
+ import { tmpdir } from 'os';
4
+ import type { ToolContext, RegisteredTool } from './types';
5
+
6
+ /**
7
+ * System tools: bash, runCode
8
+ *
9
+ * These tools provide shell command execution and sandboxed code execution.
10
+ */
11
+
12
+ // Counter for unique bash script filenames
13
+ let bashScriptCounter = 0;
14
+
15
+ // Mutex for run folder creation to prevent race conditions
16
+ let runFolderLock: Promise<void> = Promise.resolve();
17
+
18
+ /**
19
+ * Bash tool - Execute shell commands using Bun's cross-platform shell.
20
+ *
21
+ * Uses a temp script file executed with `bun run` to run commands through
22
+ * Bun's shell, which works cross-platform (Windows/Mac/Linux) without
23
+ * requiring Git Bash or WSL on Windows.
24
+ */
25
+ const bashTool: RegisteredTool = {
26
+ name: 'bash',
27
+ kind: 'builtin',
28
+ schema: {
29
+ name: 'bash',
30
+ description:
31
+ 'Execute a shell command and return stdout, stderr, and exit code. ' +
32
+ 'Works cross-platform (Windows/Mac/Linux) without requiring Git Bash. ' +
33
+ 'Supports pipes (cmd1 | cmd2), file redirection (> file, >> file), and standard shell features. ' +
34
+ 'Commands run from the project root directory by default.',
35
+ parameters: [
36
+ {
37
+ name: 'command',
38
+ type: { type: 'string' },
39
+ description: 'The shell command to execute (supports pipes, redirection, etc.)',
40
+ required: true,
41
+ },
42
+ {
43
+ name: 'cwd',
44
+ type: { type: 'string' },
45
+ description: 'Working directory for the command (defaults to project root)',
46
+ required: false,
47
+ },
48
+ {
49
+ name: 'timeout',
50
+ type: { type: 'number' },
51
+ description: 'Timeout in milliseconds (default: 30000). Process is killed if exceeded.',
52
+ required: false,
53
+ },
54
+ ],
55
+ returns: {
56
+ type: 'object',
57
+ properties: {
58
+ stdout: { type: 'string' },
59
+ stderr: { type: 'string' },
60
+ exitCode: { type: 'number' },
61
+ },
62
+ },
63
+ },
64
+ executor: async (
65
+ args: Record<string, unknown>,
66
+ context?: ToolContext
67
+ ): Promise<{ stdout: string; stderr: string; exitCode: number }> => {
68
+ const command = args.command as string;
69
+ const cwd = (args.cwd as string) || context?.rootDir || process.cwd();
70
+ const timeout = (args.timeout as number) || 30000;
71
+
72
+ // Create a temp script file that uses Bun's shell
73
+ // This avoids escaping issues with inline code
74
+ const scriptId = ++bashScriptCounter;
75
+ const scriptPath = join(tmpdir(), `vibe-bash-${process.pid}-${scriptId}.ts`);
76
+
77
+ // Escape the command for embedding in a template literal
78
+ const escapedCommand = command
79
+ .replace(/\\/g, '\\\\')
80
+ .replace(/`/g, '\\`')
81
+ .replace(/\$\{/g, '\\${');
82
+
83
+ const scriptContent = `import { $ } from 'bun';
84
+ const result = await $\`${escapedCommand}\`.cwd(${JSON.stringify(cwd)}).nothrow().quiet();
85
+ process.stdout.write(result.stdout);
86
+ process.stderr.write(result.stderr);
87
+ process.exit(result.exitCode);
88
+ `;
89
+
90
+ try {
91
+ await writeFile(scriptPath, scriptContent);
92
+
93
+ const proc = Bun.spawn(['bun', 'run', scriptPath], {
94
+ stdout: 'pipe',
95
+ stderr: 'pipe',
96
+ });
97
+
98
+ // Set up timeout to kill runaway processes
99
+ const timeoutId = setTimeout(() => proc.kill(), timeout);
100
+
101
+ // Wait for completion
102
+ const exitCode = await proc.exited;
103
+ clearTimeout(timeoutId);
104
+
105
+ const stdout = await new Response(proc.stdout).text();
106
+ const stderr = await new Response(proc.stderr).text();
107
+
108
+ return {
109
+ stdout,
110
+ stderr,
111
+ exitCode,
112
+ };
113
+ } finally {
114
+ // Clean up temp script
115
+ try {
116
+ await rm(scriptPath);
117
+ } catch {
118
+ // Ignore cleanup errors
119
+ }
120
+ }
121
+ },
122
+ };
123
+
124
+ /**
125
+ * Get the next run folder name (r1, r2, r3, etc.) and create it atomically.
126
+ * Uses a mutex to prevent race conditions when multiple runCode calls happen simultaneously.
127
+ */
128
+ async function getNextRunFolderAndCreate(cacheDir: string): Promise<string> {
129
+ // Use mutex to ensure only one folder creation at a time
130
+ let runName: string = '';
131
+
132
+ const operation = runFolderLock.then(async () => {
133
+ try {
134
+ await mkdir(cacheDir, { recursive: true });
135
+ const entries = await readdir(cacheDir);
136
+ const runNums = entries
137
+ .filter((e) => e.startsWith('r'))
138
+ .map((e) => parseInt(e.slice(1), 10))
139
+ .filter((n) => !isNaN(n));
140
+ const nextNum = runNums.length > 0 ? Math.max(...runNums) + 1 : 1;
141
+ runName = `r${nextNum}`;
142
+
143
+ // Create the directory while still holding the lock
144
+ await mkdir(join(cacheDir, runName), { recursive: true });
145
+ } catch {
146
+ // No cache dir yet, start at r1
147
+ runName = 'r1';
148
+ await mkdir(join(cacheDir, runName), { recursive: true });
149
+ }
150
+ });
151
+
152
+ // Update the lock to include this operation
153
+ runFolderLock = operation.catch(() => {});
154
+
155
+ await operation;
156
+ return runName;
157
+ }
158
+
159
+ /**
160
+ * Code execution tool - Run AI-generated TypeScript in a sandboxed subprocess.
161
+ *
162
+ * Each execution gets a unique folder in .vibe-cache/ (r1, r2, r3...)
163
+ * so the AI can reference files from previous runs.
164
+ *
165
+ * Working directory is set to the project root so relative paths work naturally.
166
+ */
167
+ const runCodeTool: RegisteredTool = {
168
+ name: 'runCode',
169
+ kind: 'builtin',
170
+ schema: {
171
+ name: 'runCode',
172
+ description:
173
+ 'Execute TypeScript/JavaScript code in a sandboxed subprocess. ' +
174
+ 'IMPORTANT: All scope variables are automatically available as local variables in your code - ' +
175
+ 'just use them directly (e.g., if scope has {items: [...], name: "test"}, you can write ' +
176
+ '`items.map(...)` or `name.toUpperCase()` without any setup). ' +
177
+ 'Working directory is the project root, so relative paths like "data/file.json" work naturally. ' +
178
+ 'Use `return value` to pass results back. Bun APIs (Bun.file, Bun.write, etc.) are available. ' +
179
+ 'Each execution creates a unique folder in .vibe-cache/ (r1, r2, r3...) for intermediate files.',
180
+ parameters: [
181
+ {
182
+ name: 'code',
183
+ type: { type: 'string' },
184
+ description:
185
+ 'TypeScript/JavaScript code to execute. Scope variables are already available as local ' +
186
+ 'variables - just use them directly. Use `return` to pass a result back.',
187
+ required: true,
188
+ },
189
+ {
190
+ name: 'scope',
191
+ type: { type: 'object', additionalProperties: true },
192
+ description:
193
+ 'Variables to make available in the code. Each key becomes a local variable. ' +
194
+ 'Example: {items: [1,2,3], name: "test"} makes `items` and `name` directly usable in code.',
195
+ required: false,
196
+ },
197
+ {
198
+ name: 'timeout',
199
+ type: { type: 'number' },
200
+ description: 'Timeout in milliseconds (default: 30000). Process is killed if exceeded.',
201
+ required: false,
202
+ },
203
+ ],
204
+ returns: {
205
+ type: 'object',
206
+ properties: {
207
+ result: { type: 'string' },
208
+ stdout: { type: 'string' },
209
+ stderr: { type: 'string' },
210
+ exitCode: { type: 'number' },
211
+ runFolder: { type: 'string' },
212
+ error: { type: 'string' },
213
+ },
214
+ },
215
+ },
216
+ executor: async (
217
+ args: Record<string, unknown>,
218
+ context?: ToolContext
219
+ ): Promise<{
220
+ result?: unknown;
221
+ stdout: string;
222
+ stderr: string;
223
+ exitCode: number;
224
+ runFolder: string;
225
+ error?: string;
226
+ }> => {
227
+ const code = args.code as string;
228
+ const scope = (args.scope as Record<string, unknown>) || {};
229
+ const timeout = (args.timeout as number) || 30000;
230
+
231
+ const projectDir = context?.rootDir || process.cwd();
232
+ const cacheDir = join(projectDir, '.vibe-cache');
233
+
234
+ // Get unique run folder (r1, r2, r3...) - mutex ensures no race conditions
235
+ const runName = await getNextRunFolderAndCreate(cacheDir);
236
+ const runDir = join(cacheDir, runName);
237
+ const runPath = `.vibe-cache/${runName}`; // Relative path for AI to use
238
+
239
+ const scopePath = join(runDir, 'scope.json');
240
+ const scriptPath = join(runDir, 'script.ts');
241
+
242
+ try {
243
+ // 1. Write scope to JSON file (directory already created by mutex)
244
+ await writeFile(scopePath, JSON.stringify(scope, null, 2));
245
+
246
+ // 3. Wrap code - AI reads scope from run folder
247
+ const scopeKeys = Object.keys(scope);
248
+ const destructure =
249
+ scopeKeys.length > 0 ? `const { ${scopeKeys.join(', ')} } = __scope;` : '';
250
+
251
+ const wrappedCode = `// Auto-generated by Vibe runtime - Run: ${runName}
252
+ // Scope: ${runPath}/scope.json
253
+ // Working directory: project root (relative paths work)
254
+
255
+ const __scope = JSON.parse(await Bun.file('${runPath}/scope.json').text());
256
+ ${destructure}
257
+
258
+ // AI-generated code
259
+ const __result = await (async () => {
260
+ ${code}
261
+ })();
262
+
263
+ console.log('__VIBE_RESULT__' + JSON.stringify(__result));
264
+ `;
265
+
266
+ await writeFile(scriptPath, wrappedCode);
267
+
268
+ // 4. Execute in subprocess
269
+ const proc = Bun.spawn(['bun', 'run', `${runPath}/script.ts`], {
270
+ stdout: 'pipe',
271
+ stderr: 'pipe',
272
+ cwd: projectDir,
273
+ });
274
+
275
+ // 5. Set up timeout to kill runaway processes
276
+ const timeoutId = setTimeout(() => proc.kill(), timeout);
277
+
278
+ // 6. Wait for completion
279
+ const exitCode = await proc.exited;
280
+ clearTimeout(timeoutId);
281
+
282
+ const stdout = await new Response(proc.stdout).text();
283
+ const stderr = await new Response(proc.stderr).text();
284
+
285
+ // 7. Parse result from stdout
286
+ let result: unknown;
287
+ const resultMatch = stdout.match(/__VIBE_RESULT__(.+)/);
288
+ if (resultMatch) {
289
+ try {
290
+ result = JSON.parse(resultMatch[1]);
291
+ } catch {
292
+ result = resultMatch[1];
293
+ }
294
+ }
295
+
296
+ const cleanStdout = stdout.replace(/__VIBE_RESULT__.+\n?/, '');
297
+ return { result, stdout: cleanStdout, stderr, exitCode, runFolder: runPath };
298
+ } catch (err) {
299
+ return {
300
+ stdout: '',
301
+ stderr: '',
302
+ exitCode: 1,
303
+ runFolder: runPath,
304
+ error: err instanceof Error ? err.message : String(err),
305
+ };
306
+ }
307
+ // Note: We don't delete .vibe-cache - useful for debugging
308
+ // AI can reference previous runs via .vibe-cache/r1/, r2/, etc.
309
+ },
310
+ };
311
+
312
+ export const systemTools: RegisteredTool[] = [bashTool, runCodeTool];
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Base types that get imported by test-types.ts
3
+ * Used to test cross-file type resolution.
4
+ */
5
+
6
+ /** A geographic address */
7
+ export interface Address {
8
+ street: string;
9
+ city: string;
10
+ zipCode: string;
11
+ country?: string;
12
+ }
13
+
14
+ /** Contact information */
15
+ export interface ContactInfo {
16
+ email: string;
17
+ phone?: string;
18
+ address: Address;
19
+ }
20
+
21
+ /** Generic metadata that can be attached to entities */
22
+ export interface Metadata {
23
+ /** When the entity was created */
24
+ createdAt: string;
25
+ /** When the entity was last updated */
26
+ updatedAt: string;
27
+ /** Optional tags for categorization */
28
+ tags?: string[];
29
+ }
30
+
31
+ /** Status values for orders */
32
+ export type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
33
+
34
+ /** A line item in an order */
35
+ export interface OrderItem {
36
+ productId: string;
37
+ productName: string;
38
+ quantity: number;
39
+ unitPrice: number;
40
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Test types for ts-schema extraction tests.
3
+ * Imports types from base-types.ts to test cross-file resolution.
4
+ */
5
+
6
+ import { Address, ContactInfo, Metadata, OrderStatus, OrderItem } from './base-types';
7
+
8
+ // ============================================================================
9
+ // Simple primitives
10
+ // ============================================================================
11
+
12
+ /** A person with basic info */
13
+ export interface Person {
14
+ name: string;
15
+ age: number;
16
+ active: boolean;
17
+ }
18
+
19
+ /** Configuration with optional fields */
20
+ export interface Config {
21
+ required: string;
22
+ optional?: number;
23
+ }
24
+
25
+ /** Container with array property */
26
+ export interface Container {
27
+ items: string[];
28
+ }
29
+
30
+ /** Simple type alias */
31
+ export type Status = string;
32
+
33
+ // ============================================================================
34
+ // Documented types (JSDoc extraction)
35
+ // ============================================================================
36
+
37
+ /** A documented entity with JSDoc on properties */
38
+ export interface Documented {
39
+ /** The user's unique identifier */
40
+ id: string;
41
+ /** The user's display name */
42
+ name: string;
43
+ }
44
+
45
+ // ============================================================================
46
+ // Types using imports from base-types.ts
47
+ // ============================================================================
48
+
49
+ /** A user with imported contact info */
50
+ export interface User {
51
+ id: string;
52
+ username: string;
53
+ contact: ContactInfo;
54
+ metadata: Metadata;
55
+ }
56
+
57
+ /** A customer with imported address */
58
+ export interface Customer {
59
+ customerId: string;
60
+ name: string;
61
+ email: string;
62
+ billingAddress: Address;
63
+ shippingAddress?: Address;
64
+ }
65
+
66
+ /** An order using imported types */
67
+ export interface Order {
68
+ orderId: string;
69
+ customerId: string;
70
+ status: OrderStatus;
71
+ items: OrderItem[];
72
+ totalAmount: number;
73
+ shippingAddress: Address;
74
+ notes?: string;
75
+ }
76
+
77
+ // ============================================================================
78
+ // Complex nested types
79
+ // ============================================================================
80
+
81
+ /** Deeply nested structure */
82
+ export interface Company {
83
+ name: string;
84
+ headquarters: Address;
85
+ employees: Employee[];
86
+ departments: Department[];
87
+ }
88
+
89
+ export interface Employee {
90
+ id: string;
91
+ name: string;
92
+ role: string;
93
+ contact: ContactInfo;
94
+ manager?: Employee;
95
+ }
96
+
97
+ export interface Department {
98
+ name: string;
99
+ head: Employee;
100
+ budget: number;
101
+ }
102
+
103
+ // ============================================================================
104
+ // Array types
105
+ // ============================================================================
106
+
107
+ /** Entity with various array types */
108
+ export interface ArrayTypes {
109
+ strings: string[];
110
+ numbers: number[];
111
+ booleans: boolean[];
112
+ nested: Address[];
113
+ matrix: number[][];
114
+ }
115
+
116
+ // ============================================================================
117
+ // Union and optional types
118
+ // ============================================================================
119
+
120
+ /** Type with unions */
121
+ export interface WithUnions {
122
+ id: string | number;
123
+ status: 'active' | 'inactive' | 'pending';
124
+ data: string | null;
125
+ }
126
+
127
+ /** Type with all optional fields */
128
+ export interface AllOptional {
129
+ field1?: string;
130
+ field2?: number;
131
+ field3?: boolean;
132
+ }