agentlang 0.10.1 → 0.10.3

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 (178) hide show
  1. package/README.md +7 -14
  2. package/out/api/http.d.ts +4 -0
  3. package/out/api/http.d.ts.map +1 -1
  4. package/out/api/http.js +307 -26
  5. package/out/api/http.js.map +1 -1
  6. package/out/cli/main.d.ts.map +1 -1
  7. package/out/cli/main.js +3 -0
  8. package/out/cli/main.js.map +1 -1
  9. package/out/extension/main.cjs +250 -250
  10. package/out/extension/main.cjs.map +2 -2
  11. package/out/language/agentlang-validator.d.ts.map +1 -1
  12. package/out/language/agentlang-validator.js +4 -0
  13. package/out/language/agentlang-validator.js.map +1 -1
  14. package/out/language/error-reporter.d.ts +53 -0
  15. package/out/language/error-reporter.d.ts.map +1 -0
  16. package/out/language/error-reporter.js +879 -0
  17. package/out/language/error-reporter.js.map +1 -0
  18. package/out/language/generated/ast.d.ts +77 -2
  19. package/out/language/generated/ast.d.ts.map +1 -1
  20. package/out/language/generated/ast.js +60 -0
  21. package/out/language/generated/ast.js.map +1 -1
  22. package/out/language/generated/grammar.d.ts.map +1 -1
  23. package/out/language/generated/grammar.js +342 -206
  24. package/out/language/generated/grammar.js.map +1 -1
  25. package/out/language/main.cjs +901 -710
  26. package/out/language/main.cjs.map +3 -3
  27. package/out/language/parser.d.ts +4 -2
  28. package/out/language/parser.d.ts.map +1 -1
  29. package/out/language/parser.js +58 -99
  30. package/out/language/parser.js.map +1 -1
  31. package/out/language/syntax.d.ts +16 -0
  32. package/out/language/syntax.d.ts.map +1 -1
  33. package/out/language/syntax.js +66 -27
  34. package/out/language/syntax.js.map +1 -1
  35. package/out/runtime/api.d.ts +2 -0
  36. package/out/runtime/api.d.ts.map +1 -1
  37. package/out/runtime/api.js +25 -0
  38. package/out/runtime/api.js.map +1 -1
  39. package/out/runtime/datefns.d.ts +34 -0
  40. package/out/runtime/datefns.d.ts.map +1 -0
  41. package/out/runtime/datefns.js +82 -0
  42. package/out/runtime/datefns.js.map +1 -0
  43. package/out/runtime/defs.d.ts +1 -0
  44. package/out/runtime/defs.d.ts.map +1 -1
  45. package/out/runtime/defs.js +2 -1
  46. package/out/runtime/defs.js.map +1 -1
  47. package/out/runtime/document-retriever.d.ts +24 -0
  48. package/out/runtime/document-retriever.d.ts.map +1 -0
  49. package/out/runtime/document-retriever.js +258 -0
  50. package/out/runtime/document-retriever.js.map +1 -0
  51. package/out/runtime/embeddings/chunker.d.ts +18 -0
  52. package/out/runtime/embeddings/chunker.d.ts.map +1 -1
  53. package/out/runtime/embeddings/chunker.js +47 -15
  54. package/out/runtime/embeddings/chunker.js.map +1 -1
  55. package/out/runtime/embeddings/openai.d.ts.map +1 -1
  56. package/out/runtime/embeddings/openai.js +22 -9
  57. package/out/runtime/embeddings/openai.js.map +1 -1
  58. package/out/runtime/embeddings/provider.d.ts +1 -0
  59. package/out/runtime/embeddings/provider.d.ts.map +1 -1
  60. package/out/runtime/embeddings/provider.js +20 -1
  61. package/out/runtime/embeddings/provider.js.map +1 -1
  62. package/out/runtime/exec-graph.d.ts.map +1 -1
  63. package/out/runtime/exec-graph.js +22 -3
  64. package/out/runtime/exec-graph.js.map +1 -1
  65. package/out/runtime/integration-client.d.ts +21 -0
  66. package/out/runtime/integration-client.d.ts.map +1 -0
  67. package/out/runtime/integration-client.js +112 -0
  68. package/out/runtime/integration-client.js.map +1 -0
  69. package/out/runtime/integrations.d.ts.map +1 -1
  70. package/out/runtime/integrations.js +20 -9
  71. package/out/runtime/integrations.js.map +1 -1
  72. package/out/runtime/interpreter.d.ts +10 -0
  73. package/out/runtime/interpreter.d.ts.map +1 -1
  74. package/out/runtime/interpreter.js +221 -22
  75. package/out/runtime/interpreter.js.map +1 -1
  76. package/out/runtime/loader.d.ts.map +1 -1
  77. package/out/runtime/loader.js +70 -7
  78. package/out/runtime/loader.js.map +1 -1
  79. package/out/runtime/logger.d.ts.map +1 -1
  80. package/out/runtime/logger.js +8 -1
  81. package/out/runtime/logger.js.map +1 -1
  82. package/out/runtime/module.d.ts +18 -0
  83. package/out/runtime/module.d.ts.map +1 -1
  84. package/out/runtime/module.js +91 -3
  85. package/out/runtime/module.js.map +1 -1
  86. package/out/runtime/modules/ai.d.ts +16 -5
  87. package/out/runtime/modules/ai.d.ts.map +1 -1
  88. package/out/runtime/modules/ai.js +286 -88
  89. package/out/runtime/modules/ai.js.map +1 -1
  90. package/out/runtime/modules/core.d.ts.map +1 -1
  91. package/out/runtime/modules/core.js +5 -1
  92. package/out/runtime/modules/core.js.map +1 -1
  93. package/out/runtime/monitor.d.ts +6 -0
  94. package/out/runtime/monitor.d.ts.map +1 -1
  95. package/out/runtime/monitor.js +21 -1
  96. package/out/runtime/monitor.js.map +1 -1
  97. package/out/runtime/relgraph.d.ts.map +1 -1
  98. package/out/runtime/relgraph.js +7 -3
  99. package/out/runtime/relgraph.js.map +1 -1
  100. package/out/runtime/resolvers/interface.d.ts +7 -2
  101. package/out/runtime/resolvers/interface.d.ts.map +1 -1
  102. package/out/runtime/resolvers/interface.js +17 -3
  103. package/out/runtime/resolvers/interface.js.map +1 -1
  104. package/out/runtime/resolvers/sqldb/database.d.ts +2 -0
  105. package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
  106. package/out/runtime/resolvers/sqldb/database.js +142 -126
  107. package/out/runtime/resolvers/sqldb/database.js.map +1 -1
  108. package/out/runtime/resolvers/sqldb/dbutil.d.ts.map +1 -1
  109. package/out/runtime/resolvers/sqldb/dbutil.js +25 -4
  110. package/out/runtime/resolvers/sqldb/dbutil.js.map +1 -1
  111. package/out/runtime/resolvers/sqldb/impl.d.ts +2 -1
  112. package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
  113. package/out/runtime/resolvers/sqldb/impl.js +24 -7
  114. package/out/runtime/resolvers/sqldb/impl.js.map +1 -1
  115. package/out/runtime/resolvers/vector/lancedb-store.d.ts +16 -0
  116. package/out/runtime/resolvers/vector/lancedb-store.d.ts.map +1 -0
  117. package/out/runtime/resolvers/vector/lancedb-store.js +159 -0
  118. package/out/runtime/resolvers/vector/lancedb-store.js.map +1 -0
  119. package/out/runtime/resolvers/vector/types.d.ts +32 -0
  120. package/out/runtime/resolvers/vector/types.d.ts.map +1 -0
  121. package/out/runtime/resolvers/vector/types.js +2 -0
  122. package/out/runtime/resolvers/vector/types.js.map +1 -0
  123. package/out/runtime/services/documentFetcher.d.ts.map +1 -1
  124. package/out/runtime/services/documentFetcher.js +21 -6
  125. package/out/runtime/services/documentFetcher.js.map +1 -1
  126. package/out/runtime/state.d.ts +19 -1
  127. package/out/runtime/state.d.ts.map +1 -1
  128. package/out/runtime/state.js +36 -1
  129. package/out/runtime/state.js.map +1 -1
  130. package/out/runtime/util.d.ts +3 -2
  131. package/out/runtime/util.d.ts.map +1 -1
  132. package/out/runtime/util.js +13 -2
  133. package/out/runtime/util.js.map +1 -1
  134. package/out/syntaxes/agentlang.monarch.js +1 -1
  135. package/out/syntaxes/agentlang.monarch.js.map +1 -1
  136. package/out/test-harness.d.ts +36 -0
  137. package/out/test-harness.d.ts.map +1 -0
  138. package/out/test-harness.js +341 -0
  139. package/out/test-harness.js.map +1 -0
  140. package/package.json +22 -19
  141. package/src/api/http.ts +336 -38
  142. package/src/cli/main.ts +3 -0
  143. package/src/language/agentlang-validator.ts +3 -0
  144. package/src/language/agentlang.langium +6 -2
  145. package/src/language/error-reporter.ts +1028 -0
  146. package/src/language/generated/ast.ts +94 -1
  147. package/src/language/generated/grammar.ts +342 -206
  148. package/src/language/parser.ts +64 -101
  149. package/src/language/syntax.ts +79 -24
  150. package/src/runtime/api.ts +36 -0
  151. package/src/runtime/datefns.ts +112 -0
  152. package/src/runtime/defs.ts +2 -1
  153. package/src/runtime/document-retriever.ts +311 -0
  154. package/src/runtime/embeddings/chunker.ts +52 -14
  155. package/src/runtime/embeddings/openai.ts +27 -9
  156. package/src/runtime/embeddings/provider.ts +22 -1
  157. package/src/runtime/exec-graph.ts +23 -2
  158. package/src/runtime/integration-client.ts +158 -0
  159. package/src/runtime/integrations.ts +20 -11
  160. package/src/runtime/interpreter.ts +221 -15
  161. package/src/runtime/loader.ts +83 -5
  162. package/src/runtime/logger.ts +12 -1
  163. package/src/runtime/module.ts +104 -3
  164. package/src/runtime/modules/ai.ts +341 -107
  165. package/src/runtime/modules/core.ts +5 -1
  166. package/src/runtime/monitor.ts +27 -1
  167. package/src/runtime/relgraph.ts +7 -3
  168. package/src/runtime/resolvers/interface.ts +23 -3
  169. package/src/runtime/resolvers/sqldb/database.ts +158 -130
  170. package/src/runtime/resolvers/sqldb/dbutil.ts +28 -6
  171. package/src/runtime/resolvers/sqldb/impl.ts +25 -7
  172. package/src/runtime/resolvers/vector/lancedb-store.ts +187 -0
  173. package/src/runtime/resolvers/vector/types.ts +39 -0
  174. package/src/runtime/services/documentFetcher.ts +21 -6
  175. package/src/runtime/state.ts +40 -1
  176. package/src/runtime/util.ts +19 -2
  177. package/src/syntaxes/agentlang.monarch.ts +1 -1
  178. package/src/test-harness.ts +423 -0
