@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,710 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
2
+ import { VerboseLogger } from '../verbose-logger';
3
+ import { existsSync, rmSync, readFileSync, readdirSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { tmpdir } from 'os';
6
+
7
+ describe('VerboseLogger', () => {
8
+ let testDir: string;
9
+
10
+ beforeEach(() => {
11
+ // Create a unique temp directory for each test
12
+ testDir = join(tmpdir(), `vibe-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
13
+ });
14
+
15
+ afterEach(() => {
16
+ // Clean up test directory
17
+ if (existsSync(testDir)) {
18
+ rmSync(testDir, { recursive: true, force: true });
19
+ }
20
+ });
21
+
22
+ describe('initialization', () => {
23
+ test('creates logger with default options', () => {
24
+ const logger = new VerboseLogger({
25
+ logDir: testDir,
26
+ printToConsole: false,
27
+ writeToFile: false,
28
+ });
29
+
30
+ expect(logger).toBeDefined();
31
+ expect(logger.getEvents()).toEqual([]);
32
+ });
33
+
34
+ test('returns empty events initially', () => {
35
+ const logger = new VerboseLogger({
36
+ logDir: testDir,
37
+ printToConsole: false,
38
+ writeToFile: false,
39
+ });
40
+
41
+ expect(logger.getEvents()).toHaveLength(0);
42
+ });
43
+ });
44
+
45
+ describe('run lifecycle', () => {
46
+ test('logs run_start event', () => {
47
+ const logger = new VerboseLogger({
48
+ logDir: testDir,
49
+ printToConsole: false,
50
+ writeToFile: false,
51
+ });
52
+
53
+ logger.start('/test/file.vibe');
54
+
55
+ const events = logger.getEvents();
56
+ expect(events).toHaveLength(1);
57
+ expect(events[0].event).toBe('run_start');
58
+ expect((events[0] as any).file).toBe('/test/file.vibe');
59
+ expect(events[0].seq).toBe(1);
60
+ });
61
+
62
+ test('logs run_complete event on success', () => {
63
+ const logger = new VerboseLogger({
64
+ logDir: testDir,
65
+ printToConsole: false,
66
+ writeToFile: false,
67
+ });
68
+
69
+ logger.start('/test/file.vibe');
70
+ logger.complete('completed');
71
+
72
+ const events = logger.getEvents();
73
+ expect(events).toHaveLength(2);
74
+ expect(events[1].event).toBe('run_complete');
75
+ expect((events[1] as any).status).toBe('completed');
76
+ expect((events[1] as any).durationMs).toBeGreaterThanOrEqual(0);
77
+ });
78
+
79
+ test('logs run_complete event on error', () => {
80
+ const logger = new VerboseLogger({
81
+ logDir: testDir,
82
+ printToConsole: false,
83
+ writeToFile: false,
84
+ });
85
+
86
+ logger.start('/test/file.vibe');
87
+ logger.complete('error', 'Something went wrong');
88
+
89
+ const events = logger.getEvents();
90
+ expect(events).toHaveLength(2);
91
+ expect(events[1].event).toBe('run_complete');
92
+ expect((events[1] as any).status).toBe('error');
93
+ expect((events[1] as any).error).toBe('Something went wrong');
94
+ });
95
+ });
96
+
97
+ describe('AI call logging', () => {
98
+ test('generates sequential IDs for do calls', () => {
99
+ const logger = new VerboseLogger({
100
+ logDir: testDir,
101
+ printToConsole: false,
102
+ writeToFile: false,
103
+ });
104
+
105
+ const id1 = logger.aiStart('do', 'gpt-4', 'prompt1', {
106
+ model: 'gpt-4',
107
+ type: 'do',
108
+ targetType: 'text',
109
+ messages: [],
110
+ });
111
+
112
+ const id2 = logger.aiStart('do', 'gpt-4', 'prompt2', {
113
+ model: 'gpt-4',
114
+ type: 'do',
115
+ targetType: 'text',
116
+ messages: [],
117
+ });
118
+
119
+ expect(id1).toBe('do-000001');
120
+ expect(id2).toBe('do-000002');
121
+ });
122
+
123
+ test('generates sequential IDs for vibe calls', () => {
124
+ const logger = new VerboseLogger({
125
+ logDir: testDir,
126
+ printToConsole: false,
127
+ writeToFile: false,
128
+ });
129
+
130
+ const id1 = logger.aiStart('vibe', 'claude', 'prompt1', {
131
+ model: 'claude',
132
+ type: 'vibe',
133
+ targetType: 'text',
134
+ messages: [],
135
+ });
136
+
137
+ const id2 = logger.aiStart('vibe', 'claude', 'prompt2', {
138
+ model: 'claude',
139
+ type: 'vibe',
140
+ targetType: 'text',
141
+ messages: [],
142
+ });
143
+
144
+ expect(id1).toBe('vibe-000001');
145
+ expect(id2).toBe('vibe-000002');
146
+ });
147
+
148
+ test('logs ai_start and ai_complete events', () => {
149
+ const logger = new VerboseLogger({
150
+ logDir: testDir,
151
+ printToConsole: false,
152
+ writeToFile: false,
153
+ });
154
+
155
+ const id = logger.aiStart('do', 'gpt-4', 'What is 2+2?', {
156
+ model: 'gpt-4',
157
+ type: 'do',
158
+ targetType: 'number',
159
+ messages: [],
160
+ });
161
+
162
+ logger.aiComplete(id, 150, { inputTokens: 10, outputTokens: 5 }, 0);
163
+
164
+ const events = logger.getEvents();
165
+ expect(events).toHaveLength(2);
166
+
167
+ const startEvent = events[0] as any;
168
+ expect(startEvent.event).toBe('ai_start');
169
+ expect(startEvent.id).toBe('do-000001');
170
+ expect(startEvent.type).toBe('do');
171
+ expect(startEvent.model).toBe('gpt-4');
172
+
173
+ const completeEvent = events[1] as any;
174
+ expect(completeEvent.event).toBe('ai_complete');
175
+ expect(completeEvent.id).toBe('do-000001');
176
+ expect(completeEvent.durationMs).toBe(150);
177
+ expect(completeEvent.tokens).toEqual({ in: 10, out: 5 });
178
+ });
179
+
180
+ test('logs ai_complete with error', () => {
181
+ const logger = new VerboseLogger({
182
+ logDir: testDir,
183
+ printToConsole: false,
184
+ writeToFile: false,
185
+ });
186
+
187
+ const id = logger.aiStart('vibe', 'claude', 'Do something', {
188
+ model: 'claude',
189
+ type: 'vibe',
190
+ targetType: null,
191
+ messages: [],
192
+ });
193
+
194
+ logger.aiComplete(id, 100, undefined, 0, 'API rate limit exceeded');
195
+
196
+ const events = logger.getEvents();
197
+ const completeEvent = events[1] as any;
198
+ expect(completeEvent.error).toBe('API rate limit exceeded');
199
+ });
200
+
201
+ test('truncates long prompts in log events', () => {
202
+ const logger = new VerboseLogger({
203
+ logDir: testDir,
204
+ printToConsole: false,
205
+ writeToFile: false,
206
+ });
207
+
208
+ const longPrompt = 'A'.repeat(200);
209
+ logger.aiStart('do', 'gpt-4', longPrompt, {
210
+ model: 'gpt-4',
211
+ type: 'do',
212
+ targetType: null,
213
+ messages: [],
214
+ });
215
+
216
+ const events = logger.getEvents();
217
+ const startEvent = events[0] as any;
218
+ expect(startEvent.prompt.length).toBeLessThanOrEqual(103); // 100 chars + '...'
219
+ expect(startEvent.prompt.endsWith('...')).toBe(true);
220
+ });
221
+ });
222
+
223
+ describe('TS block logging', () => {
224
+ test('generates sequential IDs for ts blocks', () => {
225
+ const logger = new VerboseLogger({
226
+ logDir: testDir,
227
+ printToConsole: false,
228
+ writeToFile: false,
229
+ });
230
+
231
+ const id1 = logger.tsBlockStart(['x'], [42], 'return x * 2', { file: 'test.vibe', line: 5 });
232
+ const id2 = logger.tsBlockStart(['y'], [10], 'return y + 1', { file: 'test.vibe', line: 10 });
233
+
234
+ expect(id1).toBe('ts-000001');
235
+ expect(id2).toBe('ts-000002');
236
+ });
237
+
238
+ test('logs ts_start and ts_complete events for blocks', () => {
239
+ const logger = new VerboseLogger({
240
+ logDir: testDir,
241
+ printToConsole: false,
242
+ writeToFile: false,
243
+ });
244
+
245
+ const id = logger.tsBlockStart(['x', 'y'], [1, 2], 'return x + y', { file: 'test.vibe', line: 5 });
246
+ logger.tsBlockComplete(id, 10);
247
+
248
+ const events = logger.getEvents();
249
+ expect(events).toHaveLength(2);
250
+
251
+ const startEvent = events[0] as any;
252
+ expect(startEvent.event).toBe('ts_start');
253
+ expect(startEvent.id).toBe('ts-000001');
254
+ expect(startEvent.tsType).toBe('block');
255
+ expect(startEvent.params).toEqual(['x', 'y']);
256
+ expect(startEvent.location).toEqual({ file: 'test.vibe', line: 5 });
257
+
258
+ const completeEvent = events[1] as any;
259
+ expect(completeEvent.event).toBe('ts_complete');
260
+ expect(completeEvent.id).toBe('ts-000001');
261
+ expect(completeEvent.tsType).toBe('block');
262
+ expect(completeEvent.durationMs).toBe(10);
263
+ });
264
+
265
+ test('logs ts_complete with error', () => {
266
+ const logger = new VerboseLogger({
267
+ logDir: testDir,
268
+ printToConsole: false,
269
+ writeToFile: false,
270
+ });
271
+
272
+ const id = logger.tsBlockStart([], [], 'throw new Error("test")', { file: 'test.vibe', line: 1 });
273
+ logger.tsBlockComplete(id, 5, 'ReferenceError: x is not defined');
274
+
275
+ const events = logger.getEvents();
276
+ const completeEvent = events[1] as any;
277
+ expect(completeEvent.error).toBe('ReferenceError: x is not defined');
278
+ });
279
+ });
280
+
281
+ describe('TS function logging', () => {
282
+ test('generates sequential IDs for ts functions', () => {
283
+ const logger = new VerboseLogger({
284
+ logDir: testDir,
285
+ printToConsole: false,
286
+ writeToFile: false,
287
+ });
288
+
289
+ const id1 = logger.tsFunctionStart('readFile', ['/path'], { file: 'test.vibe', line: 1 });
290
+ const id2 = logger.tsFunctionStart('writeFile', ['/path', 'data'], { file: 'test.vibe', line: 2 });
291
+
292
+ expect(id1).toBe('tsf-000001');
293
+ expect(id2).toBe('tsf-000002');
294
+ });
295
+
296
+ test('logs ts_start and ts_complete events for functions', () => {
297
+ const logger = new VerboseLogger({
298
+ logDir: testDir,
299
+ printToConsole: false,
300
+ writeToFile: false,
301
+ });
302
+
303
+ const id = logger.tsFunctionStart('myFunc', ['arg1', 42], { file: 'test.vibe', line: 10 });
304
+ logger.tsFunctionComplete(id, 25);
305
+
306
+ const events = logger.getEvents();
307
+ expect(events).toHaveLength(2);
308
+
309
+ const startEvent = events[0] as any;
310
+ expect(startEvent.event).toBe('ts_start');
311
+ expect(startEvent.id).toBe('tsf-000001');
312
+ expect(startEvent.tsType).toBe('function');
313
+ expect(startEvent.name).toBe('myFunc');
314
+
315
+ const completeEvent = events[1] as any;
316
+ expect(completeEvent.event).toBe('ts_complete');
317
+ expect(completeEvent.id).toBe('tsf-000001');
318
+ expect(completeEvent.tsType).toBe('function');
319
+ });
320
+ });
321
+
322
+ describe('tool logging', () => {
323
+ test('logs tool_start and tool_complete events', () => {
324
+ const logger = new VerboseLogger({
325
+ logDir: testDir,
326
+ printToConsole: false,
327
+ writeToFile: false,
328
+ });
329
+
330
+ logger.toolStart('do-000001', 'readFile', { path: '/test.txt' });
331
+ logger.toolComplete('do-000001', 'readFile', 50, true);
332
+
333
+ const events = logger.getEvents();
334
+ expect(events).toHaveLength(2);
335
+
336
+ const startEvent = events[0] as any;
337
+ expect(startEvent.event).toBe('tool_start');
338
+ expect(startEvent.parentId).toBe('do-000001');
339
+ expect(startEvent.tool).toBe('readFile');
340
+ expect(startEvent.args).toEqual({ path: '/test.txt' });
341
+
342
+ const completeEvent = events[1] as any;
343
+ expect(completeEvent.event).toBe('tool_complete');
344
+ expect(completeEvent.parentId).toBe('do-000001');
345
+ expect(completeEvent.tool).toBe('readFile');
346
+ expect(completeEvent.durationMs).toBe(50);
347
+ expect(completeEvent.success).toBe(true);
348
+ });
349
+
350
+ test('logs tool_complete with error', () => {
351
+ const logger = new VerboseLogger({
352
+ logDir: testDir,
353
+ printToConsole: false,
354
+ writeToFile: false,
355
+ });
356
+
357
+ logger.toolStart('vibe-000001', 'writeFile', { path: '/readonly.txt', content: 'data' });
358
+ logger.toolComplete('vibe-000001', 'writeFile', 10, false, 'Permission denied');
359
+
360
+ const events = logger.getEvents();
361
+ const completeEvent = events[1] as any;
362
+ expect(completeEvent.success).toBe(false);
363
+ expect(completeEvent.error).toBe('Permission denied');
364
+ });
365
+ });
366
+
367
+ describe('file writing', () => {
368
+ test('writes JSONL log file', () => {
369
+ const logger = new VerboseLogger({
370
+ logDir: testDir,
371
+ printToConsole: false,
372
+ writeToFile: true,
373
+ });
374
+
375
+ logger.start('/test/file.vibe');
376
+ logger.complete('completed');
377
+
378
+ const logPath = logger.getMainLogPath();
379
+ expect(existsSync(logPath)).toBe(true);
380
+
381
+ const content = readFileSync(logPath, 'utf-8');
382
+ const lines = content.trim().split('\n');
383
+ expect(lines).toHaveLength(2);
384
+
385
+ // Verify each line is valid JSON
386
+ const event1 = JSON.parse(lines[0]);
387
+ const event2 = JSON.parse(lines[1]);
388
+ expect(event1.event).toBe('run_start');
389
+ expect(event2.event).toBe('run_complete');
390
+ });
391
+
392
+ test('creates context directory', () => {
393
+ const logger = new VerboseLogger({
394
+ logDir: testDir,
395
+ printToConsole: false,
396
+ writeToFile: true,
397
+ });
398
+
399
+ logger.aiStart('do', 'gpt-4', 'test prompt', {
400
+ model: 'gpt-4',
401
+ type: 'do',
402
+ targetType: 'text',
403
+ messages: [{ role: 'user', content: 'test prompt' }],
404
+ });
405
+
406
+ const contextDir = logger.getContextDir();
407
+ expect(existsSync(contextDir)).toBe(true);
408
+ });
409
+
410
+ test('writes AI context file', () => {
411
+ const logger = new VerboseLogger({
412
+ logDir: testDir,
413
+ printToConsole: false,
414
+ writeToFile: true,
415
+ });
416
+
417
+ logger.aiStart('do', 'gpt-4', 'What is 2+2?', {
418
+ model: 'gpt-4',
419
+ modelDetails: { name: 'gpt-4', provider: 'openai' },
420
+ type: 'do',
421
+ targetType: 'number',
422
+ messages: [
423
+ { role: 'system', content: 'You are a helpful assistant.' },
424
+ { role: 'user', content: 'What is 2+2?' },
425
+ ],
426
+ });
427
+
428
+ const contextDir = logger.getContextDir();
429
+ const contextFile = join(contextDir, 'do-000001.txt');
430
+ expect(existsSync(contextFile)).toBe(true);
431
+
432
+ const content = readFileSync(contextFile, 'utf-8');
433
+ expect(content).toContain('AI Call: do-000001');
434
+ expect(content).toContain('Model: gpt-4');
435
+ expect(content).toContain('[system]');
436
+ expect(content).toContain('You are a helpful assistant.');
437
+ expect(content).toContain('[user]');
438
+ expect(content).toContain('What is 2+2?');
439
+ });
440
+
441
+ test('writes TS block context file', () => {
442
+ const logger = new VerboseLogger({
443
+ logDir: testDir,
444
+ printToConsole: false,
445
+ writeToFile: true,
446
+ });
447
+
448
+ logger.tsBlockStart(['x', 'y'], [10, 20], 'return x + y;', { file: 'test.vibe', line: 5 });
449
+
450
+ const contextDir = logger.getContextDir();
451
+ const contextFile = join(contextDir, 'ts-000001.ts');
452
+ expect(existsSync(contextFile)).toBe(true);
453
+
454
+ const content = readFileSync(contextFile, 'utf-8');
455
+ expect(content).toContain('TS Block: ts-000001');
456
+ expect(content).toContain('Location: test.vibe:5');
457
+ expect(content).toContain('x = 10');
458
+ expect(content).toContain('y = 20');
459
+ expect(content).toContain('return x + y;');
460
+ });
461
+
462
+ test('writes TS function context file', () => {
463
+ const logger = new VerboseLogger({
464
+ logDir: testDir,
465
+ printToConsole: false,
466
+ writeToFile: true,
467
+ });
468
+
469
+ logger.tsFunctionStart('processData', [{ items: [1, 2, 3] }, 'transform'], { file: 'test.vibe', line: 10 });
470
+
471
+ const contextDir = logger.getContextDir();
472
+ const contextFile = join(contextDir, 'tsf-000001.ts');
473
+ expect(existsSync(contextFile)).toBe(true);
474
+
475
+ const content = readFileSync(contextFile, 'utf-8');
476
+ expect(content).toContain('TS Function Call: tsf-000001');
477
+ expect(content).toContain('Function: processData');
478
+ expect(content).toContain('Location: test.vibe:10');
479
+ expect(content).toContain('"items"');
480
+ });
481
+ });
482
+
483
+ describe('ID counter independence', () => {
484
+ test('do, vibe, ts, and tsf counters are independent', () => {
485
+ const logger = new VerboseLogger({
486
+ logDir: testDir,
487
+ printToConsole: false,
488
+ writeToFile: false,
489
+ });
490
+
491
+ // Create one of each type
492
+ const doId = logger.aiStart('do', 'gpt-4', 'prompt', {
493
+ model: 'gpt-4',
494
+ type: 'do',
495
+ targetType: null,
496
+ messages: [],
497
+ });
498
+
499
+ const vibeId = logger.aiStart('vibe', 'claude', 'prompt', {
500
+ model: 'claude',
501
+ type: 'vibe',
502
+ targetType: null,
503
+ messages: [],
504
+ });
505
+
506
+ const tsId = logger.tsBlockStart([], [], 'return 1', { file: 'test.vibe', line: 1 });
507
+ const tsfId = logger.tsFunctionStart('func', [], { file: 'test.vibe', line: 2 });
508
+
509
+ // All should be 000001 since counters are independent
510
+ expect(doId).toBe('do-000001');
511
+ expect(vibeId).toBe('vibe-000001');
512
+ expect(tsId).toBe('ts-000001');
513
+ expect(tsfId).toBe('tsf-000001');
514
+
515
+ // Create another of each
516
+ const doId2 = logger.aiStart('do', 'gpt-4', 'prompt', {
517
+ model: 'gpt-4',
518
+ type: 'do',
519
+ targetType: null,
520
+ messages: [],
521
+ });
522
+
523
+ const vibeId2 = logger.aiStart('vibe', 'claude', 'prompt', {
524
+ model: 'claude',
525
+ type: 'vibe',
526
+ targetType: null,
527
+ messages: [],
528
+ });
529
+
530
+ const tsId2 = logger.tsBlockStart([], [], 'return 2', { file: 'test.vibe', line: 3 });
531
+ const tsfId2 = logger.tsFunctionStart('func2', [], { file: 'test.vibe', line: 4 });
532
+
533
+ expect(doId2).toBe('do-000002');
534
+ expect(vibeId2).toBe('vibe-000002');
535
+ expect(tsId2).toBe('ts-000002');
536
+ expect(tsfId2).toBe('tsf-000002');
537
+ });
538
+ });
539
+
540
+ describe('sequence numbers', () => {
541
+ test('events have sequential seq numbers', () => {
542
+ const logger = new VerboseLogger({
543
+ logDir: testDir,
544
+ printToConsole: false,
545
+ writeToFile: false,
546
+ });
547
+
548
+ logger.start('/test/file.vibe');
549
+ logger.aiStart('do', 'gpt-4', 'prompt', {
550
+ model: 'gpt-4',
551
+ type: 'do',
552
+ targetType: null,
553
+ messages: [],
554
+ });
555
+ logger.aiComplete('do-000001', 100);
556
+ logger.complete('completed');
557
+
558
+ const events = logger.getEvents();
559
+ expect(events.map(e => e.seq)).toEqual([1, 2, 3, 4]);
560
+ });
561
+ });
562
+
563
+ describe('getMainLogPath and getContextDir', () => {
564
+ test('returns correct paths', () => {
565
+ const logger = new VerboseLogger({
566
+ logDir: testDir,
567
+ printToConsole: false,
568
+ writeToFile: true,
569
+ });
570
+
571
+ const mainLogPath = logger.getMainLogPath();
572
+ const contextDir = logger.getContextDir();
573
+
574
+ expect(mainLogPath).toContain(testDir);
575
+ expect(mainLogPath).toMatch(/run-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.jsonl$/);
576
+
577
+ expect(contextDir).toContain(testDir);
578
+ expect(contextDir).toMatch(/run-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/);
579
+
580
+ // Both should share the same timestamp
581
+ const logTimestamp = mainLogPath.match(/run-(.+)\.jsonl/)?.[1];
582
+ const dirTimestamp = contextDir.match(/run-(.+)$/)?.[1];
583
+ expect(logTimestamp).toBe(dirTimestamp);
584
+ });
585
+ });
586
+
587
+ describe('complete file structure', () => {
588
+ test('creates main log file, context subdirectory, and all context files', () => {
589
+ const logger = new VerboseLogger({
590
+ logDir: testDir,
591
+ printToConsole: false,
592
+ writeToFile: true,
593
+ });
594
+
595
+ // Simulate a complete run with multiple operation types
596
+ logger.start('/project/main.vibe');
597
+
598
+ // AI calls
599
+ const doId = logger.aiStart('do', 'gpt-4', 'Do task 1', {
600
+ model: 'gpt-4',
601
+ modelDetails: { name: 'gpt-4', provider: 'openai' },
602
+ type: 'do',
603
+ targetType: 'text',
604
+ messages: [{ role: 'user', content: 'Do task 1' }],
605
+ });
606
+ logger.aiComplete(doId, 100, { inputTokens: 10, outputTokens: 20 }, 0);
607
+
608
+ const vibeId = logger.aiStart('vibe', 'claude', 'Vibe task 1', {
609
+ model: 'claude',
610
+ modelDetails: { name: 'claude-3-sonnet', provider: 'anthropic' },
611
+ type: 'vibe',
612
+ targetType: 'json',
613
+ messages: [
614
+ { role: 'system', content: 'System prompt' },
615
+ { role: 'user', content: 'Vibe task 1' },
616
+ ],
617
+ });
618
+ logger.toolStart(vibeId, 'readFile', { path: '/data.json' });
619
+ logger.toolComplete(vibeId, 'readFile', 50, true);
620
+ logger.aiComplete(vibeId, 200, { inputTokens: 50, outputTokens: 100 }, 1);
621
+
622
+ // TS block
623
+ const tsId = logger.tsBlockStart(['x'], [42], 'return x * 2;', { file: '/project/main.vibe', line: 10 });
624
+ logger.tsBlockComplete(tsId, 5);
625
+
626
+ // TS function
627
+ const tsfId = logger.tsFunctionStart('processData', ['input'], { file: '/project/main.vibe', line: 15 });
628
+ logger.tsFunctionComplete(tsfId, 10);
629
+
630
+ logger.complete('completed');
631
+
632
+ // Verify main log directory exists
633
+ expect(existsSync(testDir)).toBe(true);
634
+
635
+ // Verify main log file exists and contains all events
636
+ const mainLogPath = logger.getMainLogPath();
637
+ expect(existsSync(mainLogPath)).toBe(true);
638
+
639
+ const logContent = readFileSync(mainLogPath, 'utf-8');
640
+ const logLines = logContent.trim().split('\n');
641
+ expect(logLines.length).toBeGreaterThanOrEqual(10); // At least: start, 2 ai_start, 2 ai_complete, tool_start, tool_complete, ts_start, ts_complete, tsf_start, tsf_complete, complete
642
+
643
+ // Verify each line is valid JSON
644
+ for (const line of logLines) {
645
+ expect(() => JSON.parse(line)).not.toThrow();
646
+ }
647
+
648
+ // Verify context subdirectory exists
649
+ const contextDir = logger.getContextDir();
650
+ expect(existsSync(contextDir)).toBe(true);
651
+
652
+ // Verify all context files exist
653
+ const contextFiles = readdirSync(contextDir);
654
+
655
+ // Should have: do-000001.txt, vibe-000001.txt, ts-000001.ts, tsf-000001.ts
656
+ expect(contextFiles).toContain('do-000001.txt');
657
+ expect(contextFiles).toContain('vibe-000001.txt');
658
+ expect(contextFiles).toContain('ts-000001.ts');
659
+ expect(contextFiles).toContain('tsf-000001.ts');
660
+ expect(contextFiles).toHaveLength(4);
661
+
662
+ // Verify context file contents
663
+ const doContent = readFileSync(join(contextDir, 'do-000001.txt'), 'utf-8');
664
+ expect(doContent).toContain('AI Call: do-000001');
665
+ expect(doContent).toContain('Model: gpt-4');
666
+ expect(doContent).toContain('Do task 1');
667
+
668
+ const vibeContent = readFileSync(join(contextDir, 'vibe-000001.txt'), 'utf-8');
669
+ expect(vibeContent).toContain('AI Call: vibe-000001');
670
+ expect(vibeContent).toContain('claude-3-sonnet');
671
+ expect(vibeContent).toContain('System prompt');
672
+ expect(vibeContent).toContain('Vibe task 1');
673
+
674
+ const tsContent = readFileSync(join(contextDir, 'ts-000001.ts'), 'utf-8');
675
+ expect(tsContent).toContain('TS Block: ts-000001');
676
+ expect(tsContent).toContain('x = 42');
677
+ expect(tsContent).toContain('return x * 2;');
678
+
679
+ const tsfContent = readFileSync(join(contextDir, 'tsf-000001.ts'), 'utf-8');
680
+ expect(tsfContent).toContain('TS Function Call: tsf-000001');
681
+ expect(tsfContent).toContain('processData');
682
+ });
683
+
684
+ test('directory names are linked by timestamp', () => {
685
+ const logger = new VerboseLogger({
686
+ logDir: testDir,
687
+ printToConsole: false,
688
+ writeToFile: true,
689
+ });
690
+
691
+ logger.start('/test.vibe');
692
+ logger.complete('completed');
693
+
694
+ const mainLogPath = logger.getMainLogPath();
695
+ const contextDir = logger.getContextDir();
696
+
697
+ // Extract timestamps from paths
698
+ const logFilename = mainLogPath.split(/[/\\]/).pop() ?? '';
699
+ const contextDirname = contextDir.split(/[/\\]/).pop() ?? '';
700
+
701
+ // Log file: run-2024-01-11T15-30-00.jsonl
702
+ // Context dir: run-2024-01-11T15-30-00
703
+ const logTimestamp = logFilename.replace('run-', '').replace('.jsonl', '');
704
+ const dirTimestamp = contextDirname.replace('run-', '');
705
+
706
+ expect(logTimestamp).toBe(dirTimestamp);
707
+ expect(logTimestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/);
708
+ });
709
+ });
710
+ });