@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,374 @@
1
+ import { describe, test, expect, beforeEach } from 'bun:test';
2
+ import { parse } from '../../parser/parse';
3
+ import { createInitialState } from '../../runtime/state';
4
+ import { createDebugState, setBreakpoints, getCurrentLocation } from '../state';
5
+ import { debugStep, debugContinue, runWithDebug } from '../runner';
6
+ import {
7
+ registerTsBlock,
8
+ getTsBlockMapping,
9
+ mapTsLocationToVibe,
10
+ mapVibeLocationToTs,
11
+ isLocationInTsBlock,
12
+ clearTsBlockMappings,
13
+ } from '../ts-source-map';
14
+ import {
15
+ createHandoffState,
16
+ shouldInitiateHandoff,
17
+ initiateHandoff,
18
+ isInTsMode,
19
+ getCurrentMode,
20
+ } from '../handoff-manager';
21
+ import {
22
+ createDebugExecutionContext,
23
+ enterTsBlock,
24
+ mergeStackFrames,
25
+ getUnifiedStackTrace,
26
+ } from '../stack-merger';
27
+ import type { RuntimeEvent } from '@vibe-lang/debug-core';
28
+ import type { AIProvider, AIExecutionResult } from '../../runtime';
29
+
30
+ // Mock AI provider
31
+ const mockAIProvider: AIProvider = {
32
+ async execute(prompt: string): Promise<AIExecutionResult> {
33
+ return { value: `Mock: ${prompt.slice(0, 20)}...` };
34
+ },
35
+ async generateCode(prompt: string): Promise<AIExecutionResult> {
36
+ return { value: '// mock' };
37
+ },
38
+ async askUser(prompt: string): Promise<string> {
39
+ return 'mock input';
40
+ },
41
+ };
42
+
43
+ describe('TS Block Debug Integration', () => {
44
+ beforeEach(() => {
45
+ clearTsBlockMappings();
46
+ });
47
+
48
+ describe('Source mapping for ts blocks in real code', () => {
49
+ test('parses ts block location from AST', () => {
50
+ const code = `
51
+ let x = 5
52
+ let result = ts(x) {
53
+ return x * 2
54
+ }
55
+ `;
56
+ const ast = parse(code, { file: '/test.vibe' });
57
+
58
+ // Find the ts block in the AST
59
+ const tsBlock = ast.body.find(
60
+ (stmt: any) => stmt.type === 'LetDeclaration' && stmt.initializer?.type === 'TsBlock'
61
+ );
62
+
63
+ expect(tsBlock).toBeDefined();
64
+ const tsBlockExpr = (tsBlock as any).initializer;
65
+ expect(tsBlockExpr.type).toBe('TsBlock');
66
+ expect(tsBlockExpr.body).toContain('return x * 2');
67
+
68
+ // Register the ts block for source mapping
69
+ const location = tsBlockExpr.location ?? { file: '/test.vibe', line: 3, column: 14 };
70
+ const id = registerTsBlock(
71
+ '/test.vibe',
72
+ location,
73
+ tsBlockExpr.body,
74
+ tsBlockExpr.params // params are already strings
75
+ );
76
+
77
+ const mapping = getTsBlockMapping(id);
78
+ expect(mapping).toBeDefined();
79
+ expect(mapping?.tsBody).toContain('return x * 2');
80
+ });
81
+
82
+ test('maps ts block line to vibe line correctly', () => {
83
+ // Simulate a ts block at line 10 of a .vibe file
84
+ const tsBody = `const doubled = x * 2
85
+ const tripled = x * 3
86
+ return doubled + tripled`;
87
+
88
+ const id = registerTsBlock(
89
+ '/app.vibe',
90
+ { file: '/app.vibe', line: 10, column: 5 },
91
+ tsBody,
92
+ ['x']
93
+ );
94
+
95
+ const mapping = getTsBlockMapping(id)!;
96
+
97
+ // Line 1 of TS (after 'use strict') -> line 10 of Vibe
98
+ expect(mapTsLocationToVibe(mapping, 1, 0).line).toBe(10);
99
+
100
+ // Line 2 of TS -> line 11 of Vibe
101
+ expect(mapTsLocationToVibe(mapping, 2, 0).line).toBe(11);
102
+
103
+ // Line 3 of TS -> line 12 of Vibe
104
+ expect(mapTsLocationToVibe(mapping, 3, 0).line).toBe(12);
105
+ });
106
+
107
+ test('detects when vibe location is inside ts block', () => {
108
+ const tsBody = `line1
109
+ line2
110
+ line3`;
111
+
112
+ registerTsBlock(
113
+ '/test.vibe',
114
+ { file: '/test.vibe', line: 20, column: 1 },
115
+ tsBody,
116
+ []
117
+ );
118
+
119
+ // Lines 20-22 should be in the TS block
120
+ expect(isLocationInTsBlock('/test.vibe', 20)).not.toBeNull();
121
+ expect(isLocationInTsBlock('/test.vibe', 21)).not.toBeNull();
122
+ expect(isLocationInTsBlock('/test.vibe', 22)).not.toBeNull();
123
+
124
+ // Lines outside should not be
125
+ expect(isLocationInTsBlock('/test.vibe', 19)).toBeNull();
126
+ expect(isLocationInTsBlock('/test.vibe', 23)).toBeNull();
127
+ });
128
+ });
129
+
130
+ describe('Handoff detection for ts blocks', () => {
131
+ test('detects pending ts block execution', () => {
132
+ const code = `
133
+ let x = 10
134
+ let y = ts(x) {
135
+ return x + 1
136
+ }
137
+ `;
138
+ const ast = parse(code, { file: '/test.vibe' });
139
+ let runtimeState = createInitialState(ast);
140
+ let handoffState = createHandoffState();
141
+
142
+ // Initially no handoff needed
143
+ let result = shouldInitiateHandoff(runtimeState, handoffState);
144
+ expect(result.shouldHandoff).toBe(false);
145
+
146
+ // Simulate state where ts block is pending
147
+ const pendingState = {
148
+ ...runtimeState,
149
+ status: 'awaiting_ts' as const,
150
+ pendingTS: {
151
+ params: ['x'],
152
+ body: 'return x + 1',
153
+ paramValues: [10],
154
+ location: { file: '/test.vibe', line: 3, column: 9 },
155
+ },
156
+ };
157
+
158
+ result = shouldInitiateHandoff(pendingState as any, handoffState);
159
+ expect(result.shouldHandoff).toBe(true);
160
+ expect(result.reason).toBe('ts_block');
161
+ });
162
+
163
+ test('initiates handoff and enters ts mode', () => {
164
+ const code = 'let x = 1';
165
+ const ast = parse(code);
166
+ const runtimeState = {
167
+ ...createInitialState(ast),
168
+ status: 'awaiting_ts' as const,
169
+ pendingTS: {
170
+ params: ['x'],
171
+ body: 'return x * 2',
172
+ paramValues: [5],
173
+ location: { file: '/test.vibe', line: 5, column: 1 },
174
+ },
175
+ callStack: [{ locals: {}, functionName: 'main' }],
176
+ };
177
+ const debugState = createDebugState();
178
+ let handoffState = createHandoffState();
179
+
180
+ expect(getCurrentMode(handoffState)).toBe('vibe');
181
+
182
+ handoffState = initiateHandoff(runtimeState as any, debugState, handoffState, 'ts_block');
183
+
184
+ expect(getCurrentMode(handoffState)).toBe('typescript');
185
+ expect(isInTsMode(handoffState)).toBe(true);
186
+ expect(handoffState.context.currentTsBlockId).toBeTruthy();
187
+ });
188
+ });
189
+
190
+ describe('Stack frame merging', () => {
191
+ test('merges vibe and ts stack frames', () => {
192
+ const vibeFrames = [
193
+ { id: 0, name: 'main', source: { file: '/app.vibe', line: 1, column: 1 }, isVibeCode: true },
194
+ { id: 1, name: 'process', source: { file: '/app.vibe', line: 10, column: 1 }, isVibeCode: true },
195
+ ];
196
+
197
+ const tsFrames = [
198
+ { id: 0, name: 'calculate', source: { file: '/utils.ts', line: 5, column: 1 }, isVibeCode: false },
199
+ ];
200
+
201
+ // In Vibe mode, only vibe frames
202
+ let context = createDebugExecutionContext();
203
+ let merged = mergeStackFrames(vibeFrames, tsFrames, context);
204
+ expect(merged.length).toBe(2);
205
+ expect(merged.every(f => f.origin === 'vibe')).toBe(true);
206
+
207
+ // In TS mode, ts frames on top + vibe frames below
208
+ context = enterTsBlock(context, 'ts_1', 1);
209
+ merged = mergeStackFrames(vibeFrames, tsFrames, context);
210
+ expect(merged[0].origin).toBe('typescript');
211
+ expect(merged[0].name).toBe('calculate');
212
+ });
213
+
214
+ test('unified stack trace includes both vibe and ts frames', () => {
215
+ const code = 'let x = 1';
216
+ const ast = parse(code, { file: '/test.vibe' });
217
+ const runtimeState = {
218
+ ...createInitialState(ast),
219
+ callStack: [
220
+ { locals: {}, functionName: 'main', currentLocation: { file: '/test.vibe', line: 1, column: 1 } },
221
+ ],
222
+ };
223
+
224
+ const tsFrames = [
225
+ { id: 0, name: 'tsHelper', source: { file: 'ts_block', line: 1, column: 1 }, isVibeCode: false },
226
+ ];
227
+
228
+ let context = createDebugExecutionContext();
229
+ context = enterTsBlock(context, 'ts_1', 1);
230
+
231
+ const { stackFrames, totalFrames } = getUnifiedStackTrace(runtimeState as any, tsFrames, context);
232
+
233
+ expect(totalFrames).toBeGreaterThan(0);
234
+ // Should have ts frame on top
235
+ expect(stackFrames[0].origin).toBe('typescript');
236
+ });
237
+ });
238
+
239
+ describe('Breakpoints in ts blocks', () => {
240
+ test('can set breakpoint at ts block location', () => {
241
+ // Register a ts block
242
+ registerTsBlock(
243
+ '/test.vibe',
244
+ { file: '/test.vibe', line: 10, column: 1 },
245
+ 'return x + 1',
246
+ ['x']
247
+ );
248
+
249
+ let debugState = createDebugState();
250
+
251
+ // Set breakpoint at line 10 (start of ts block)
252
+ const { debugState: newState, breakpoints } = setBreakpoints(
253
+ debugState,
254
+ '/test.vibe',
255
+ [10]
256
+ );
257
+
258
+ expect(breakpoints.length).toBe(1);
259
+ expect(breakpoints[0].line).toBe(10);
260
+ expect(breakpoints[0].verified).toBe(true);
261
+
262
+ // Verify breakpoint is associated with ts block location
263
+ const mapping = isLocationInTsBlock('/test.vibe', 10);
264
+ expect(mapping).not.toBeNull();
265
+ });
266
+
267
+ test('maps vibe breakpoint to ts line for bun inspector', () => {
268
+ const id = registerTsBlock(
269
+ '/test.vibe',
270
+ { file: '/test.vibe', line: 10, column: 1 },
271
+ 'const a = 1\nconst b = 2\nreturn a + b',
272
+ []
273
+ );
274
+
275
+ const mapping = getTsBlockMapping(id)!;
276
+
277
+ // Breakpoint at vibe line 11 (second line of ts block)
278
+ const tsLocation = mapVibeLocationToTs(mapping, 11, 0);
279
+ expect(tsLocation).not.toBeNull();
280
+ expect(tsLocation?.line).toBe(2); // Second line in TS (after 'use strict')
281
+ });
282
+ });
283
+
284
+ describe('Debug session with ts block', () => {
285
+ test('debug session handles ts block execution', async () => {
286
+ const code = `
287
+ let x = 5
288
+ let doubled = ts(x) {
289
+ return x * 2
290
+ }
291
+ let result = doubled
292
+ `;
293
+ const ast = parse(code, { file: '/test.vibe' });
294
+ let runtimeState = createInitialState(ast);
295
+ let debugState = createDebugState();
296
+
297
+ const events: RuntimeEvent[] = [];
298
+
299
+ // Run the debug session
300
+ const result = await runWithDebug(
301
+ runtimeState,
302
+ debugState,
303
+ mockAIProvider,
304
+ (e) => events.push(e)
305
+ );
306
+
307
+ // Should complete (ts block should be handled)
308
+ expect(['completed', 'error'].includes(result.runtimeState.status)).toBe(true);
309
+ });
310
+
311
+ test('stepping through code with ts block', () => {
312
+ const code = `
313
+ let x = 1
314
+ let y = 2
315
+ let z = x + y
316
+ `;
317
+ const ast = parse(code, { file: '/test.vibe' });
318
+ let runtimeState = createInitialState(ast);
319
+ let debugState = createDebugState();
320
+
321
+ // Step through each statement
322
+ let stepCount = 0;
323
+ while (runtimeState.status === 'running' && stepCount < 20) {
324
+ const result = debugStep(runtimeState, debugState);
325
+ runtimeState = result.runtimeState;
326
+ debugState = result.debugState;
327
+ stepCount++;
328
+ }
329
+
330
+ expect(runtimeState.status).toBe('completed');
331
+ });
332
+ });
333
+ });
334
+
335
+ describe('Imported TS Function Debug Integration', () => {
336
+ test('detects pending imported ts function call', () => {
337
+ const code = 'let x = 1';
338
+ const ast = parse(code);
339
+ const runtimeState = {
340
+ ...createInitialState(ast),
341
+ status: 'awaiting_ts' as const,
342
+ pendingImportedTsCall: {
343
+ funcName: 'calculateSum',
344
+ args: [1, 2, 3],
345
+ },
346
+ };
347
+ const handoffState = createHandoffState();
348
+
349
+ const result = shouldInitiateHandoff(runtimeState as any, handoffState);
350
+ expect(result.shouldHandoff).toBe(true);
351
+ expect(result.reason).toBe('ts_import');
352
+ });
353
+
354
+ test('initiates handoff for ts import', () => {
355
+ const code = 'let x = 1';
356
+ const ast = parse(code);
357
+ const runtimeState = {
358
+ ...createInitialState(ast),
359
+ status: 'awaiting_ts' as const,
360
+ pendingImportedTsCall: {
361
+ funcName: 'myTsFunction',
362
+ args: [42],
363
+ },
364
+ callStack: [{ locals: {} }],
365
+ };
366
+ const debugState = createDebugState();
367
+ let handoffState = createHandoffState();
368
+
369
+ handoffState = initiateHandoff(runtimeState as any, debugState, handoffState, 'ts_import');
370
+
371
+ expect(isInTsMode(handoffState)).toBe(true);
372
+ expect(handoffState.context.currentTsImport).toBe('myTsFunction');
373
+ });
374
+ });
@@ -0,0 +1,125 @@
1
+ import { describe, test, expect, beforeEach } from 'bun:test';
2
+ import {
3
+ registerTsImport,
4
+ getTsImportInfo,
5
+ isTsImportCall,
6
+ setTsImportEntryLine,
7
+ registerTempBreakpoint,
8
+ popTempBreakpoint,
9
+ clearTempBreakpoints,
10
+ getAllTsImports,
11
+ getTsImportsForFile,
12
+ clearTsImports,
13
+ buildTsEntryPointId,
14
+ parseTsEntryPointId,
15
+ } from '../ts-import-tracker';
16
+
17
+ describe('TS Import Tracker', () => {
18
+ beforeEach(() => {
19
+ clearTsImports();
20
+ });
21
+
22
+ describe('registerTsImport', () => {
23
+ test('registers a TS import', () => {
24
+ registerTsImport('add', '/utils.ts', 'add', '/main.vibe', 5);
25
+
26
+ const info = getTsImportInfo('/main.vibe', 'add');
27
+ expect(info).toBeDefined();
28
+ expect(info?.vibeName).toBe('add');
29
+ expect(info?.tsFile).toBe('/utils.ts');
30
+ expect(info?.tsFunctionName).toBe('add');
31
+ expect(info?.importedBy).toBe('/main.vibe');
32
+ expect(info?.importLine).toBe(5);
33
+ });
34
+ });
35
+
36
+ describe('isTsImportCall', () => {
37
+ test('returns true for registered import', () => {
38
+ registerTsImport('calculate', '/math.ts', 'calculate', '/app.vibe', 10);
39
+
40
+ expect(isTsImportCall('/app.vibe', 'calculate')).toBe(true);
41
+ });
42
+
43
+ test('returns false for unregistered function', () => {
44
+ expect(isTsImportCall('/app.vibe', 'unknown')).toBe(false);
45
+ });
46
+ });
47
+
48
+ describe('setTsImportEntryLine', () => {
49
+ test('sets entry line for registered import', () => {
50
+ registerTsImport('foo', '/foo.ts', 'foo', '/main.vibe', 1);
51
+ setTsImportEntryLine('/main.vibe', 'foo', 42);
52
+
53
+ const info = getTsImportInfo('/main.vibe', 'foo');
54
+ expect(info?.entryLine).toBe(42);
55
+ });
56
+ });
57
+
58
+ describe('temporary breakpoints', () => {
59
+ test('registers and pops temp breakpoint', () => {
60
+ registerTempBreakpoint('/utils.ts', 10, 'bp_123');
61
+
62
+ const id = popTempBreakpoint('/utils.ts', 10);
63
+ expect(id).toBe('bp_123');
64
+
65
+ // Should be gone after pop
66
+ const id2 = popTempBreakpoint('/utils.ts', 10);
67
+ expect(id2).toBeUndefined();
68
+ });
69
+
70
+ test('clearTempBreakpoints returns all and clears', () => {
71
+ registerTempBreakpoint('/a.ts', 5, 'bp_1');
72
+ registerTempBreakpoint('/b.ts', 10, 'bp_2');
73
+
74
+ const all = clearTempBreakpoints();
75
+ expect(all.size).toBe(2);
76
+ expect(all.get('/a.ts:5')).toBe('bp_1');
77
+ expect(all.get('/b.ts:10')).toBe('bp_2');
78
+
79
+ // Should be empty now
80
+ const empty = clearTempBreakpoints();
81
+ expect(empty.size).toBe(0);
82
+ });
83
+ });
84
+
85
+ describe('getAllTsImports', () => {
86
+ test('returns all registered imports', () => {
87
+ registerTsImport('a', '/a.ts', 'a', '/main.vibe', 1);
88
+ registerTsImport('b', '/b.ts', 'b', '/main.vibe', 2);
89
+
90
+ const all = getAllTsImports();
91
+ expect(all.length).toBe(2);
92
+ });
93
+ });
94
+
95
+ describe('getTsImportsForFile', () => {
96
+ test('returns imports for specific file', () => {
97
+ registerTsImport('a', '/a.ts', 'a', '/main.vibe', 1);
98
+ registerTsImport('b', '/b.ts', 'b', '/other.vibe', 2);
99
+ registerTsImport('c', '/c.ts', 'c', '/main.vibe', 3);
100
+
101
+ const mainImports = getTsImportsForFile('/main.vibe');
102
+ expect(mainImports.length).toBe(2);
103
+ expect(mainImports.map(i => i.vibeName).sort()).toEqual(['a', 'c']);
104
+ });
105
+ });
106
+
107
+ describe('entry point ID helpers', () => {
108
+ test('buildTsEntryPointId creates unique ID', () => {
109
+ const id = buildTsEntryPointId('/utils.ts', 'calculate');
110
+ expect(id).toBe('ts:/utils.ts:calculate');
111
+ });
112
+
113
+ test('parseTsEntryPointId extracts components', () => {
114
+ const parsed = parseTsEntryPointId('ts:/path/to/file.ts:myFunc');
115
+ expect(parsed).not.toBeNull();
116
+ expect(parsed?.tsFile).toBe('/path/to/file.ts');
117
+ expect(parsed?.functionName).toBe('myFunc');
118
+ });
119
+
120
+ test('parseTsEntryPointId returns null for invalid format', () => {
121
+ expect(parseTsEntryPointId('invalid')).toBeNull();
122
+ expect(parseTsEntryPointId('wrong:format')).toBeNull();
123
+ });
124
+ });
125
+ });
@@ -0,0 +1,169 @@
1
+ import { describe, test, expect, beforeEach } from 'bun:test';
2
+ import {
3
+ registerTsBlock,
4
+ getTsBlockMapping,
5
+ setScriptId,
6
+ findMappingByScriptId,
7
+ mapTsLocationToVibe,
8
+ mapVibeLocationToTs,
9
+ isLocationInTsBlock,
10
+ getMappingsForFile,
11
+ clearTsBlockMappings,
12
+ getAllMappings,
13
+ } from '../ts-source-map';
14
+
15
+ describe('TS Block Source Mapping', () => {
16
+ beforeEach(() => {
17
+ clearTsBlockMappings();
18
+ });
19
+
20
+ describe('registerTsBlock', () => {
21
+ test('registers a TS block and returns unique ID', () => {
22
+ const id = registerTsBlock(
23
+ '/test.vibe',
24
+ { file: '/test.vibe', line: 10, column: 5 },
25
+ 'return x + y',
26
+ ['x', 'y']
27
+ );
28
+
29
+ expect(id).toMatch(/^ts_block_\d+$/);
30
+
31
+ const mapping = getTsBlockMapping(id);
32
+ expect(mapping).toBeDefined();
33
+ expect(mapping?.vibeFile).toBe('/test.vibe');
34
+ expect(mapping?.vibeStartLine).toBe(10);
35
+ expect(mapping?.vibeStartColumn).toBe(5);
36
+ expect(mapping?.tsBody).toBe('return x + y');
37
+ expect(mapping?.params).toEqual(['x', 'y']);
38
+ });
39
+
40
+ test('generates unique IDs for each block', () => {
41
+ const id1 = registerTsBlock('/a.vibe', { file: '/a.vibe', line: 1, column: 1 }, 'a', []);
42
+ const id2 = registerTsBlock('/b.vibe', { file: '/b.vibe', line: 2, column: 1 }, 'b', []);
43
+
44
+ expect(id1).not.toBe(id2);
45
+ });
46
+ });
47
+
48
+ describe('script ID association', () => {
49
+ test('associates script ID with mapping', () => {
50
+ const id = registerTsBlock('/test.vibe', { file: '/test.vibe', line: 5, column: 1 }, 'code', []);
51
+
52
+ setScriptId(id, 'script_123');
53
+
54
+ const mapping = getTsBlockMapping(id);
55
+ expect(mapping?.scriptId).toBe('script_123');
56
+ });
57
+
58
+ test('finds mapping by script ID', () => {
59
+ const id = registerTsBlock('/test.vibe', { file: '/test.vibe', line: 5, column: 1 }, 'code', []);
60
+ setScriptId(id, 'script_456');
61
+
62
+ const found = findMappingByScriptId('script_456');
63
+ expect(found).toBeDefined();
64
+ expect(found?.vibeStartLine).toBe(5);
65
+ });
66
+
67
+ test('returns undefined for unknown script ID', () => {
68
+ const found = findMappingByScriptId('unknown');
69
+ expect(found).toBeUndefined();
70
+ });
71
+ });
72
+
73
+ describe('mapTsLocationToVibe', () => {
74
+ test('maps TS line to Vibe line', () => {
75
+ const id = registerTsBlock('/test.vibe', { file: '/test.vibe', line: 10, column: 3 }, 'line1\nline2\nline3', []);
76
+ const mapping = getTsBlockMapping(id)!;
77
+
78
+ // Line 1 of TS (after 'use strict') maps to line 10 of Vibe
79
+ const loc1 = mapTsLocationToVibe(mapping, 1, 0);
80
+ expect(loc1.line).toBe(10);
81
+ expect(loc1.column).toBe(3);
82
+
83
+ // Line 2 of TS maps to line 11 of Vibe
84
+ const loc2 = mapTsLocationToVibe(mapping, 2, 5);
85
+ expect(loc2.line).toBe(11);
86
+ expect(loc2.column).toBe(5);
87
+ });
88
+ });
89
+
90
+ describe('mapVibeLocationToTs', () => {
91
+ test('maps Vibe location to TS location', () => {
92
+ const id = registerTsBlock('/test.vibe', { file: '/test.vibe', line: 10, column: 3 }, 'line1\nline2\nline3', []);
93
+ const mapping = getTsBlockMapping(id)!;
94
+
95
+ // Vibe line 10 maps to TS line 1 (after 'use strict')
96
+ const ts1 = mapVibeLocationToTs(mapping, 10, 5);
97
+ expect(ts1).not.toBeNull();
98
+ expect(ts1?.line).toBe(1);
99
+ expect(ts1?.column).toBe(2); // 5 - 3 start column
100
+
101
+ // Vibe line 11 maps to TS line 2
102
+ const ts2 = mapVibeLocationToTs(mapping, 11, 0);
103
+ expect(ts2).not.toBeNull();
104
+ expect(ts2?.line).toBe(2);
105
+ });
106
+
107
+ test('returns null for location outside TS block', () => {
108
+ const id = registerTsBlock('/test.vibe', { file: '/test.vibe', line: 10, column: 1 }, 'single', []);
109
+ const mapping = getTsBlockMapping(id)!;
110
+
111
+ expect(mapVibeLocationToTs(mapping, 5, 0)).toBeNull();
112
+ expect(mapVibeLocationToTs(mapping, 15, 0)).toBeNull();
113
+ });
114
+ });
115
+
116
+ describe('isLocationInTsBlock', () => {
117
+ test('returns mapping when location is in TS block', () => {
118
+ registerTsBlock('/test.vibe', { file: '/test.vibe', line: 10, column: 1 }, 'line1\nline2\nline3', []);
119
+
120
+ expect(isLocationInTsBlock('/test.vibe', 10)).not.toBeNull();
121
+ expect(isLocationInTsBlock('/test.vibe', 11)).not.toBeNull();
122
+ expect(isLocationInTsBlock('/test.vibe', 12)).not.toBeNull();
123
+ });
124
+
125
+ test('returns null when location is outside TS block', () => {
126
+ registerTsBlock('/test.vibe', { file: '/test.vibe', line: 10, column: 1 }, 'single', []);
127
+
128
+ expect(isLocationInTsBlock('/test.vibe', 5)).toBeNull();
129
+ expect(isLocationInTsBlock('/test.vibe', 15)).toBeNull();
130
+ });
131
+
132
+ test('returns null for different file', () => {
133
+ registerTsBlock('/a.vibe', { file: '/a.vibe', line: 10, column: 1 }, 'code', []);
134
+
135
+ expect(isLocationInTsBlock('/b.vibe', 10)).toBeNull();
136
+ });
137
+ });
138
+
139
+ describe('getMappingsForFile', () => {
140
+ test('returns all mappings for a file', () => {
141
+ registerTsBlock('/a.vibe', { file: '/a.vibe', line: 5, column: 1 }, 'block1', []);
142
+ registerTsBlock('/a.vibe', { file: '/a.vibe', line: 15, column: 1 }, 'block2', []);
143
+ registerTsBlock('/b.vibe', { file: '/b.vibe', line: 10, column: 1 }, 'other', []);
144
+
145
+ const mappings = getMappingsForFile('/a.vibe');
146
+ expect(mappings.length).toBe(2);
147
+ expect(mappings.map(m => m.vibeStartLine).sort((a, b) => a - b)).toEqual([5, 15]);
148
+ });
149
+
150
+ test('returns empty array for file with no TS blocks', () => {
151
+ registerTsBlock('/a.vibe', { file: '/a.vibe', line: 5, column: 1 }, 'code', []);
152
+
153
+ expect(getMappingsForFile('/c.vibe')).toEqual([]);
154
+ });
155
+ });
156
+
157
+ describe('clearTsBlockMappings', () => {
158
+ test('clears all mappings', () => {
159
+ registerTsBlock('/a.vibe', { file: '/a.vibe', line: 1, column: 1 }, 'a', []);
160
+ registerTsBlock('/b.vibe', { file: '/b.vibe', line: 2, column: 1 }, 'b', []);
161
+
162
+ expect(getAllMappings().size).toBe(2);
163
+
164
+ clearTsBlockMappings();
165
+
166
+ expect(getAllMappings().size).toBe(0);
167
+ });
168
+ });
169
+ });