@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,575 @@
1
+ /**
2
+ * Semantic Analyzer Validators
3
+ *
4
+ * Validation functions for the semantic analyzer.
5
+ * Each function takes an AnalyzerContext and performs specific validation.
6
+ */
7
+ import * as AST from '../ast';
8
+ import type { SourceLocation } from '../errors';
9
+ import type { AnalyzerContext } from './analyzer-context';
10
+ import { isValidType, typesCompatible, isValidJson, getBaseType } from './types';
11
+ import { ESCAPED_LBRACE, ESCAPED_RBRACE, ESCAPED_BANG_LBRACE } from '../parser/visitor/helpers';
12
+ import { isCoreFunction } from '../runtime/stdlib/core';
13
+ import { checkTsBlockTypes, inferTsBlockReturnType } from './ts-block-checker';
14
+ import { tsTypeToVibe, isVibeTypeCompatibleWithTs } from './ts-types';
15
+ import type { TsFunctionSignature } from './ts-signatures';
16
+
17
+ // ============================================================================
18
+ // Model and Tool Validation
19
+ // ============================================================================
20
+
21
+ /**
22
+ * Validates model declaration configuration.
23
+ */
24
+ export function validateModelConfig(
25
+ ctx: AnalyzerContext,
26
+ node: AST.ModelDeclaration,
27
+ visitExpression: (node: AST.Expression) => void
28
+ ): void {
29
+ const config = node.config;
30
+ const requiredFields = ['name', 'apiKey', 'url'];
31
+ const optionalFields = ['provider', 'maxRetriesOnError', 'thinkingLevel', 'tools'];
32
+ const validFields = [...requiredFields, ...optionalFields];
33
+ const provided = new Set(config.providedFields);
34
+
35
+ // Check for missing required fields
36
+ for (const field of requiredFields) {
37
+ if (!provided.has(field)) {
38
+ ctx.error(`Model '${node.name}' is missing required field '${field}'`, node.location);
39
+ }
40
+ }
41
+
42
+ // Check for unknown fields
43
+ for (const field of config.providedFields) {
44
+ if (!validFields.includes(field)) {
45
+ ctx.error(`Model '${node.name}' has unknown field '${field}'`, node.location);
46
+ }
47
+ }
48
+
49
+ // Validate provider is one of the allowed values
50
+ if (config.provider) {
51
+ if (config.provider.type === 'StringLiteral') {
52
+ const validProviders = ['anthropic', 'openai', 'google'];
53
+ if (!validProviders.includes(config.provider.value)) {
54
+ ctx.error(
55
+ `Invalid provider '${config.provider.value}'. Must be: ${validProviders.join(', ')}`,
56
+ config.provider.location
57
+ );
58
+ }
59
+ }
60
+ visitExpression(config.provider);
61
+ }
62
+
63
+ // Validate maxRetriesOnError is a non-negative number
64
+ if (config.maxRetriesOnError) {
65
+ if (config.maxRetriesOnError.type === 'NumberLiteral') {
66
+ if (config.maxRetriesOnError.value < 0 || !Number.isInteger(config.maxRetriesOnError.value)) {
67
+ ctx.error(
68
+ `maxRetriesOnError must be a non-negative integer, got ${config.maxRetriesOnError.value}`,
69
+ config.maxRetriesOnError.location
70
+ );
71
+ }
72
+ }
73
+ visitExpression(config.maxRetriesOnError);
74
+ }
75
+
76
+ // Visit field expressions (check for undefined variables, etc.)
77
+ if (config.modelName) visitExpression(config.modelName);
78
+ if (config.apiKey) visitExpression(config.apiKey);
79
+ if (config.url) visitExpression(config.url);
80
+ }
81
+
82
+ /**
83
+ * Validates tool declaration.
84
+ */
85
+ export function validateToolDeclaration(ctx: AnalyzerContext, node: AST.ToolDeclaration): void {
86
+ // Validate @param decorators reference actual parameters
87
+ if (node.paramDecorators) {
88
+ const paramNames = new Set(node.params.map(p => p.name));
89
+ for (const decoratorName of node.paramDecorators) {
90
+ if (!paramNames.has(decoratorName)) {
91
+ ctx.error(
92
+ `@param '${decoratorName}' does not match any parameter in tool '${node.name}'. ` +
93
+ `Valid parameters: ${node.params.map(p => p.name).join(', ') || '(none)'}`,
94
+ node.location
95
+ );
96
+ }
97
+ }
98
+ }
99
+
100
+ // Validate parameter type annotations (allow both Vibe types and imported types)
101
+ for (const param of node.params) {
102
+ const baseType = param.typeAnnotation.replace(/\[\]$/, '');
103
+ const isVibeType = ['text', 'json', 'boolean', 'number', 'prompt'].includes(baseType);
104
+ if (!isVibeType) {
105
+ const symbol = ctx.symbols.lookup(baseType);
106
+ if (!symbol) {
107
+ ctx.error(
108
+ `Unknown type '${baseType}' in tool parameter '${param.name}'`,
109
+ node.location
110
+ );
111
+ }
112
+ }
113
+ }
114
+
115
+ // Validate return type if present
116
+ if (node.returnType) {
117
+ const baseType = node.returnType.replace(/\[\]$/, '');
118
+ const isVibeType = ['text', 'json', 'boolean', 'number', 'prompt'].includes(baseType);
119
+ if (!isVibeType) {
120
+ const symbol = ctx.symbols.lookup(baseType);
121
+ if (!symbol) {
122
+ ctx.error(
123
+ `Unknown return type '${baseType}' in tool '${node.name}'`,
124
+ node.location
125
+ );
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ // ============================================================================
132
+ // Type Validation
133
+ // ============================================================================
134
+
135
+ /**
136
+ * Validates a type annotation is valid.
137
+ */
138
+ export function validateTypeAnnotation(ctx: AnalyzerContext, type: string, location: SourceLocation): void {
139
+ if (!isValidType(type)) {
140
+ ctx.error(`Unknown type '${getBaseType(type)}'`, location);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Validates a JSON literal string.
146
+ */
147
+ export function validateJsonLiteral(ctx: AnalyzerContext, value: string, location: SourceLocation): void {
148
+ if (!isValidJson(value)) {
149
+ ctx.error(`Invalid JSON literal`, location);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Validates that a condition expression is boolean.
155
+ */
156
+ export function validateConditionType(
157
+ ctx: AnalyzerContext,
158
+ expr: AST.Expression,
159
+ conditionContext: 'if' | 'while',
160
+ getExpressionType: (expr: AST.Expression) => string | null
161
+ ): void {
162
+ const exprType = getExpressionType(expr);
163
+ if (exprType && exprType !== 'boolean') {
164
+ ctx.error(`${conditionContext} condition must be boolean, got ${exprType}`, expr.location);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Validates that an expression is compatible with its type annotation.
170
+ */
171
+ export function validateLiteralType(
172
+ ctx: AnalyzerContext,
173
+ expr: AST.Expression,
174
+ type: string,
175
+ location: SourceLocation,
176
+ getExpressionType: (expr: AST.Expression) => string | null
177
+ ): void {
178
+ // Handle array types
179
+ if (type.endsWith('[]')) {
180
+ if (expr.type === 'ArrayLiteral') {
181
+ const elementType = type.slice(0, -2);
182
+ for (const element of expr.elements) {
183
+ validateLiteralType(ctx, element, elementType, element.location, getExpressionType);
184
+ }
185
+ }
186
+ return;
187
+ }
188
+
189
+ // json type cannot be an array literal - use json[] for arrays
190
+ if (type === 'json' && expr.type === 'ArrayLiteral') {
191
+ ctx.error(`json type expects an object, not an array. Use json[] for arrays.`, location);
192
+ return;
193
+ }
194
+
195
+ // Get the source type from the expression
196
+ const sourceType = getExpressionType(expr);
197
+ if (!sourceType) {
198
+ // Can't determine type at compile time
199
+ if (type === 'json' && expr.type === 'StringLiteral') {
200
+ validateJsonLiteral(ctx, expr.value, location);
201
+ }
202
+ return;
203
+ }
204
+
205
+ // Check type compatibility
206
+ if (!typesCompatible(sourceType, type)) {
207
+ ctx.error(`Type error: cannot assign ${sourceType} to ${type}`, location);
208
+ }
209
+
210
+ // Additional JSON validation for string literals
211
+ if (type === 'json' && expr.type === 'StringLiteral') {
212
+ validateJsonLiteral(ctx, expr.value, location);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Validates that an async expression is a single async-capable operation.
218
+ */
219
+ export function validateAsyncExpression(ctx: AnalyzerContext, expr: AST.Expression, location: SourceLocation): void {
220
+ const validTypes = ['VibeExpression', 'TsBlock', 'CallExpression'];
221
+ if (!validTypes.includes(expr.type)) {
222
+ ctx.error(
223
+ `async declarations require a single do, vibe, ts block, or function call`,
224
+ location
225
+ );
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Validates compress context mode arguments.
231
+ */
232
+ export function validateContextMode(ctx: AnalyzerContext, mode: AST.ContextMode, location: SourceLocation): void {
233
+ if (mode === 'forget' || mode === 'verbose') return;
234
+
235
+ const { arg1, arg2 } = mode.compress;
236
+
237
+ if (arg1 && arg1.kind === 'identifier') {
238
+ const sym = ctx.symbols.lookup(arg1.name);
239
+ if (!sym) {
240
+ ctx.error(`compress argument '${arg1.name}' is not declared`, location);
241
+ } else if (arg2) {
242
+ const isPromptType = sym.kind === 'constant' && sym.typeAnnotation === 'prompt';
243
+ const isTextType = sym.typeAnnotation === 'text' || sym.typeAnnotation === 'prompt';
244
+ if (!isPromptType && !isTextType) {
245
+ ctx.error(
246
+ `compress first argument '${arg1.name}' must be prompt type when two arguments provided, got ${sym.typeAnnotation ?? sym.kind}`,
247
+ location
248
+ );
249
+ }
250
+ } else {
251
+ const isModelType = sym.kind === 'model' || (sym.kind === 'constant' && sym.typeAnnotation === 'model');
252
+ const isPromptType = sym.kind === 'constant' && sym.typeAnnotation === 'prompt';
253
+ const isTextType = sym.typeAnnotation === 'text' || sym.typeAnnotation === 'prompt';
254
+ if (!isModelType && !isPromptType && !isTextType) {
255
+ ctx.error(
256
+ `compress argument '${arg1.name}' must be prompt or model type, got ${sym.typeAnnotation ?? sym.kind}`,
257
+ location
258
+ );
259
+ }
260
+ }
261
+ }
262
+
263
+ if (arg2 && arg2.kind === 'identifier') {
264
+ const sym = ctx.symbols.lookup(arg2.name);
265
+ if (!sym) {
266
+ ctx.error(`compress model '${arg2.name}' is not declared`, location);
267
+ } else {
268
+ const isModelType = sym.kind === 'model' || (sym.kind === 'constant' && sym.typeAnnotation === 'model');
269
+ if (!isModelType) {
270
+ ctx.error(
271
+ `compress second argument '${arg2.name}' must be model type, got ${sym.typeAnnotation ?? sym.kind}`,
272
+ location
273
+ );
274
+ }
275
+ }
276
+ }
277
+ }
278
+
279
+ // ============================================================================
280
+ // TypeScript Validation
281
+ // ============================================================================
282
+
283
+ /**
284
+ * Validates a call to an imported TypeScript function.
285
+ */
286
+ export function validateTsCall(
287
+ ctx: AnalyzerContext,
288
+ node: AST.CallExpression,
289
+ sig: TsFunctionSignature,
290
+ getExpressionType: (expr: AST.Expression) => string | null
291
+ ): void {
292
+ // Check argument count
293
+ const requiredParams = sig.params.filter(p => !p.optional).length;
294
+ if (node.arguments.length < requiredParams) {
295
+ ctx.error(
296
+ `Function '${sig.name}' requires ${requiredParams} argument${requiredParams === 1 ? '' : 's'}, got ${node.arguments.length}`,
297
+ node.location
298
+ );
299
+ }
300
+ if (node.arguments.length > sig.params.length) {
301
+ ctx.error(
302
+ `Function '${sig.name}' accepts at most ${sig.params.length} argument${sig.params.length === 1 ? '' : 's'}, got ${node.arguments.length}`,
303
+ node.location
304
+ );
305
+ }
306
+
307
+ // Check argument types
308
+ for (let i = 0; i < node.arguments.length && i < sig.params.length; i++) {
309
+ const arg = node.arguments[i];
310
+ const argType = getExpressionType(arg);
311
+ const paramTsType = sig.params[i].tsType;
312
+
313
+ if (!argType) continue;
314
+
315
+ if (!isVibeTypeCompatibleWithTs(argType, paramTsType)) {
316
+ ctx.error(
317
+ `Argument ${i + 1} of '${sig.name}': expected ${paramTsType}, got ${argType}`,
318
+ arg.location
319
+ );
320
+ }
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Validates a ts() block by checking parameter references and type-checking the body.
326
+ */
327
+ export function validateTsBlock(ctx: AnalyzerContext, node: AST.TsBlock): void {
328
+ const params: Array<{ name: string; vibeType: string | null }> = [];
329
+ for (const paramName of node.params) {
330
+ const symbol = ctx.symbols.lookup(paramName);
331
+ if (!symbol) {
332
+ ctx.error(`'${paramName}' is not defined`, node.location);
333
+ continue;
334
+ }
335
+ params.push({
336
+ name: paramName,
337
+ vibeType: symbol.typeAnnotation ?? null,
338
+ });
339
+ }
340
+
341
+ const tsErrors = checkTsBlockTypes(params, node.body, node.location);
342
+ for (const err of tsErrors) {
343
+ ctx.error(err.message, err.location);
344
+ }
345
+ }
346
+
347
+ // ============================================================================
348
+ // Call Validation
349
+ // ============================================================================
350
+
351
+ /**
352
+ * Check that we're not calling a tool directly.
353
+ */
354
+ export function checkToolCall(ctx: AnalyzerContext, node: AST.CallExpression): void {
355
+ if (node.callee.type !== 'Identifier') return;
356
+
357
+ const symbol = ctx.symbols.lookup(node.callee.name);
358
+ if (symbol?.kind === 'tool') {
359
+ ctx.error(
360
+ `Cannot call tool '${node.callee.name}' directly. Tools can only be used by AI models via the tools array in model declarations.`,
361
+ node.location
362
+ );
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Check that call arguments match the function's parameter types.
368
+ */
369
+ export function checkCallArguments(
370
+ ctx: AnalyzerContext,
371
+ node: AST.CallExpression,
372
+ getExpressionType: (expr: AST.Expression) => string | null,
373
+ validateLiteralTypeFn: (expr: AST.Expression, type: string, location: SourceLocation) => void
374
+ ): void {
375
+ if (node.callee.type !== 'Identifier') return;
376
+ const calleeName = node.callee.name;
377
+
378
+ // Check if it's a TS import - validate against TS signature
379
+ const tsSig = ctx.tsImportSignatures.get(calleeName);
380
+ if (tsSig) {
381
+ validateTsCall(ctx, node, tsSig, getExpressionType);
382
+ return;
383
+ }
384
+
385
+ // Otherwise, check against Vibe function signature
386
+ const funcSymbol = ctx.symbols.lookup(calleeName);
387
+ if (!funcSymbol || funcSymbol.kind !== 'function') return;
388
+ if (!funcSymbol.paramTypes) return;
389
+
390
+ // Check each argument against corresponding parameter type
391
+ for (let i = 0; i < node.arguments.length && i < funcSymbol.paramTypes.length; i++) {
392
+ const arg = node.arguments[i];
393
+ const expectedType = funcSymbol.paramTypes[i];
394
+ if (expectedType) {
395
+ validateLiteralTypeFn(arg, expectedType, arg.location);
396
+ }
397
+ }
398
+ }
399
+
400
+ // ============================================================================
401
+ // Prompt and Context Validation
402
+ // ============================================================================
403
+
404
+ /**
405
+ * Validates that prompt parameters are string literals or text/prompt typed variables.
406
+ */
407
+ export function checkPromptType(ctx: AnalyzerContext, node: AST.Expression): void {
408
+ if (node.type !== 'Identifier') return;
409
+
410
+ const sym = ctx.symbols.lookup(node.name);
411
+ if (!sym) return;
412
+
413
+ if (sym.kind === 'model') {
414
+ ctx.error(`Cannot use model '${node.name}' as prompt`, node.location);
415
+ } else if (sym.kind === 'function') {
416
+ ctx.error(`Cannot use function '${node.name}' as prompt`, node.location);
417
+ } else if (sym.typeAnnotation === 'json') {
418
+ ctx.error(`Cannot use json typed variable '${node.name}' as prompt`, node.location);
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Validates the model type in a vibe expression.
424
+ */
425
+ export function checkModelType(
426
+ ctx: AnalyzerContext,
427
+ node: AST.Expression,
428
+ visitExpression: (node: AST.Expression) => void
429
+ ): void {
430
+ if (node.type === 'Identifier') {
431
+ const sym = ctx.symbols.lookup(node.name);
432
+ if (!sym) {
433
+ ctx.error(`'${node.name}' is not defined`, node.location);
434
+ } else if (sym.kind !== 'model') {
435
+ const isModelParam = sym.kind === 'parameter' && sym.typeAnnotation === 'model';
436
+ if (!isModelParam) {
437
+ ctx.error(`Expected model, got ${sym.kind} '${node.name}'`, node.location);
438
+ }
439
+ }
440
+ } else {
441
+ visitExpression(node);
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Validates the context variable in a vibe expression.
447
+ */
448
+ export function checkContextVariable(ctx: AnalyzerContext, context: AST.ContextSpecifier): void {
449
+ if (context.kind === 'variable' && context.variable) {
450
+ if (!ctx.symbols.lookup(context.variable)) {
451
+ ctx.error(`'${context.variable}' is not defined`, context.location);
452
+ }
453
+ }
454
+ }
455
+
456
+ // ============================================================================
457
+ // String Interpolation Validation
458
+ // ============================================================================
459
+
460
+ /** Pattern to match interpolation syntax */
461
+ const INTERPOLATION_PATTERN = /(!?)\{(\w+(?:\.\w+|\[\d+\]|\[\d*:\d*\])*)\}/g;
462
+
463
+ /**
464
+ * Validates string interpolation references.
465
+ */
466
+ export function validateStringInterpolation(
467
+ ctx: AnalyzerContext,
468
+ value: string,
469
+ isPromptContext: boolean,
470
+ location: SourceLocation
471
+ ): void {
472
+ // Skip escaped placeholders
473
+ const testValue = value
474
+ .replace(new RegExp(ESCAPED_LBRACE, 'g'), '')
475
+ .replace(new RegExp(ESCAPED_RBRACE, 'g'), '')
476
+ .replace(new RegExp(ESCAPED_BANG_LBRACE, 'g'), '');
477
+
478
+ // Reset regex state
479
+ INTERPOLATION_PATTERN.lastIndex = 0;
480
+
481
+ let match;
482
+ while ((match = INTERPOLATION_PATTERN.exec(testValue)) !== null) {
483
+ const [, bang, path] = match;
484
+ const isExpansion = bang === '!';
485
+ const varName = path.split(/[.\[]/)[0];
486
+
487
+ const symbol = ctx.symbols.lookup(varName);
488
+ if (!symbol && !isCoreFunction(varName)) {
489
+ ctx.error(`'${varName}' is not defined`, location);
490
+ continue;
491
+ }
492
+
493
+ if (!isPromptContext && isExpansion) {
494
+ ctx.error(
495
+ `Expansion syntax !{${path}} is only valid in prompt strings (do/vibe expressions or prompt-typed variables)`,
496
+ location
497
+ );
498
+ }
499
+ }
500
+ }
501
+
502
+ // ============================================================================
503
+ // Expression Type Inference
504
+ // ============================================================================
505
+
506
+ /**
507
+ * Gets the type of an expression if it can be determined at compile time.
508
+ */
509
+ export function getExpressionType(ctx: AnalyzerContext, expr: AST.Expression): string | null {
510
+ switch (expr.type) {
511
+ case 'StringLiteral':
512
+ case 'TemplateLiteral':
513
+ return 'text';
514
+ case 'BooleanLiteral':
515
+ return 'boolean';
516
+ case 'NumberLiteral':
517
+ return 'number';
518
+ case 'NullLiteral':
519
+ return 'null';
520
+ case 'ObjectLiteral':
521
+ return 'json';
522
+ case 'ArrayLiteral': {
523
+ // Infer array type from first element
524
+ if (expr.elements.length === 0) {
525
+ return null; // Empty array - type unknown
526
+ }
527
+ const firstElementType = getExpressionType(ctx, expr.elements[0]);
528
+ if (firstElementType && !firstElementType.endsWith('[]')) {
529
+ return `${firstElementType}[]`;
530
+ }
531
+ return null;
532
+ }
533
+ case 'SliceExpression': {
534
+ // A slice of an array has the same type as the array
535
+ const objectType = getExpressionType(ctx, expr.object);
536
+ if (objectType?.endsWith('[]')) {
537
+ return objectType;
538
+ }
539
+ return null;
540
+ }
541
+ case 'Identifier': {
542
+ const symbol = ctx.symbols.lookup(expr.name);
543
+ if (symbol?.typeAnnotation) {
544
+ return symbol.typeAnnotation;
545
+ }
546
+ return null;
547
+ }
548
+ case 'CallExpression': {
549
+ if (expr.callee.type === 'Identifier') {
550
+ const tsSig = ctx.tsImportSignatures.get(expr.callee.name);
551
+ if (tsSig) {
552
+ return tsTypeToVibe(tsSig.returnType);
553
+ }
554
+ const funcSymbol = ctx.symbols.lookup(expr.callee.name);
555
+ if (funcSymbol?.kind === 'function' && funcSymbol.returnType) {
556
+ return funcSymbol.returnType;
557
+ }
558
+ }
559
+ return null;
560
+ }
561
+ case 'TsBlock': {
562
+ const params: Array<{ name: string; vibeType: string | null }> = [];
563
+ for (const paramName of expr.params) {
564
+ const symbol = ctx.symbols.lookup(paramName);
565
+ params.push({
566
+ name: paramName,
567
+ vibeType: symbol?.typeAnnotation ?? null,
568
+ });
569
+ }
570
+ return inferTsBlockReturnType(params, expr.body);
571
+ }
572
+ default:
573
+ return null;
574
+ }
575
+ }