@@ -0,0 +1,879 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Error collection – convert Langium/Chevrotain errors to AgentlangError[]
3
+ // ---------------------------------------------------------------------------
4
+ export function collectErrors(document) {
5
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
6
+ const errors = [];
7
+ const uri = document.uri.toString();
8
+ const file = uri.replace(/^file:\/\/\//, '');
9
+ // Lexer errors
10
+ for (const err of document.parseResult.lexerErrors) {
11
+ const le = err;
12
+ const line = (_a = le.line) !== null && _a !== void 0 ? _a : 1;
13
+ const col = (_b = le.column) !== null && _b !== void 0 ? _b : 1;
14
+ const length = (_c = le.length) !== null && _c !== void 0 ? _c : 1;
15
+ const { message: lexMsg, hint: lexHint } = humanizeLexerError(le.message);
16
+ errors.push({
17
+ category: 'UNEXPECTED TOKEN',
18
+ file,
19
+ region: {
20
+ startLine: line,
21
+ startCol: col,
22
+ endLine: line,
23
+ endCol: col + length - 1,
24
+ },
25
+ message: lexMsg,
26
+ hint: lexHint,
27
+ });
28
+ }
29
+ // Parser errors
30
+ const sourceLines = document.textDocument.getText().split('\n');
31
+ for (const err of document.parseResult.parserErrors) {
32
+ const pe = err;
33
+ const token = pe.token;
34
+ let startLine = token === null || token === void 0 ? void 0 : token.startLine;
35
+ let startCol = token === null || token === void 0 ? void 0 : token.startColumn;
36
+ let endLine = token === null || token === void 0 ? void 0 : token.endLine;
37
+ let endCol = token === null || token === void 0 ? void 0 : token.endColumn;
38
+ // When the error token is EOF, positions are NaN.
39
+ // Fall back to the previous token's end position, or the last line.
40
+ if (!startLine || isNaN(startLine)) {
41
+ const prev = pe.previousToken;
42
+ if ((prev === null || prev === void 0 ? void 0 : prev.endLine) && !isNaN(prev.endLine)) {
43
+ startLine = prev.endLine;
44
+ startCol = ((_d = prev.endColumn) !== null && _d !== void 0 ? _d : 0) + 1;
45
+ endLine = startLine;
46
+ endCol = startCol;
47
+ }
48
+ else {
49
+ // Last resort: point to end of source
50
+ startLine = sourceLines.length;
51
+ startCol = ((_f = (_e = sourceLines[sourceLines.length - 1]) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0) + 1;
52
+ endLine = startLine;
53
+ endCol = startCol;
54
+ }
55
+ }
56
+ const source = document.textDocument.getText();
57
+ const { category, message, hint, regionOverride } = classifyParserError(pe, source);
58
+ errors.push({
59
+ category,
60
+ file,
61
+ region: regionOverride !== null && regionOverride !== void 0 ? regionOverride : {
62
+ startLine: startLine !== null && startLine !== void 0 ? startLine : 1,
63
+ startCol: startCol !== null && startCol !== void 0 ? startCol : 1,
64
+ endLine: (_g = endLine !== null && endLine !== void 0 ? endLine : startLine) !== null && _g !== void 0 ? _g : 1,
65
+ endCol: (_h = endCol !== null && endCol !== void 0 ? endCol : startCol) !== null && _h !== void 0 ? _h : 1,
66
+ },
67
+ message,
68
+ hint,
69
+ });
70
+ }
71
+ // Validation errors (severity 1 = error)
72
+ const validationErrors = ((_j = document.diagnostics) !== null && _j !== void 0 ? _j : []).filter(e => e.severity === 1);
73
+ for (const ve of validationErrors) {
74
+ const text = document.textDocument.getText(ve.range);
75
+ errors.push({
76
+ category: 'VALIDATION ERROR',
77
+ file,
78
+ region: {
79
+ startLine: ve.range.start.line + 1,
80
+ startCol: ve.range.start.character + 1,
81
+ endLine: ve.range.end.line + 1,
82
+ endCol: ve.range.end.character + 1,
83
+ },
84
+ message: ve.message || `Unexpected token '${text}'.`,
85
+ });
86
+ }
87
+ return errors;
88
+ }
89
+ // Human-readable names for grammar rules found in Chevrotain's ruleStack
90
+ const RULE_NAMES = {
91
+ ModuleDefinition: 'module',
92
+ EntityDefinition: 'entity',
93
+ EventDefinition: 'event',
94
+ RecordDefinition: 'record',
95
+ WorkflowDefinition: 'workflow',
96
+ AgentDefinition: 'agent',
97
+ FlowDefinition: 'flow',
98
+ DecisionDefinition: 'decision',
99
+ RelationshipDefinition: 'relationship',
100
+ ResolverDefinition: 'resolver',
101
+ ScenarioDefinition: 'scenario',
102
+ DirectiveDefinition: 'directive',
103
+ RecordSchemaDefinition: 'schema body',
104
+ RecDef: 'definition',
105
+ CrudMap: 'data pattern',
106
+ CrudMapBody: 'data pattern body',
107
+ If: 'if expression',
108
+ ForEach: 'for-each loop',
109
+ Body: 'body',
110
+ QualifiedName: 'name',
111
+ AttributeDefinition: 'attribute',
112
+ GenericDefBody: 'definition body',
113
+ };
114
+ // The known decorator keywords in agentlang (used for suggestions in later phases)
115
+ export const KNOWN_DECORATORS = [
116
+ '@public',
117
+ '@id',
118
+ '@ref',
119
+ '@enum',
120
+ '@oneof',
121
+ '@expr',
122
+ '@optional',
123
+ '@unique',
124
+ '@default',
125
+ '@as',
126
+ '@catch',
127
+ '@empty',
128
+ '@then',
129
+ '@async',
130
+ '@after',
131
+ '@before',
132
+ '@when',
133
+ '@rbac',
134
+ '@meta',
135
+ '@with_unique',
136
+ '@actions',
137
+ '@where',
138
+ '@into',
139
+ '@join',
140
+ '@inner_join',
141
+ '@left_join',
142
+ '@right_join',
143
+ '@full_join',
144
+ '@groupBy',
145
+ '@orderBy',
146
+ '@limit',
147
+ '@offset',
148
+ '@upsert',
149
+ '@distinct',
150
+ '@withRole',
151
+ ];
152
+ function classifyParserError(err, source) {
153
+ var _a, _b, _c, _d, _e, _f, _g;
154
+ const raw = (_a = err.message) !== null && _a !== void 0 ? _a : '';
155
+ const token = err.token;
156
+ const prevToken = err.previousToken;
157
+ // Langium appends U+200B (zero-width space) to rule names; strip it for matching
158
+ const ruleStack = ((_c = (_b = err.context) === null || _b === void 0 ? void 0 : _b.ruleStack) !== null && _c !== void 0 ? _c : []).map((s) => s.replace(/\u200B/g, ''));
159
+ const found = (_d = token === null || token === void 0 ? void 0 : token.image) !== null && _d !== void 0 ? _d : '';
160
+ const prevImage = (_e = prevToken === null || prevToken === void 0 ? void 0 : prevToken.image) !== null && _e !== void 0 ? _e : '';
161
+ const isEOF = !found || found === '' || ((_f = token === null || token === void 0 ? void 0 : token.tokenType) === null || _f === void 0 ? void 0 : _f.name) === 'EOF';
162
+ const errorName = (_g = err.name) !== null && _g !== void 0 ? _g : '';
163
+ // -----------------------------------------------------------------------
164
+ // 1. NotAllInputParsedException – extra tokens after valid parse
165
+ // -----------------------------------------------------------------------
166
+ if (errorName === 'NotAllInputParsedException' || raw.includes('Expecting end of file')) {
167
+ if (found === '@' || found.startsWith('@')) {
168
+ const attempted = extractDecoratorAtPosition(source, token);
169
+ const decoratorHint = buildDecoratorSuggestion(attempted);
170
+ return {
171
+ category: 'SYNTAX ERROR',
172
+ message: attempted
173
+ ? `I don't recognize \`${attempted}\` as a valid decorator or keyword here.`
174
+ : `I don't recognize '${found}' as a valid decorator or keyword here.`,
175
+ hint: decoratorHint,
176
+ regionOverride: decoratorRegion(token, attempted),
177
+ };
178
+ }
179
+ return {
180
+ category: 'SYNTAX ERROR',
181
+ message: `There is unexpected input '${found}' after what I expected to be the end.`,
182
+ hint: `This usually means a closing brace '}' is missing earlier, or there are extra characters that don't belong.`,
183
+ };
184
+ }
185
+ // -----------------------------------------------------------------------
186
+ // 2. MismatchedTokenException – expected a specific token, got something else
187
+ // -----------------------------------------------------------------------
188
+ if (errorName === 'MismatchedTokenException' || raw.includes('Expecting token of type')) {
189
+ return classifyMismatch(raw, found, prevImage, ruleStack, isEOF, source, token);
190
+ }
191
+ // -----------------------------------------------------------------------
192
+ // 3. NoViableAltException – none of the grammar alternatives matched
193
+ // -----------------------------------------------------------------------
194
+ if (errorName === 'NoViableAltException' ||
195
+ raw.includes('one of these possible Token sequences')) {
196
+ return classifyNoViableAlt(found, prevImage, ruleStack, isEOF, source, token);
197
+ }
198
+ // -----------------------------------------------------------------------
199
+ // 4. EarlyExitException – a required repetition had zero matches
200
+ // -----------------------------------------------------------------------
201
+ if (errorName === 'EarlyExitException' || raw.includes('EarlyExit')) {
202
+ const ctx = innermostContext(ruleStack);
203
+ return {
204
+ category: 'SYNTAX ERROR',
205
+ message: `I was expecting more input here${ctx ? ` while parsing ${ctx}` : ''}, but the definition ended too soon.`,
206
+ hint: `A required element may be missing. Check that all definitions are complete.`,
207
+ };
208
+ }
209
+ // -----------------------------------------------------------------------
210
+ // Fallback
211
+ // -----------------------------------------------------------------------
212
+ const fallbackCtx = innermostContext(ruleStack);
213
+ return {
214
+ category: 'SYNTAX ERROR',
215
+ message: humanizeFallback(raw),
216
+ hint: fallbackCtx
217
+ ? `This error occurred while parsing ${fallbackCtx}. Check for typos, missing punctuation, or incomplete definitions.`
218
+ : `Check for typos, missing commas, unclosed braces, or other syntax issues near this location.`,
219
+ };
220
+ }
221
+ // ---------------------------------------------------------------------------
222
+ // MismatchedTokenException handler
223
+ // ---------------------------------------------------------------------------
224
+ function classifyMismatch(raw, found, prevImage, ruleStack, isEOF, source, token) {
225
+ // Extract the expected token from the raw message
226
+ const expectMatch = raw.match(/Expecting token of type '([^']+)'/);
227
+ const expectedToken = expectMatch ? expectMatch[1] : '';
228
+ // --- Missing closing brace at EOF ---
229
+ if (expectedToken === '}' && isEOF) {
230
+ const ctx = innermostDefinition(ruleStack);
231
+ if (ctx) {
232
+ return {
233
+ category: 'MISSING TOKEN',
234
+ message: `I was parsing ${aOrAn(ctx)} ${ctx} definition but never found a closing \`}\`.`,
235
+ hint: `Check that every opening \`{\` has a matching closing \`}\`.`,
236
+ };
237
+ }
238
+ return {
239
+ category: 'MISSING TOKEN',
240
+ message: `I reached the end of the file but was expecting a closing \`}\`.`,
241
+ hint: `Check that every opening \`{\` has a matching closing \`}\`.`,
242
+ };
243
+ }
244
+ // --- Missing closing paren at EOF ---
245
+ if (expectedToken === ')' && isEOF) {
246
+ return {
247
+ category: 'MISSING TOKEN',
248
+ message: `I reached the end of the file but was expecting a closing \`)\`.`,
249
+ hint: `Check that every opening \`(\` has a matching closing \`)\`.`,
250
+ };
251
+ }
252
+ // --- Semicolon instead of comma in entity attributes ---
253
+ if (expectedToken === '}' && found === ';' && ruleStack.includes('RecordSchemaDefinition')) {
254
+ return {
255
+ category: 'SYNTAX ERROR',
256
+ message: `I found a semicolon \`;\` but attributes should be separated by commas.`,
257
+ hint: `Use commas between attributes, not semicolons. Example:\n\n entity E {\n name String,\n age Int\n }`,
258
+ };
259
+ }
260
+ // --- Duplicate module declaration ---
261
+ if (expectedToken === 'EOF' && found === 'module') {
262
+ return {
263
+ category: 'SYNTAX ERROR',
264
+ message: `I found a second \`module\` declaration, but only one is allowed per file.`,
265
+ hint: `Each file should contain exactly one \`module\` declaration at the top.`,
266
+ };
267
+ }
268
+ // --- Expected '}' but found a decorator like '@ref' ---
269
+ if (expectedToken === '}' && found.startsWith('@')) {
270
+ const ctx = innermostDefinition(ruleStack);
271
+ if (ctx) {
272
+ return {
273
+ category: 'SYNTAX ERROR',
274
+ message: `I was expecting \`}\` to close the ${ctx}, but found \`${found}\`.`,
275
+ hint: `'${found}' may need a preceding comma, or you may be missing a closing \`}\` before it.`,
276
+ };
277
+ }
278
+ }
279
+ // --- Expected EOF but found '@' (bad decorator) ---
280
+ if (expectedToken === 'EOF' && (found === '@' || found.startsWith('@'))) {
281
+ const attempted = extractDecoratorAtPosition(source, token);
282
+ const decoratorHint = buildDecoratorSuggestion(attempted);
283
+ return {
284
+ category: 'SYNTAX ERROR',
285
+ message: attempted
286
+ ? `I don't recognize \`${attempted}\` as a valid decorator or keyword here.`
287
+ : `I don't recognize '${found}' as a valid decorator or keyword here.`,
288
+ hint: decoratorHint,
289
+ regionOverride: decoratorRegion(token, attempted),
290
+ };
291
+ }
292
+ // --- Generic mismatch at EOF ---
293
+ if (isEOF) {
294
+ const ctx = innermostContext(ruleStack);
295
+ return {
296
+ category: 'MISSING TOKEN',
297
+ message: `I reached the end of the file${ctx ? ` while parsing ${ctx}` : ''}, but I was expecting \`${expectedToken}\`.`,
298
+ hint: `The definition may be incomplete. Check that nothing is missing at the end.`,
299
+ };
300
+ }
301
+ // --- Generic mismatch ---
302
+ const humanExpected = humanizeTokenName(expectedToken);
303
+ const mismatchCtx = innermostContext(ruleStack);
304
+ const defType = innermostDefinition(ruleStack);
305
+ let mismatchHint;
306
+ if (defType && expectedToken === '{') {
307
+ mismatchHint = `${capitalize(defType)} definitions need braces around their body. Example:\n\n ${defType} MyName {\n ...\n }`;
308
+ }
309
+ else if (defType) {
310
+ mismatchHint = `Check the syntax of your ${defType} definition. There may be a typo, a missing comma, or an extra token before \`${found}\`.`;
311
+ }
312
+ else if (mismatchCtx) {
313
+ mismatchHint = `This error occurred while parsing ${mismatchCtx}. Check for typos or missing punctuation near \`${found}\`.`;
314
+ }
315
+ else {
316
+ mismatchHint = `There may be a typo or missing punctuation near \`${found}\`.`;
317
+ }
318
+ return {
319
+ category: 'SYNTAX ERROR',
320
+ message: `I was expecting ${humanExpected} but found \`${found}\`.`,
321
+ hint: mismatchHint,
322
+ };
323
+ }
324
+ // ---------------------------------------------------------------------------
325
+ // NoViableAltException handler
326
+ // ---------------------------------------------------------------------------
327
+ function classifyNoViableAlt(found, prevImage, ruleStack, isEOF, _source, _token) {
328
+ var _a;
329
+ // --- Missing module name (after 'module' keyword) ---
330
+ if (ruleStack.includes('QualifiedName') && prevImage === 'module') {
331
+ if (isEOF) {
332
+ return {
333
+ category: 'MISSING TOKEN',
334
+ message: `I was expecting a module name after \`module\`, but reached the end of the file.`,
335
+ hint: `Every module needs a name, like:\n\n module MyApp\n module acme.core`,
336
+ };
337
+ }
338
+ return {
339
+ category: 'SYNTAX ERROR',
340
+ message: `I was expecting a module name after \`module\`, but found \`${found}\`.`,
341
+ hint: `Module names must start with a letter. Example:\n\n module MyApp\n module acme.core`,
342
+ };
343
+ }
344
+ // --- Missing entity/event/record name ---
345
+ if (ruleStack.includes('QualifiedName') && ruleStack.includes('RecDef')) {
346
+ const defType = ruleStack.includes('EntityDefinition')
347
+ ? 'entity'
348
+ : ruleStack.includes('EventDefinition')
349
+ ? 'event'
350
+ : ruleStack.includes('RecordDefinition')
351
+ ? 'record'
352
+ : 'definition';
353
+ if (isEOF) {
354
+ return {
355
+ category: 'MISSING TOKEN',
356
+ message: `I was expecting ${aOrAn(defType)} ${defType} name after \`${prevImage}\`, but reached the end of the file.`,
357
+ hint: `Every ${defType} needs a name and a body. Example:\n\n ${defType} My${capitalize(defType)} {\n name String\n }`,
358
+ };
359
+ }
360
+ if (RESERVED_KEYWORDS.has(found)) {
361
+ return {
362
+ category: 'SYNTAX ERROR',
363
+ message: `\`${found}\` is a reserved keyword and cannot be used as ${aOrAn(defType)} ${defType} name.`,
364
+ hint: `Choose a different name, for example:\n\n ${defType} My${capitalize(found)} { ... }`,
365
+ };
366
+ }
367
+ return {
368
+ category: 'SYNTAX ERROR',
369
+ message: `I was expecting ${aOrAn(defType)} ${defType} name after \`${prevImage}\`, but found \`${found}\`.`,
370
+ hint: `Names must start with a letter. Example:\n\n ${defType} My${capitalize(defType)} { ... }`,
371
+ };
372
+ }
373
+ // --- Missing type on attribute (found comma or '}' when parsing AttributeDefinition) ---
374
+ if ((found === ',' || found === '}') &&
375
+ ruleStack.includes('AttributeDefinition') &&
376
+ ruleStack.includes('RecordSchemaDefinition')) {
377
+ return {
378
+ category: 'SYNTAX ERROR',
379
+ message: `The attribute \`${prevImage}\` is missing a type.`,
380
+ hint: `Each attribute needs a name followed by a type. Example:\n\n name String,\n age Int`,
381
+ };
382
+ }
383
+ // --- Colon after attribute name (coming from another language) ---
384
+ if (found === ':' &&
385
+ ruleStack.includes('AttributeDefinition') &&
386
+ ruleStack.includes('RecordSchemaDefinition')) {
387
+ return {
388
+ category: 'SYNTAX ERROR',
389
+ message: `Attributes don't use colons between the name and type.`,
390
+ hint: `Write the type directly after the name, without a colon:\n\n name String (not name: String)`,
391
+ };
392
+ }
393
+ // --- Equals in attribute definition (coming from another language) ---
394
+ if (found === '=' &&
395
+ ruleStack.includes('AttributeDefinition') &&
396
+ ruleStack.includes('RecordSchemaDefinition')) {
397
+ return {
398
+ category: 'SYNTAX ERROR',
399
+ message: `Attributes don't use \`=\` for assignment.`,
400
+ hint: `To declare an attribute, write: \`name String\`\nTo set a default value, use: \`name String @default("value")\``,
401
+ };
402
+ }
403
+ // --- Missing opening brace for entity/record/event body ---
404
+ if (ruleStack.includes('RecordSchemaDefinition') &&
405
+ !ruleStack.includes('AttributeDefinition') &&
406
+ !isEOF &&
407
+ found !== ',' &&
408
+ found !== '}') {
409
+ const defType = (_a = innermostDefinition(ruleStack)) !== null && _a !== void 0 ? _a : 'definition';
410
+ return {
411
+ category: 'SYNTAX ERROR',
412
+ message: `I was expecting \`{\` to start the ${defType} body, but found \`${found}\`.`,
413
+ hint: `${capitalize(defType)} definitions need braces around their attributes. Example:\n\n ${defType} MyName {\n name String\n }`,
414
+ };
415
+ }
416
+ // --- Trailing comma (found '}' in RecordExtraDefinition context) ---
417
+ if (found === '}' && ruleStack.includes('RecordExtraDefinition')) {
418
+ return {
419
+ category: 'SYNTAX ERROR',
420
+ message: `There is a trailing comma before the closing \`}\`.`,
421
+ hint: `Remove the comma after the last attribute:\n\n entity E {\n name String,\n age Int\n }`,
422
+ };
423
+ }
424
+ // --- Double comma / unexpected comma in entity attributes ---
425
+ if (found === ',' && ruleStack.includes('RecordExtraDefinition')) {
426
+ return {
427
+ category: 'SYNTAX ERROR',
428
+ message: `I found an unexpected comma here.`,
429
+ hint: `It looks like there may be a double comma in the attribute list, or an attribute is missing between two commas.`,
430
+ };
431
+ }
432
+ // --- Unexpected token in entity attribute list (cascading from double comma) ---
433
+ if (ruleStack.includes('RecordExtraDefinition') && !isEOF) {
434
+ return {
435
+ category: 'SYNTAX ERROR',
436
+ message: `I found unexpected \`${found}\` in the attribute list.`,
437
+ hint: `Check for double commas or missing attribute definitions. Each attribute needs a name and a type:\n\n name String,\n age Int`,
438
+ };
439
+ }
440
+ // --- Workflow body parse failure ---
441
+ if (ruleStack.includes('WorkflowDefinition') && ruleStack.includes('Body')) {
442
+ return {
443
+ category: 'SYNTAX ERROR',
444
+ message: `I had trouble parsing the workflow body starting at \`${found}\`.`,
445
+ hint: `Check for missing closing parentheses \`)\`, unclosed braces \`}\`, or incorrect statement syntax inside the workflow.`,
446
+ };
447
+ }
448
+ // --- Reserved keyword used as workflow/agent/flow/decision name ---
449
+ if (!isEOF && RESERVED_KEYWORDS.has(found)) {
450
+ const nameDefRules = [
451
+ ['WorkflowDefinition', 'workflow'],
452
+ ['AgentDefinition', 'agent'],
453
+ ['FlowDefinition', 'flow'],
454
+ ['DecisionDefinition', 'decision'],
455
+ ];
456
+ for (const [rule, defType] of nameDefRules) {
457
+ if (ruleStack.includes(rule) &&
458
+ !ruleStack.includes('Body') &&
459
+ !ruleStack.includes('GenericDefBody') &&
460
+ !ruleStack.includes('FlowDefBody') &&
461
+ !ruleStack.includes('DecisionDefBody')) {
462
+ return {
463
+ category: 'SYNTAX ERROR',
464
+ message: `\`${found}\` is a reserved keyword and cannot be used as ${aOrAn(defType)} ${defType} name.`,
465
+ hint: `Choose a different name, for example:\n\n ${defType} My${capitalize(found)} { ... }`,
466
+ };
467
+ }
468
+ }
469
+ }
470
+ // --- Definition-level failure (can't match any definition type) ---
471
+ if (ruleStack.length >= 2 &&
472
+ ruleStack[ruleStack.length - 1] === 'Definition' &&
473
+ ruleStack[ruleStack.length - 2] === 'ModuleDefinition') {
474
+ const keywordSuggestions = suggest(found, KNOWN_KEYWORDS);
475
+ const suggestionText = formatSuggestions(keywordSuggestions);
476
+ return {
477
+ category: 'SYNTAX ERROR',
478
+ message: `I don't recognize what kind of definition starts with \`${found}\`.`,
479
+ hint: suggestionText
480
+ ? suggestionText
481
+ : `Definitions must start with a keyword like: entity, event, record, workflow, agent, flow, relationship, or a decorator like @public.`,
482
+ };
483
+ }
484
+ // --- Generic NoViableAlt at EOF ---
485
+ if (isEOF) {
486
+ const ctx = innermostContext(ruleStack);
487
+ return {
488
+ category: 'MISSING TOKEN',
489
+ message: `I reached the end of the file${ctx ? ` while parsing ${ctx}` : ''}, but I was expecting more input.`,
490
+ hint: `The definition may be incomplete. Check that nothing is missing at the end.`,
491
+ };
492
+ }
493
+ // --- Generic NoViableAlt ---
494
+ const ctx = innermostContext(ruleStack);
495
+ if (!isEOF && RESERVED_KEYWORDS.has(found)) {
496
+ return {
497
+ category: 'SYNTAX ERROR',
498
+ message: `I got stuck on \`${found}\`${ctx ? ` while parsing ${ctx}` : ''}.`,
499
+ hint: `\`${found}\` is a reserved keyword and cannot be used as a name. Choose a different name.`,
500
+ };
501
+ }
502
+ return {
503
+ category: 'SYNTAX ERROR',
504
+ message: `I got stuck on \`${found}\`${ctx ? ` while parsing ${ctx}` : ''}.`,
505
+ hint: `Check for typos, missing commas, or incorrect punctuation around this location.`,
506
+ };
507
+ }
508
+ // ---------------------------------------------------------------------------
509
+ // Helpers for context extraction from ruleStack
510
+ // ---------------------------------------------------------------------------
511
+ /** Find the innermost "definition" rule (entity, workflow, agent, etc.) */
512
+ function innermostDefinition(ruleStack) {
513
+ for (let i = ruleStack.length - 1; i >= 0; i--) {
514
+ const name = RULE_NAMES[ruleStack[i]];
515
+ if (name &&
516
+ name !== 'name' &&
517
+ name !== 'body' &&
518
+ name !== 'schema body' &&
519
+ name !== 'definition' &&
520
+ name !== 'attribute') {
521
+ return name;
522
+ }
523
+ }
524
+ return undefined;
525
+ }
526
+ /** Get a human-readable description of the innermost parsing context */
527
+ function innermostContext(ruleStack) {
528
+ for (let i = ruleStack.length - 1; i >= 0; i--) {
529
+ const name = RULE_NAMES[ruleStack[i]];
530
+ if (name && name !== 'name' && name !== 'definition') {
531
+ return aOrAn(name) + ' ' + name;
532
+ }
533
+ }
534
+ return undefined;
535
+ }
536
+ function aOrAn(word) {
537
+ return /^[aeiou]/i.test(word) ? 'an' : 'a';
538
+ }
539
+ function capitalize(s) {
540
+ return s.charAt(0).toUpperCase() + s.slice(1);
541
+ }
542
+ // ---------------------------------------------------------------------------
543
+ // Decorator suggestion helpers
544
+ // ---------------------------------------------------------------------------
545
+ /**
546
+ * When the lexer produces a bare `@` token (because `@pubic` isn't a known
547
+ * decorator keyword), look at the source text starting at the `@` to extract
548
+ * the full attempted decorator string like `@pubic`.
549
+ */
550
+ function extractDecoratorAtPosition(source, token) {
551
+ const line = token === null || token === void 0 ? void 0 : token.startLine;
552
+ const col = token === null || token === void 0 ? void 0 : token.startColumn;
553
+ if (!line || !col || isNaN(line) || isNaN(col))
554
+ return undefined;
555
+ const lines = source.split('\n');
556
+ const lineText = lines[line - 1];
557
+ if (!lineText)
558
+ return undefined;
559
+ // Starting from the @ position, grab the @ and the word that follows it
560
+ const rest = lineText.substring(col - 1); // col is 1-based
561
+ const match = rest.match(/^@[a-zA-Z_]\w*/);
562
+ return match ? match[0] : undefined;
563
+ }
564
+ /**
565
+ * Expand the error region to cover the full `@xxx` text instead of just `@`.
566
+ */
567
+ function decoratorRegion(token, attempted) {
568
+ if (!attempted || !(token === null || token === void 0 ? void 0 : token.startLine) || isNaN(token.startLine))
569
+ return undefined;
570
+ return {
571
+ startLine: token.startLine,
572
+ startCol: token.startColumn,
573
+ endLine: token.startLine,
574
+ endCol: token.startColumn + attempted.length - 1,
575
+ };
576
+ }
577
+ /**
578
+ * Build a hint string for an unrecognized decorator, including
579
+ * "Did you mean?" suggestions when a close match exists.
580
+ */
581
+ function buildDecoratorSuggestion(attempted) {
582
+ if (attempted) {
583
+ const suggestions = suggest(attempted, KNOWN_DECORATORS);
584
+ const suggestionText = formatSuggestions(suggestions);
585
+ if (suggestionText) {
586
+ return suggestionText;
587
+ }
588
+ }
589
+ return `Valid decorators include: @public, @id, @ref, @optional, @expr, @as, @catch, and others.\nMake sure the decorator is spelled correctly.`;
590
+ }
591
+ // ---------------------------------------------------------------------------
592
+ // Lexer error humanizer
593
+ // ---------------------------------------------------------------------------
594
+ function humanizeLexerError(raw) {
595
+ const charMatch = raw.match(/unexpected character:\s*(.+?)\s*at offset/i);
596
+ if (charMatch) {
597
+ const ch = charMatch[1].trim();
598
+ // Chevrotain wraps chars in arrows: ->"<- — extract the inner character
599
+ const innerMatch = ch.match(/^->(.+)<-$/);
600
+ const actualChar = innerMatch ? innerMatch[1] : ch;
601
+ // Unclosed string literal
602
+ if (actualChar === '"' || actualChar === '`') {
603
+ const quote = actualChar === '"' ? 'double' : 'backtick';
604
+ return {
605
+ message: `This string literal is missing its closing ${quote} quote \`${actualChar}\`.`,
606
+ hint: `Add the matching closing \`${actualChar}\` at the end of the string. Example:\n\n name ${actualChar}hello world${actualChar}`,
607
+ };
608
+ }
609
+ return {
610
+ message: `I found an unexpected character \`${actualChar}\` that I don't recognize.`,
611
+ hint: `This character isn't valid in agentlang. Check for accidental keystrokes or characters copied from another source.`,
612
+ };
613
+ }
614
+ return { message: raw };
615
+ }
616
+ // ---------------------------------------------------------------------------
617
+ // Token name humanizer
618
+ // ---------------------------------------------------------------------------
619
+ function humanizeTokenName(token) {
620
+ var _a;
621
+ const map = {
622
+ ID: 'a name',
623
+ NAME: 'a name',
624
+ INT: 'a number',
625
+ STRING: 'a string',
626
+ QUOTED_STRING: 'a quoted string',
627
+ TICK_QUOTED_STRING: 'a backtick-quoted string',
628
+ EOF: 'the end of the file',
629
+ WS: 'whitespace',
630
+ };
631
+ // Punctuation tokens — show them as literals
632
+ if (token.length <= 3 && /^[^a-zA-Z]/.test(token)) {
633
+ return `\`${token}\``;
634
+ }
635
+ return (_a = map[token]) !== null && _a !== void 0 ? _a : `\`${token}\``;
636
+ }
637
+ // ---------------------------------------------------------------------------
638
+ // Fallback humanizer
639
+ // ---------------------------------------------------------------------------
640
+ function humanizeFallback(raw) {
641
+ const msg = raw.replace(/^\w+Exception:\s*/i, '');
642
+ const expMatch = msg.match(/Expecting[:\s]+(.+?),?\s*but found[:\s]+'([^']*)'/i);
643
+ if (expMatch) {
644
+ return `I was expecting ${expMatch[1].trim().toLowerCase()}, but found \`${expMatch[2]}\`.`;
645
+ }
646
+ return msg || 'I encountered an unexpected error while parsing.';
647
+ }
648
+ // ---------------------------------------------------------------------------
649
+ // Edit distance & suggestions ("Did you mean?")
650
+ // ---------------------------------------------------------------------------
651
+ /**
652
+ * Restricted Damerau-Levenshtein distance between two strings.
653
+ * Handles insertions, deletions, substitutions, and adjacent transpositions.
654
+ * Comparison is case-insensitive.
655
+ */
656
+ export function editDistance(a, b) {
657
+ const s = a.toLowerCase();
658
+ const t = b.toLowerCase();
659
+ const sLen = s.length;
660
+ const tLen = t.length;
661
+ // Quick exits
662
+ if (s === t)
663
+ return 0;
664
+ if (sLen === 0)
665
+ return tLen;
666
+ if (tLen === 0)
667
+ return sLen;
668
+ // Full matrix for restricted Damerau-Levenshtein
669
+ const d = Array.from({ length: sLen + 1 }, () => new Array(tLen + 1).fill(0));
670
+ for (let i = 0; i <= sLen; i++)
671
+ d[i][0] = i;
672
+ for (let j = 0; j <= tLen; j++)
673
+ d[0][j] = j;
674
+ for (let i = 1; i <= sLen; i++) {
675
+ for (let j = 1; j <= tLen; j++) {
676
+ const cost = s[i - 1] === t[j - 1] ? 0 : 1;
677
+ d[i][j] = Math.min(d[i - 1][j] + 1, // deletion
678
+ d[i][j - 1] + 1, // insertion
679
+ d[i - 1][j - 1] + cost // substitution
680
+ );
681
+ // Transposition
682
+ if (i > 1 && j > 1 && s[i - 1] === t[j - 2] && s[i - 2] === t[j - 1]) {
683
+ d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost);
684
+ }
685
+ }
686
+ }
687
+ return d[sLen][tLen];
688
+ }
689
+ /**
690
+ * Find the closest matches to `input` from a list of `candidates`.
691
+ * Returns up to `maxResults` candidates sorted by ascending distance.
692
+ *
693
+ * The effective max distance scales with input length to avoid noisy
694
+ * suggestions on short inputs:
695
+ * length 1-3 → max 1, length 4-6 → max 2, length 7+ → max 3
696
+ */
697
+ export function suggest(input, candidates, maxDistance, maxResults = 4) {
698
+ if (!input)
699
+ return [];
700
+ const effectiveMax = maxDistance !== null && maxDistance !== void 0 ? maxDistance : (input.length <= 3 ? 1 : input.length <= 6 ? 2 : 3);
701
+ const scored = candidates
702
+ .map(c => ({ candidate: c, distance: editDistance(input, c) }))
703
+ .filter(({ distance }) => distance > 0 && distance <= effectiveMax)
704
+ .sort((a, b) => a.distance - b.distance);
705
+ return scored.slice(0, maxResults).map(s => s.candidate);
706
+ }
707
+ /**
708
+ * Format suggestion list into a hint string:
709
+ * - 0 matches → undefined
710
+ * - 1 match → "Did you mean `X`?"
711
+ * - 2+ matches → "These names seem close:\n X\n Y"
712
+ */
713
+ export function formatSuggestions(suggestions) {
714
+ if (suggestions.length === 0)
715
+ return undefined;
716
+ if (suggestions.length === 1)
717
+ return `Did you mean \`${suggestions[0]}\`?`;
718
+ const list = suggestions.map(s => ` ${s}`).join('\n');
719
+ return `These names seem close though:\n\n${list}`;
720
+ }
721
+ // ---------------------------------------------------------------------------
722
+ // Known keywords for suggestion matching
723
+ // ---------------------------------------------------------------------------
724
+ /** Top-level definition keywords in agentlang */
725
+ export const KNOWN_KEYWORDS = [
726
+ 'entity',
727
+ 'event',
728
+ 'record',
729
+ 'workflow',
730
+ 'agent',
731
+ 'flow',
732
+ 'relationship',
733
+ 'resolver',
734
+ 'scenario',
735
+ 'directive',
736
+ 'glossaryEntry',
737
+ 'eval',
738
+ 'decision',
739
+ 'import',
740
+ 'module',
741
+ ];
742
+ /**
743
+ * All grammar keywords that conflict with identifier (ID) positions.
744
+ * When a user writes e.g. `entity query { ... }`, the lexer tokenizes `query`
745
+ * as a keyword token instead of ID, causing a confusing parse error.
746
+ * This set lets us detect that case and produce a clear message.
747
+ */
748
+ export const RESERVED_KEYWORDS = new Set([
749
+ // Top-level definition keywords
750
+ ...KNOWN_KEYWORDS,
751
+ // Control flow
752
+ 'if',
753
+ 'else',
754
+ 'for',
755
+ 'in',
756
+ 'return',
757
+ 'throw',
758
+ 'await',
759
+ // CRUD / resolver operations
760
+ 'create',
761
+ 'update',
762
+ 'delete',
763
+ 'read',
764
+ 'query',
765
+ 'purge',
766
+ 'upsert',
767
+ // Logical / boolean
768
+ 'true',
769
+ 'false',
770
+ 'not',
771
+ 'or',
772
+ 'and',
773
+ // Schema / relationship
774
+ 'extends',
775
+ 'contains',
776
+ 'between',
777
+ // Decision / case
778
+ 'case',
779
+ // RBAC
780
+ 'roles',
781
+ 'allow',
782
+ 'where',
783
+ // SQL-like
784
+ 'like',
785
+ // Misc
786
+ 'backoff',
787
+ 'attempts',
788
+ 'subscribe',
789
+ ]);
790
+ // ---------------------------------------------------------------------------
791
+ // Snippet renderer – show source context with ^^^ underlines
792
+ // ---------------------------------------------------------------------------
793
+ export function renderSnippet(source, region, contextLines = 2) {
794
+ var _a;
795
+ const lines = source.split('\n');
796
+ const totalLines = lines.length;
797
+ // Compute visible line range (1-based internally, but array is 0-based)
798
+ const firstVisible = Math.max(1, region.startLine - contextLines);
799
+ const lastVisible = Math.min(totalLines, region.endLine + contextLines);
800
+ // Width of the widest line number for alignment
801
+ const gutterWidth = String(lastVisible).length;
802
+ const output = [];
803
+ for (let lineNum = firstVisible; lineNum <= lastVisible; lineNum++) {
804
+ const lineText = (_a = lines[lineNum - 1]) !== null && _a !== void 0 ? _a : '';
805
+ const numStr = String(lineNum).padStart(gutterWidth);
806
+ const isErrorLine = lineNum >= region.startLine && lineNum <= region.endLine;
807
+ const prefix = isErrorLine ? `${numStr} | ` : `${numStr} | `;
808
+ output.push(`${prefix}${lineText}`);
809
+ // Add underline on error lines
810
+ if (isErrorLine) {
811
+ const underlineStart = lineNum === region.startLine ? region.startCol : 1;
812
+ const underlineEnd = lineNum === region.endLine ? region.endCol : lineText.length;
813
+ const caretCount = Math.max(1, underlineEnd - underlineStart + 1);
814
+ const padding = ' '.repeat(gutterWidth) + ' ' + ' '.repeat(underlineStart - 1);
815
+ output.push(`${padding}${'~'.repeat(caretCount)}`);
816
+ }
817
+ }
818
+ return output.join('\n');
819
+ }
820
+ // ---------------------------------------------------------------------------
821
+ // Error formatter
822
+ // ---------------------------------------------------------------------------
823
+ const SEPARATOR_WIDTH = 60;
824
+ function header(category, file) {
825
+ const left = `-- ${category} `;
826
+ const right = ` ${file}`;
827
+ const dashCount = Math.max(3, SEPARATOR_WIDTH - left.length - right.length);
828
+ return `${left}${'-'.repeat(dashCount)}${right}`;
829
+ }
830
+ export function formatError(err, source) {
831
+ const parts = [];
832
+ // 1. Header
833
+ parts.push(header(err.category, err.file));
834
+ parts.push('');
835
+ // 2. Message
836
+ parts.push(err.message);
837
+ parts.push('');
838
+ // 3. Source snippet
839
+ parts.push(renderSnippet(source, err.region));
840
+ // 4. Hint
841
+ if (err.hint) {
842
+ parts.push('');
843
+ parts.push(`Hint: ${err.hint}`);
844
+ }
845
+ return parts.join('\n');
846
+ }
847
+ export function formatErrors(errors, source) {
848
+ if (errors.length === 0)
849
+ return '';
850
+ // Show only the first error to avoid cascading noise
851
+ // but include up to 3 for genuinely independent errors.
852
+ const toShow = deduplicateErrors(errors).slice(0, 3);
853
+ return toShow.map(err => formatError(err, source)).join('\n\n');
854
+ }
855
+ // ---------------------------------------------------------------------------
856
+ // Deduplication – avoid cascading / duplicate errors on the same line
857
+ // ---------------------------------------------------------------------------
858
+ function deduplicateErrors(errors) {
859
+ const seen = new Set();
860
+ const result = [];
861
+ for (const err of errors) {
862
+ if (!seen.has(err.region.startLine)) {
863
+ seen.add(err.region.startLine);
864
+ result.push(err);
865
+ }
866
+ }
867
+ return result;
868
+ }
869
+ // ---------------------------------------------------------------------------
870
+ // Main entry point – collect + format from a LangiumDocument
871
+ // ---------------------------------------------------------------------------
872
+ export function getFormattedErrors(document) {
873
+ const errors = collectErrors(document);
874
+ if (errors.length === 0)
875
+ return undefined;
876
+ const source = document.textDocument.getText();
877
+ return formatErrors(errors, source);
878
+ }
879
+ //# sourceMappingURL=error-reporter.js.map