@forgehive/forge-cli 0.3.6 → 0.3.8

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.
@@ -7,9 +7,11 @@ interface TaskLocation {
7
7
  }
8
8
 
9
9
  interface SchemaProperty {
10
+ name?: string
10
11
  type: string
11
12
  optional?: boolean
12
13
  default?: string
14
+ properties?: Record<string, SchemaProperty>
13
15
  }
14
16
 
15
17
  interface InputSchema {
@@ -20,6 +22,18 @@ interface InputSchema {
20
22
  interface OutputType {
21
23
  type: string
22
24
  properties?: Record<string, SchemaProperty>
25
+ elementType?: OutputType
26
+ }
27
+
28
+ export interface FingerprintError {
29
+ type: 'parsing' | 'analysis' | 'boundary' | 'schema'
30
+ message: string
31
+ location?: {
32
+ file: string
33
+ line?: number
34
+ column?: number
35
+ }
36
+ details?: Record<string, unknown>
23
37
  }
24
38
 
25
39
  interface TaskFingerprint {
@@ -28,16 +42,30 @@ interface TaskFingerprint {
28
42
  location: TaskLocation
29
43
  inputSchema: InputSchema
30
44
  outputType: OutputType
31
- boundaries: string[]
45
+ boundaries: BoundaryFingerprint[]
32
46
  hash: string
33
47
  }
34
48
 
49
+ interface BoundaryFingerprint {
50
+ name: string
51
+ input: SchemaProperty[]
52
+ output: OutputType
53
+ errors: FingerprintError[]
54
+ }
55
+
35
56
  // Simplified interface for filesystem output (excludes name, location, hash)
36
57
  export interface TaskFingerprintOutput {
37
58
  description?: string
38
59
  inputSchema: InputSchema
39
60
  outputType: OutputType
40
- boundaries: string[]
61
+ boundaries: BoundaryFingerprint[]
62
+ errors: FingerprintError[]
63
+ analysisMetadata: {
64
+ timestamp: string
65
+ filePath: string
66
+ success: boolean
67
+ analysisVersion: string
68
+ }
41
69
  }
42
70
 
43
71
  // Hash generation function
@@ -51,8 +79,23 @@ function generateHash(input: string): string {
51
79
  return Math.abs(hash).toString(36)
52
80
  }
53
81
 
54
- // TypeScript AST analysis function
55
- function extractTaskFingerprints(sourceCode: string, filePath: string): TaskFingerprint[] {
82
+ // TypeScript AST analysis function with error collection
83
+ function extractTaskFingerprintsWithErrors(sourceCode: string, filePath: string, errors: FingerprintError[]): TaskFingerprint[] {
84
+ try {
85
+ return extractTaskFingerprintsInternal(sourceCode, filePath, errors)
86
+ } catch (error) {
87
+ errors.push({
88
+ type: 'parsing',
89
+ message: error instanceof Error ? error.message : 'TypeScript parsing failed',
90
+ location: { file: filePath },
91
+ details: { error: error instanceof Error ? error.stack : String(error) }
92
+ })
93
+ return []
94
+ }
95
+ }
96
+
97
+ // TypeScript AST analysis function with error collection
98
+ function extractTaskFingerprintsInternal(sourceCode: string, filePath: string, errors: FingerprintError[]): TaskFingerprint[] {
56
99
  const sourceFile = ts.createSourceFile(
57
100
  filePath,
58
101
  sourceCode,
@@ -61,6 +104,7 @@ function extractTaskFingerprints(sourceCode: string, filePath: string): TaskFing
61
104
  )
62
105
 
63
106
  const fingerprints: TaskFingerprint[] = []
107
+ const processedNodes = new Set<ts.Node>() // Track processed createTask nodes to prevent duplicates
64
108
  let schemaNode: ts.Expression | null = null
65
109
  let boundariesNode: ts.Expression | null = null
66
110
 
@@ -84,14 +128,17 @@ function extractTaskFingerprints(sourceCode: string, filePath: string): TaskFing
84
128
  function findCreateTask(node: ts.Node): void {
85
129
  // Look for createTask calls
86
130
  if (ts.isCallExpression(node) &&
87
- ts.isIdentifier(node.expression) &&
88
- node.expression.text === 'createTask') {
89
-
90
- const taskName = extractTaskName(node, sourceFile)
91
- if (taskName) {
92
- const fingerprint = analyzeCreateTaskCall(node, sourceFile, filePath, taskName, schemaNode, boundariesNode)
93
- if (fingerprint) {
94
- fingerprints.push(fingerprint)
131
+ ts.isIdentifier(node.expression) &&
132
+ node.expression.text === 'createTask') {
133
+
134
+ if (!processedNodes.has(node)) {
135
+ processedNodes.add(node)
136
+ const taskName = extractTaskName(node, sourceFile)
137
+ if (taskName) {
138
+ const fingerprint = analyzeCreateTaskCall(node, sourceFile, filePath, taskName, schemaNode, boundariesNode, errors)
139
+ if (fingerprint) {
140
+ fingerprints.push(fingerprint)
141
+ }
95
142
  }
96
143
  }
97
144
  }
@@ -100,15 +147,18 @@ function extractTaskFingerprints(sourceCode: string, filePath: string): TaskFing
100
147
  if (ts.isVariableStatement(node) && node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
101
148
  node.declarationList.declarations.forEach(decl => {
102
149
  if (ts.isVariableDeclaration(decl) &&
103
- decl.initializer &&
104
- ts.isCallExpression(decl.initializer) &&
105
- ts.isIdentifier(decl.initializer.expression) &&
106
- decl.initializer.expression.text === 'createTask') {
150
+ decl.initializer &&
151
+ ts.isCallExpression(decl.initializer) &&
152
+ ts.isIdentifier(decl.initializer.expression) &&
153
+ decl.initializer.expression.text === 'createTask') {
107
154
 
108
- const taskName = ts.isIdentifier(decl.name) ? decl.name.text : 'unknown'
109
- const fingerprint = analyzeCreateTaskCall(decl.initializer, sourceFile, filePath, taskName, schemaNode, boundariesNode)
110
- if (fingerprint) {
111
- fingerprints.push(fingerprint)
155
+ if (!processedNodes.has(decl.initializer)) {
156
+ processedNodes.add(decl.initializer)
157
+ const taskName = ts.isIdentifier(decl.name) ? decl.name.text : 'unknown'
158
+ const fingerprint = analyzeCreateTaskCall(decl.initializer, sourceFile, filePath, taskName, schemaNode, boundariesNode, errors)
159
+ if (fingerprint) {
160
+ fingerprints.push(fingerprint)
161
+ }
112
162
  }
113
163
  }
114
164
  })
@@ -142,47 +192,119 @@ function analyzeCreateTaskCall(
142
192
  filePath: string,
143
193
  taskName: string,
144
194
  schemaNode: ts.Expression | null = null,
145
- boundariesNode: ts.Expression | null = null
195
+ boundariesNode: ts.Expression | null = null,
196
+ errors: FingerprintError[] = []
146
197
  ): TaskFingerprint | null {
147
198
  try {
148
199
  const position = sourceFile.getLineAndCharacterOfPosition(node.getStart())
149
200
  const args = node.arguments
150
201
 
151
- // Use pre-found schema or fall back to argument analysis
202
+ // Analyze createTask({ schema, boundaries, fn }) structure
152
203
  let inputSchema: InputSchema = { type: 'object', properties: {} }
153
- if (schemaNode) {
154
- inputSchema = analyzeSchemaArg(schemaNode, sourceFile)
155
- } else if (args[0]) {
156
- inputSchema = analyzeSchemaArg(args[0], sourceFile)
157
- }
204
+ let boundaries: BoundaryFingerprint[] = []
205
+ let boundaryTypes: Map<string, OutputType> = new Map()
206
+
207
+ if (args[0] && ts.isObjectLiteralExpression(args[0])) {
208
+ const schemaProperty = args[0].properties.find(prop =>
209
+ ts.isPropertyAssignment(prop) &&
210
+ ts.isIdentifier(prop.name) &&
211
+ prop.name.text === 'schema'
212
+ )
213
+ const boundariesProperty = args[0].properties.find(prop =>
214
+ ts.isPropertyAssignment(prop) &&
215
+ ts.isIdentifier(prop.name) &&
216
+ prop.name.text === 'boundaries'
217
+ )
158
218
 
159
- // Use pre-found boundaries or fall back to argument analysis - simplified to just names
160
- let boundaries: string[] = []
161
- if (boundariesNode) {
162
- boundaries = analyzeBoundariesArg(boundariesNode, sourceFile)
163
- } else if (args[1]) {
164
- boundaries = analyzeBoundariesArg(args[1], sourceFile)
219
+ if (schemaNode) {
220
+ try {
221
+ inputSchema = analyzeSchemaArg(schemaNode, sourceFile)
222
+ } catch (error) {
223
+ errors.push({
224
+ type: 'schema',
225
+ message: error instanceof Error ? error.message : 'Schema analysis failed',
226
+ location: { file: filePath, line: position.line + 1, column: position.character + 1 },
227
+ details: { taskName, schemaSource: 'variable' }
228
+ })
229
+ }
230
+ } else if (schemaProperty && ts.isPropertyAssignment(schemaProperty)) {
231
+ try {
232
+ inputSchema = analyzeSchemaArg(schemaProperty.initializer, sourceFile)
233
+ } catch (error) {
234
+ errors.push({
235
+ type: 'schema',
236
+ message: error instanceof Error ? error.message : 'Schema analysis failed',
237
+ location: { file: filePath, line: position.line + 1, column: position.character + 1 },
238
+ details: { taskName, schemaSource: 'property' }
239
+ })
240
+ }
241
+ }
242
+
243
+ if (boundariesNode) {
244
+ try {
245
+ const boundaryInfo = analyzeBoundariesWithTypes(boundariesNode, sourceFile)
246
+ boundaries = boundaryInfo.boundaries
247
+ boundaryTypes = boundaryInfo.types
248
+ } catch (error) {
249
+ errors.push({
250
+ type: 'boundary',
251
+ message: error instanceof Error ? error.message : 'Boundary analysis failed',
252
+ location: { file: filePath, line: position.line + 1, column: position.character + 1 },
253
+ details: { taskName, boundarySource: 'variable' }
254
+ })
255
+ }
256
+ } else if (boundariesProperty && ts.isPropertyAssignment(boundariesProperty)) {
257
+ try {
258
+ const boundaryInfo = analyzeBoundariesWithTypes(boundariesProperty.initializer, sourceFile)
259
+ boundaries = boundaryInfo.boundaries
260
+ boundaryTypes = boundaryInfo.types
261
+ } catch (error) {
262
+ errors.push({
263
+ type: 'boundary',
264
+ message: error instanceof Error ? error.message : 'Boundary analysis failed',
265
+ location: { file: filePath, line: position.line + 1, column: position.character + 1 },
266
+ details: { taskName, boundarySource: 'property' }
267
+ })
268
+ }
269
+ }
165
270
  }
166
271
 
167
272
  // Extract function output type with better detection
168
273
  let outputType: OutputType = { type: 'unknown' }
169
- const functionArg = args[2]
274
+ let functionArg: ts.Expression | undefined
275
+
276
+ // Extract function from createTask({ fn }) structure
277
+ if (args[0] && ts.isObjectLiteralExpression(args[0])) {
278
+ const fnProperty = args[0].properties.find(prop =>
279
+ ts.isPropertyAssignment(prop) &&
280
+ ts.isIdentifier(prop.name) &&
281
+ prop.name.text === 'fn'
282
+ )
283
+ if (fnProperty && ts.isPropertyAssignment(fnProperty)) {
284
+ functionArg = fnProperty.initializer
285
+ }
286
+ }
170
287
 
171
288
  if (functionArg) {
172
289
  if (ts.isFunctionExpression(functionArg) || ts.isArrowFunction(functionArg)) {
290
+ // Collect errors from main task function
291
+ const mainFunctionErrors = analyzeMainTaskFunctionErrors(functionArg, sourceFile, taskName)
292
+ errors.push(...mainFunctionErrors)
293
+
173
294
  // Better return type extraction
174
295
  if (functionArg.type) {
175
296
  const typeString = cleanTypeString(functionArg.type.getText(sourceFile))
176
297
  outputType = { type: typeString }
177
298
  } else {
178
- // Try to infer from return statements with better object analysis
179
- outputType = inferDetailedReturnType(functionArg, sourceFile)
299
+ // Try to infer from return statements with boundary type information
300
+ outputType = inferDetailedReturnType(functionArg, sourceFile, boundaryTypes)
180
301
  }
181
302
  }
182
303
  }
183
304
 
184
305
  // Generate hash from task signature
185
- const hashInput = `${taskName}:${JSON.stringify(inputSchema)}:${JSON.stringify(boundaries)}`
306
+ const boundaryNames = boundaries.map(b => b.name)
307
+ const hashInput = `${taskName}:${JSON.stringify(inputSchema)}:${JSON.stringify(boundaryNames)}`
186
308
  const hash = generateHash(hashInput)
187
309
 
188
310
  return {
@@ -204,9 +326,39 @@ function analyzeCreateTaskCall(
204
326
  }
205
327
 
206
328
  // Enhanced return type inference with detailed object analysis
207
- function inferDetailedReturnType(func: ts.FunctionExpression | ts.ArrowFunction, _sourceFile: ts.SourceFile): OutputType {
329
+ function inferDetailedReturnType(func: ts.FunctionExpression | ts.ArrowFunction, sourceFile: ts.SourceFile, boundaryTypes: Map<string, OutputType> = new Map()): OutputType {
208
330
  let returnType: OutputType = { type: 'unknown' }
209
331
 
332
+ // First, collect variable declarations and their types within the function
333
+ const variableTypes = new Map<string, OutputType>()
334
+
335
+ function collectVariableDeclarations(node: ts.Node): void {
336
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
337
+ const varName = node.name.text
338
+
339
+ // Handle await expressions specially for boundary calls
340
+ if (ts.isAwaitExpression(node.initializer) &&
341
+ ts.isCallExpression(node.initializer.expression) &&
342
+ ts.isIdentifier(node.initializer.expression.expression)) {
343
+
344
+ const boundaryName = node.initializer.expression.expression.text
345
+ const boundaryType = boundaryTypes.get(boundaryName)
346
+
347
+ if (boundaryType && typeof boundaryType === 'object') {
348
+ // Store the detailed type information
349
+ variableTypes.set(varName, boundaryType)
350
+ } else {
351
+ const varType = inferTypeFromExpression(node.initializer, sourceFile, variableTypes, boundaryTypes)
352
+ variableTypes.set(varName, { type: varType })
353
+ }
354
+ } else {
355
+ const varType = inferTypeFromExpression(node.initializer, sourceFile, variableTypes, boundaryTypes)
356
+ variableTypes.set(varName, { type: varType })
357
+ }
358
+ }
359
+ ts.forEachChild(node, collectVariableDeclarations)
360
+ }
361
+
210
362
  function visitReturnStatements(node: ts.Node): void {
211
363
  if (ts.isReturnStatement(node) && node.expression) {
212
364
  if (ts.isObjectLiteralExpression(node.expression)) {
@@ -216,31 +368,61 @@ function inferDetailedReturnType(func: ts.FunctionExpression | ts.ArrowFunction,
216
368
  if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
217
369
  // Handle explicit property assignments: { propName: value }
218
370
  const propName = prop.name.text
219
- let propType = 'unknown'
220
-
221
- // Try to infer property type from the initializer
222
- if (ts.isStringLiteral(prop.initializer)) {
223
- propType = 'string'
224
- } else if (ts.isNumericLiteral(prop.initializer)) {
225
- propType = 'number'
226
- } else if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
227
- prop.initializer.kind === ts.SyntaxKind.FalseKeyword) {
228
- propType = 'boolean'
229
- } else if (ts.isIdentifier(prop.initializer)) {
230
- // Variable reference - try to infer from name
231
- const varName = prop.initializer.text
232
- propType = inferTypeFromVariableName(varName)
233
- } else if (ts.isPropertyAccessExpression(prop.initializer)) {
234
- // Property access like response.handler
235
- propType = 'unknown'
236
- }
237
371
 
238
- properties[propName] = { type: propType }
372
+ // Check if it's a property access like result1.result
373
+ if (ts.isPropertyAccessExpression(prop.initializer)) {
374
+ const baseExpr = prop.initializer.expression
375
+ const propertyName = prop.initializer.name.text
376
+
377
+ if (ts.isIdentifier(baseExpr)) {
378
+ const baseVarType = variableTypes.get(baseExpr.text)
379
+ if (baseVarType && typeof baseVarType === 'object' && baseVarType.properties) {
380
+ const propertyType = baseVarType.properties[propertyName]
381
+ if (propertyType) {
382
+ properties[propName] = propertyType
383
+ } else {
384
+ properties[propName] = { type: 'unknown' }
385
+ }
386
+ } else {
387
+ properties[propName] = { type: 'unknown' }
388
+ }
389
+ } else {
390
+ properties[propName] = { type: 'unknown' }
391
+ }
392
+ } else {
393
+ const propType = inferTypeFromExpression(prop.initializer, sourceFile, variableTypes, boundaryTypes)
394
+
395
+ // Handle identifiers that might reference boundary call results
396
+ if (ts.isIdentifier(prop.initializer)) {
397
+ const varType = variableTypes.get(prop.initializer.text)
398
+ if (varType && typeof varType === 'object' && varType.type) {
399
+ properties[propName] = { type: varType.type }
400
+ } else {
401
+ properties[propName] = { type: propType }
402
+ }
403
+ } else {
404
+ properties[propName] = { type: propType }
405
+ }
406
+ }
239
407
  } else if (ts.isShorthandPropertyAssignment(prop)) {
240
408
  // Handle shorthand properties: { propName } (equivalent to { propName: propName })
241
409
  const propName = prop.name.text
242
- const propType = inferTypeFromVariableName(propName)
243
- properties[propName] = { type: propType }
410
+ const varType = variableTypes.get(propName)
411
+
412
+ if (varType && typeof varType === 'object' && varType.type) {
413
+ // If we have detailed type information from boundary calls
414
+ if (varType.type === 'object' && varType.properties) {
415
+ properties[propName] = varType
416
+ } else if (varType.type === 'array' && varType.elementType) {
417
+ // Handle arrays with elementType information
418
+ properties[propName] = varType
419
+ } else {
420
+ properties[propName] = { type: varType.type }
421
+ }
422
+ } else {
423
+ const propType = varType ? (varType.type || 'unknown') : inferTypeFromIdentifier(prop.name.text, boundaryTypes)
424
+ properties[propName] = { type: propType }
425
+ }
244
426
  }
245
427
  })
246
428
 
@@ -257,53 +439,103 @@ function inferDetailedReturnType(func: ts.FunctionExpression | ts.ArrowFunction,
257
439
  } else if (ts.isNumericLiteral(node.expression)) {
258
440
  returnType = { type: 'number' }
259
441
  } else if (node.expression.kind === ts.SyntaxKind.TrueKeyword ||
260
- node.expression.kind === ts.SyntaxKind.FalseKeyword) {
442
+ node.expression.kind === ts.SyntaxKind.FalseKeyword) {
261
443
  returnType = { type: 'boolean' }
262
444
  } else if (ts.isIdentifier(node.expression)) {
263
445
  // Single variable return
264
- const varType = inferTypeFromVariableName(node.expression.text)
265
- returnType = { type: varType }
446
+ const varType = variableTypes.get(node.expression.text)
447
+ if (varType) {
448
+ returnType = varType
449
+ } else {
450
+ const inferredType = inferTypeFromIdentifier(node.expression.text, boundaryTypes)
451
+ returnType = { type: inferredType }
452
+ }
266
453
  }
267
454
  }
268
455
  ts.forEachChild(node, visitReturnStatements)
269
456
  }
270
457
 
271
458
  if (func.body) {
459
+ // First pass: collect variable declarations
460
+ collectVariableDeclarations(func.body)
461
+ // Second pass: analyze return statements
272
462
  visitReturnStatements(func.body)
273
463
  }
274
464
 
275
465
  return returnType
276
466
  }
277
467
 
278
- // Helper function to infer types from variable names
279
- function inferTypeFromVariableName(varName: string): string {
280
- // Common patterns for type inference based on variable names
281
- if (varName.includes('path') || varName.includes('Path') ||
282
- varName.includes('name') || varName.includes('Name') ||
283
- varName.includes('descriptor') || varName.includes('Descriptor') ||
284
- varName.includes('fileName') || varName.includes('handler') ||
285
- varName.includes('url') || varName.includes('id') ||
286
- varName.includes('uuid') || varName.includes('token')) {
468
+ // Helper function to infer type from any expression
469
+ function inferTypeFromExpression(expr: ts.Expression, sourceFile: ts.SourceFile, variableTypes: Map<string, OutputType>, boundaryTypes: Map<string, OutputType> = new Map()): string {
470
+ if (ts.isStringLiteral(expr)) {
287
471
  return 'string'
288
- } else if (varName.includes('count') || varName.includes('Count') ||
289
- varName.includes('size') || varName.includes('Size') ||
290
- varName.includes('length') || varName.includes('Length') ||
291
- varName.includes('index') || varName.includes('Index')) {
472
+ } else if (ts.isNumericLiteral(expr)) {
292
473
  return 'number'
293
- } else if (varName.includes('is') || varName.includes('has') ||
294
- varName.includes('can') || varName.includes('should') ||
295
- varName.includes('enabled') || varName.includes('success')) {
474
+ } else if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword) {
296
475
  return 'boolean'
297
- } else if (varName.includes('config') || varName.includes('Config') ||
298
- varName.includes('options') || varName.includes('Options') ||
299
- varName.includes('data') || varName.includes('result') ||
300
- varName.includes('response') || varName.includes('error')) {
476
+ } else if (ts.isArrayLiteralExpression(expr)) {
477
+ return 'array'
478
+ } else if (ts.isObjectLiteralExpression(expr)) {
479
+ return 'object'
480
+ } else if (ts.isIdentifier(expr)) {
481
+ // Check if we know the type from variable declarations
482
+ const varType = variableTypes.get(expr.text)
483
+ if (varType) {
484
+ return varType.type || 'unknown'
485
+ }
486
+ return inferTypeFromIdentifier(expr.text, boundaryTypes)
487
+ } else if (ts.isCallExpression(expr)) {
488
+ // Handle method calls like array.reduce(), boundary calls, etc.
489
+ if (ts.isPropertyAccessExpression(expr.expression)) {
490
+ const methodName = expr.expression.name.text
491
+ if (methodName === 'reduce') {
492
+ // For reduce, infer type from initial value (second argument)
493
+ if (expr.arguments.length > 1) {
494
+ const initialValue = expr.arguments[1]
495
+ return inferTypeFromExpression(initialValue, sourceFile, variableTypes, boundaryTypes)
496
+ }
497
+ return 'number' // Common case for reduce operations
498
+ } else if (methodName === 'map' || methodName === 'filter') {
499
+ // These typically return arrays
500
+ return 'array'
501
+ } else if (methodName === 'join' || methodName === 'toString') {
502
+ // These return strings
503
+ return 'string'
504
+ } else if (methodName === 'length' || methodName === 'indexOf' || methodName === 'findIndex') {
505
+ // These return numbers
506
+ return 'number'
507
+ }
508
+ } else if (ts.isIdentifier(expr.expression)) {
509
+ // Direct function calls - could be boundary functions
510
+ const functionName = expr.expression.text
511
+ const boundaryType = boundaryTypes.get(functionName)
512
+ if (boundaryType) {
513
+ // If boundaryType is an object with type info, return the type
514
+ if (typeof boundaryType === 'object' && boundaryType.type) {
515
+ return boundaryType.type
516
+ }
517
+ return typeof boundaryType === 'string' ? boundaryType : 'unknown'
518
+ }
519
+ return 'unknown'
520
+ }
521
+ return 'unknown'
522
+ } else if (ts.isAwaitExpression(expr)) {
523
+ // Handle await expressions - analyze the awaited expression
524
+ return inferTypeFromExpression(expr.expression, sourceFile, variableTypes, boundaryTypes)
525
+ } else if (ts.isPropertyAccessExpression(expr)) {
526
+ // Handle property access like obj.prop - try to infer from base object
527
+ const baseType = inferTypeFromExpression(expr.expression, sourceFile, variableTypes, boundaryTypes)
528
+ if (baseType === 'object') {
529
+ return 'unknown' // Could be any property type
530
+ }
301
531
  return 'unknown'
302
532
  }
303
533
 
304
534
  return 'unknown'
305
535
  }
306
536
 
537
+
538
+
307
539
  // Clean up type strings (remove Promise wrappers for boundaries)
308
540
  function cleanTypeString(typeString: string): string {
309
541
  // Remove Promise wrapper for boundary functions
@@ -340,33 +572,43 @@ function analyzeSchemaArg(node: ts.Expression, sourceFile: ts.SourceFile): Input
340
572
  }
341
573
 
342
574
  // Enhanced schema property analysis
343
- function analyzeSchemaProp(node: ts.Expression, _sourceFile: ts.SourceFile): SchemaProperty {
575
+ function analyzeSchemaProp(node: ts.Expression, sourceFile: ts.SourceFile): SchemaProperty {
344
576
  // Analyze Schema.string(), Schema.number(), etc.
345
577
  if (ts.isCallExpression(node)) {
346
578
  if (ts.isPropertyAccessExpression(node.expression) &&
347
- ts.isIdentifier(node.expression.expression) &&
348
- node.expression.expression.text === 'Schema') {
579
+ ts.isIdentifier(node.expression.expression) &&
580
+ node.expression.expression.text === 'Schema') {
349
581
 
350
582
  const methodName = node.expression.name.text
351
- let baseType: SchemaProperty = { type: getSchemaTypeFromMethod(methodName) }
352
-
353
- // Check for chained methods like .optional() or .default()
354
- let current = node
355
- while (current.parent && ts.isCallExpression(current.parent)) {
356
- current = current.parent
357
- if (ts.isPropertyAccessExpression(current.expression)) {
358
- const chainedMethod = current.expression.name.text
359
- if (chainedMethod === 'optional') {
360
- baseType = { ...baseType, optional: true }
361
- } else if (chainedMethod === 'default' && current.arguments[0]) {
362
- baseType = { ...baseType, default: current.arguments[0].getText() }
363
- }
364
- }
365
- }
583
+ const baseType: SchemaProperty = { type: getSchemaTypeFromMethod(methodName) }
366
584
 
367
585
  return baseType
368
586
  }
369
587
  }
588
+
589
+ // Handle chained calls like Schema.number().optional()
590
+ if (ts.isCallExpression(node)) {
591
+ if (ts.isPropertyAccessExpression(node.expression)) {
592
+ const chainedMethod = node.expression.name.text
593
+ if (chainedMethod === 'optional') {
594
+ // This is a .optional() call, get the base type
595
+ const baseCall = node.expression.expression
596
+ if (ts.isCallExpression(baseCall)) {
597
+ const baseType = analyzeSchemaProp(baseCall, sourceFile)
598
+ return { ...baseType, optional: true }
599
+ }
600
+ } else if (chainedMethod === 'default') {
601
+ // This is a .default() call, get the base type
602
+ const baseCall = node.expression.expression
603
+ if (ts.isCallExpression(baseCall)) {
604
+ const baseType = analyzeSchemaProp(baseCall, sourceFile)
605
+ const defaultValue = node.arguments[0]?.getText() || 'undefined'
606
+ return { ...baseType, default: defaultValue }
607
+ }
608
+ }
609
+ }
610
+ }
611
+
370
612
  return { type: 'unknown' }
371
613
  }
372
614
 
@@ -381,39 +623,512 @@ function getSchemaTypeFromMethod(methodName: string): string {
381
623
  return typeMap[methodName] || 'unknown'
382
624
  }
383
625
 
384
- function analyzeBoundariesArg(node: ts.Expression, _sourceFile: ts.SourceFile): string[] {
385
- const boundaries: string[] = []
386
626
 
387
- // Handle variable references (e.g., when boundaries is defined as const boundaries = ...)
388
- if (ts.isIdentifier(node) && node.text === 'boundaries') {
389
- // This case is now handled by pre-finding the boundaries node
390
- return []
391
- }
627
+ // Enhanced boundary analysis that extracts detailed boundary information
628
+ function analyzeBoundariesWithTypes(node: ts.Expression, sourceFile: ts.SourceFile): { names: string[], types: Map<string, OutputType>, boundaries: BoundaryFingerprint[] } {
629
+ const names: string[] = []
630
+ const types = new Map<string, OutputType>()
631
+ const boundaries: BoundaryFingerprint[] = []
392
632
 
393
633
  if (ts.isObjectLiteralExpression(node)) {
394
634
  node.properties.forEach(prop => {
395
635
  if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
396
636
  const boundaryName = prop.name.text
397
- boundaries.push(boundaryName)
637
+ names.push(boundaryName)
638
+
639
+ const boundaryErrors: FingerprintError[] = []
640
+
641
+ // Try to analyze the boundary function to extract input and output types
642
+ if (ts.isArrowFunction(prop.initializer) || ts.isFunctionExpression(prop.initializer)) {
643
+ try {
644
+ // Analyze input parameters with error collection
645
+ const inputAnalysis = analyzeBoundaryInputTypesWithErrors(prop.initializer, sourceFile, boundaryName)
646
+ boundaryErrors.push(...inputAnalysis.errors)
647
+
648
+ // Analyze return type with error collection
649
+ const returnAnalysis = analyzeBoundaryReturnTypeWithErrors(prop.initializer, sourceFile, boundaryName)
650
+ boundaryErrors.push(...returnAnalysis.errors)
651
+
652
+ types.set(boundaryName, returnAnalysis.returnType)
653
+
654
+ // Validate boundary structure and collect structural errors
655
+ const structuralErrors = validateBoundaryStructure(prop.initializer, sourceFile, boundaryName)
656
+ boundaryErrors.push(...structuralErrors)
657
+
658
+ // Create boundary fingerprint
659
+ const boundaryFingerprint: BoundaryFingerprint = {
660
+ name: boundaryName,
661
+ input: inputAnalysis.inputTypes,
662
+ output: returnAnalysis.returnType,
663
+ errors: boundaryErrors
664
+ }
665
+
666
+ boundaries.push(boundaryFingerprint)
667
+ } catch (error) {
668
+ boundaryErrors.push({
669
+ type: 'boundary',
670
+ message: error instanceof Error ? error.message : 'Boundary analysis failed',
671
+ location: { file: sourceFile.fileName },
672
+ details: { boundaryName, errorType: 'critical_analysis_failure' }
673
+ })
674
+
675
+ // Create boundary fingerprint with error
676
+ const boundaryFingerprint: BoundaryFingerprint = {
677
+ name: boundaryName,
678
+ input: [],
679
+ output: { type: 'unknown' },
680
+ errors: boundaryErrors
681
+ }
682
+
683
+ boundaries.push(boundaryFingerprint)
684
+ }
685
+ } else {
686
+ // Not a function - this is an error
687
+ boundaryErrors.push({
688
+ type: 'boundary',
689
+ message: 'Boundary is not a function',
690
+ location: { file: sourceFile.fileName },
691
+ details: { boundaryName, nodeType: ts.SyntaxKind[prop.initializer.kind] }
692
+ })
693
+
694
+ const boundaryFingerprint: BoundaryFingerprint = {
695
+ name: boundaryName,
696
+ input: [],
697
+ output: { type: 'unknown' },
698
+ errors: boundaryErrors
699
+ }
700
+
701
+ boundaries.push(boundaryFingerprint)
702
+ }
703
+ }
704
+ })
705
+ }
706
+
707
+ return { names, types, boundaries }
708
+ }
709
+
710
+ // Analyze boundary function input parameter types with error collection
711
+ function analyzeBoundaryInputTypesWithErrors(func: ts.ArrowFunction | ts.FunctionExpression, sourceFile: ts.SourceFile, boundaryName: string): { inputTypes: SchemaProperty[], errors: FingerprintError[] } {
712
+ const inputTypes: SchemaProperty[] = []
713
+ const errors: FingerprintError[] = []
714
+
715
+ if (func.parameters) {
716
+ func.parameters.forEach((param, index) => {
717
+ if (ts.isIdentifier(param.name)) {
718
+ const paramName = param.name.text
719
+
720
+ if (param.type) {
721
+ try {
722
+ const typeText = param.type.getText(sourceFile)
723
+ const schemaProperty = parseTypeToSchemaProperty(typeText)
724
+ inputTypes.push({ ...schemaProperty, name: paramName })
725
+ } catch (error) {
726
+ errors.push({
727
+ type: 'boundary',
728
+ message: `Failed to parse parameter type for '${paramName}': ${error instanceof Error ? error.message : 'Unknown error'}`,
729
+ location: { file: sourceFile.fileName },
730
+ details: { boundaryName, parameterName: paramName, parameterIndex: index }
731
+ })
732
+ inputTypes.push({ name: paramName, type: 'unknown' })
733
+ }
734
+ } else {
735
+ // No type annotation - just use unknown
736
+ inputTypes.push({ name: paramName, type: 'unknown' })
737
+ }
738
+ } else {
739
+ // Complex parameter patterns (destructuring, etc.) - just use unknown
740
+ inputTypes.push({ name: `param${index}`, type: 'unknown' })
398
741
  }
399
742
  })
743
+
744
+ // Only check for obvious parameter issues
745
+ if (func.parameters.length === 0) {
746
+ errors.push({
747
+ type: 'boundary',
748
+ message: 'Boundary function has no parameters',
749
+ location: { file: sourceFile.fileName },
750
+ details: { boundaryName, issue: 'no_parameters' }
751
+ })
752
+ }
400
753
  }
401
754
 
402
- return boundaries
755
+ return { inputTypes, errors }
756
+ }
757
+
758
+
759
+ // Helper function to convert TypeScript type text to SchemaProperty
760
+ function parseTypeToSchemaProperty(typeText: string): SchemaProperty {
761
+ // Remove whitespace
762
+ const cleanType = typeText.trim()
763
+
764
+ if (cleanType === 'string') {
765
+ return { type: 'string' }
766
+ } else if (cleanType === 'number') {
767
+ return { type: 'number' }
768
+ } else if (cleanType === 'boolean') {
769
+ return { type: 'boolean' }
770
+ } else if (cleanType.includes('[]') || cleanType.includes('Array<')) {
771
+ return { type: 'array' }
772
+ } else if (cleanType.includes('{') && cleanType.includes('}')) {
773
+ // Parse object type structure
774
+ const objectMatch = cleanType.match(/^\s*\{\s*(.+)\s*\}\s*$/)
775
+ if (objectMatch) {
776
+ const properties: Record<string, SchemaProperty> = {}
777
+ const propsString = objectMatch[1]
778
+
779
+ // Split by commas and semicolons
780
+ const propPairs = propsString.split(/[,;]/).map(s => s.trim())
781
+
782
+ for (const propPair of propPairs) {
783
+ const colonIndex = propPair.indexOf(':')
784
+ if (colonIndex > 0) {
785
+ const propName = propPair.substring(0, colonIndex).trim()
786
+ const propType = propPair.substring(colonIndex + 1).trim()
787
+
788
+ properties[propName] = parseTypeToSchemaProperty(propType)
789
+ }
790
+ }
791
+
792
+ return {
793
+ type: 'object',
794
+ properties
795
+ }
796
+ }
797
+ return { type: 'object' }
798
+ } else {
799
+ return { type: cleanType }
800
+ }
801
+ }
802
+
803
+ // Helper function to infer type from variable names using TypeScript compiler analysis
804
+ function inferTypeFromIdentifier(identifierText: string, boundaryTypes: Map<string, OutputType>): string {
805
+ // Check boundary types first
806
+ if (boundaryTypes.has(identifierText)) {
807
+ const boundaryType = boundaryTypes.get(identifierText)
808
+ if (typeof boundaryType === 'object' && boundaryType.type) {
809
+ return boundaryType.type
810
+ }
811
+ return (typeof boundaryType === 'object' && boundaryType?.type) ? boundaryType.type : 'unknown'
812
+ }
813
+
814
+ // Use TypeScript's built-in type inference instead of hardcoded patterns
815
+ // For now, return unknown and let the compiler handle it
816
+ return 'unknown'
817
+ }
818
+
819
+ // Analyze boundary function return type with error collection
820
+ function analyzeBoundaryReturnTypeWithErrors(func: ts.ArrowFunction | ts.FunctionExpression, sourceFile: ts.SourceFile, boundaryName: string): { returnType: OutputType, errors: FingerprintError[] } {
821
+ const errors: FingerprintError[] = []
822
+
823
+ // Check if function has explicit return type annotation
824
+ if (func.type) {
825
+ try {
826
+ const typeText = func.type.getText(sourceFile)
827
+
828
+ // Handle Promise<T> types - extract T
829
+ const promiseMatch = typeText.match(/Promise<(.+)>/)
830
+ if (promiseMatch) {
831
+ const innerType = promiseMatch[1]
832
+
833
+ // Just process the type without validation
834
+
835
+ // Parse detailed type patterns
836
+ if (innerType.includes('[]') || innerType.includes('Array<')) {
837
+ // Extract array element type
838
+ let elementType = 'unknown'
839
+
840
+ if (innerType.includes('[]')) {
841
+ // Handle T[] format
842
+ const elementTypeMatch = innerType.match(/^(.+)\[\]$/)
843
+ if (elementTypeMatch) {
844
+ const rawElementType = elementTypeMatch[1].trim()
845
+
846
+ // Parse object element types
847
+ if (rawElementType.includes('{') && rawElementType.includes('}')) {
848
+ const objectType = parseObjectTypeFromString(rawElementType)
849
+ if (objectType && objectType.properties) {
850
+ return {
851
+ returnType: {
852
+ type: 'array',
853
+ elementType: {
854
+ type: 'object',
855
+ properties: objectType.properties
856
+ }
857
+ },
858
+ errors
859
+ }
860
+ }
861
+ }
862
+
863
+ elementType = rawElementType
864
+ }
865
+ } else if (innerType.includes('Array<')) {
866
+ // Handle Array<T> format
867
+ const elementTypeMatch = innerType.match(/Array<(.+)>/)
868
+ if (elementTypeMatch) {
869
+ elementType = elementTypeMatch[1].trim()
870
+ }
871
+ }
872
+
873
+ return {
874
+ returnType: {
875
+ type: 'array',
876
+ elementType: elementType === 'unknown' ? undefined : { type: elementType }
877
+ },
878
+ errors
879
+ }
880
+ } else if (innerType === 'string') {
881
+ return { returnType: { type: 'string' }, errors }
882
+ } else if (innerType === 'number') {
883
+ return { returnType: { type: 'number' }, errors }
884
+ } else if (innerType === 'boolean') {
885
+ return { returnType: { type: 'boolean' }, errors }
886
+ } else if (innerType.includes('{') && innerType.includes('}')) {
887
+ // Try to parse object type structure from string
888
+ try {
889
+ const parsedType = parseObjectTypeFromString(innerType)
890
+ return { returnType: parsedType, errors }
891
+ } catch (parseError) {
892
+ errors.push({
893
+ type: 'boundary',
894
+ message: `Failed to parse return type object structure: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`,
895
+ location: { file: sourceFile.fileName },
896
+ details: { boundaryName, returnType: innerType, issue: 'object_parse_failure' }
897
+ })
898
+ return { returnType: { type: 'object' }, errors }
899
+ }
900
+ }
901
+ } else {
902
+ // Not a Promise type - just use the type as-is
903
+ }
904
+
905
+ return { returnType: { type: cleanTypeString(typeText) }, errors }
906
+ } catch (error) {
907
+ errors.push({
908
+ type: 'boundary',
909
+ message: `Failed to analyze return type: ${error instanceof Error ? error.message : 'Unknown error'}`,
910
+ location: { file: sourceFile.fileName },
911
+ details: { boundaryName, issue: 'return_type_analysis_failure' }
912
+ })
913
+ return { returnType: { type: 'unknown' }, errors }
914
+ }
915
+ } else {
916
+ // No return type annotation - will try to infer
917
+ }
918
+
919
+ // If no explicit type, try to infer from return statements
920
+ if (func.body) {
921
+ try {
922
+ const returnType = inferDetailedReturnType(func, sourceFile, new Map())
923
+ return { returnType, errors }
924
+ } catch (error) {
925
+ errors.push({
926
+ type: 'boundary',
927
+ message: `Failed to infer return type from function body: ${error instanceof Error ? error.message : 'Unknown error'}`,
928
+ location: { file: sourceFile.fileName },
929
+ details: { boundaryName, issue: 'return_type_inference_failure' }
930
+ })
931
+ }
932
+ }
933
+
934
+ return { returnType: { type: 'unknown' }, errors }
935
+ }
936
+
937
+
938
+ // Helper function to check for throw statements in boundary functions
939
+ function checkForBoundaryThrowStatements(node: ts.Node, sourceFile: ts.SourceFile, errors: FingerprintError[]): void {
940
+ if (ts.isThrowStatement(node)) {
941
+ let errorMessage = 'Boundary function contains throw statement'
942
+
943
+ // Try to extract error message if it's a simple throw new Error('message')
944
+ if (node.expression && ts.isNewExpression(node.expression)) {
945
+ if (ts.isIdentifier(node.expression.expression) &&
946
+ node.expression.expression.text === 'Error' &&
947
+ node.expression.arguments &&
948
+ node.expression.arguments.length > 0) {
949
+ const errorArg = node.expression.arguments[0]
950
+ if (ts.isStringLiteral(errorArg)) {
951
+ errorMessage = `Boundary function throws: ${errorArg.text}`
952
+ }
953
+ }
954
+ }
955
+
956
+ const position = sourceFile.getLineAndCharacterOfPosition(node.getStart())
957
+ errors.push({
958
+ type: 'boundary',
959
+ message: errorMessage,
960
+ location: {
961
+ file: sourceFile.fileName,
962
+ line: position.line + 1,
963
+ column: position.character + 1
964
+ }
965
+ })
966
+ }
967
+ ts.forEachChild(node, (childNode) => checkForBoundaryThrowStatements(childNode, sourceFile, errors))
968
+ }
969
+
970
+ // Validate boundary structure and collect structural errors (simplified to focus on runtime errors)
971
+ function validateBoundaryStructure(func: ts.ArrowFunction | ts.FunctionExpression, sourceFile: ts.SourceFile, _boundaryName: string): FingerprintError[] {
972
+ const errors: FingerprintError[] = []
973
+
974
+ // Check function body for potential runtime errors
975
+ if (func.body) {
976
+ if (ts.isBlock(func.body)) {
977
+ checkForBoundaryThrowStatements(func.body, sourceFile, errors)
978
+ }
979
+ }
980
+
981
+ return errors
982
+ }
983
+
984
+ // Helper function to check for throw statements in main task functions
985
+ function checkForMainTaskThrowStatements(node: ts.Node, sourceFile: ts.SourceFile, errors: FingerprintError[]): void {
986
+ if (ts.isThrowStatement(node)) {
987
+ let errorMessage = 'Main task function contains throw statement'
988
+
989
+ // Try to extract error message if it's a simple throw new Error('message')
990
+ if (node.expression && ts.isNewExpression(node.expression)) {
991
+ if (ts.isIdentifier(node.expression.expression) &&
992
+ node.expression.expression.text === 'Error' &&
993
+ node.expression.arguments &&
994
+ node.expression.arguments.length > 0) {
995
+ const errorArg = node.expression.arguments[0]
996
+ if (ts.isStringLiteral(errorArg)) {
997
+ errorMessage = `Main task function throws: ${errorArg.text}`
998
+ } else if (ts.isTemplateExpression(errorArg)) {
999
+ // Handle template literals like `User with ID ${userId} not found`
1000
+ const templateText = errorArg.getText(sourceFile)
1001
+ errorMessage = `Main task function throws: ${templateText.replace(/`/g, '')}`
1002
+ }
1003
+ }
1004
+ }
1005
+
1006
+ const position = sourceFile.getLineAndCharacterOfPosition(node.getStart())
1007
+ errors.push({
1008
+ type: 'analysis',
1009
+ message: errorMessage,
1010
+ location: {
1011
+ file: sourceFile.fileName,
1012
+ line: position.line + 1,
1013
+ column: position.character + 1
1014
+ }
1015
+ })
1016
+ }
1017
+ ts.forEachChild(node, (childNode) => checkForMainTaskThrowStatements(childNode, sourceFile, errors))
1018
+ }
1019
+
1020
+ // Analyze main task function for throw statements and runtime errors
1021
+ function analyzeMainTaskFunctionErrors(func: ts.ArrowFunction | ts.FunctionExpression, sourceFile: ts.SourceFile, _taskName: string): FingerprintError[] {
1022
+ const errors: FingerprintError[] = []
1023
+
1024
+ // Check function body for potential runtime errors
1025
+ if (func.body) {
1026
+ if (ts.isBlock(func.body)) {
1027
+ checkForMainTaskThrowStatements(func.body, sourceFile, errors)
1028
+ }
1029
+ }
1030
+
1031
+ return errors
1032
+ }
1033
+
1034
+ // Helper function to parse object type structure from type string
1035
+ function parseObjectTypeFromString(typeString: string): OutputType {
1036
+ // Simple parsing for common object patterns like "{ result: string }"
1037
+ const objectMatch = typeString.match(/^\s*\{\s*(.+)\s*\}\s*$/)
1038
+ if (objectMatch) {
1039
+ const properties: Record<string, SchemaProperty> = {}
1040
+ const propsString = objectMatch[1]
1041
+
1042
+ // Split by commas (simple approach - doesn't handle nested objects)
1043
+ const propPairs = propsString.split(',').map(s => s.trim())
1044
+
1045
+ for (const propPair of propPairs) {
1046
+ const colonIndex = propPair.indexOf(':')
1047
+ if (colonIndex > 0) {
1048
+ const propName = propPair.substring(0, colonIndex).trim()
1049
+ const propType = propPair.substring(colonIndex + 1).trim()
1050
+
1051
+ if (propType === 'string') {
1052
+ properties[propName] = { type: 'string' }
1053
+ } else if (propType === 'number') {
1054
+ properties[propName] = { type: 'number' }
1055
+ } else if (propType === 'boolean') {
1056
+ properties[propName] = { type: 'boolean' }
1057
+ } else if (propType.includes('[]')) {
1058
+ properties[propName] = { type: 'array' }
1059
+ } else {
1060
+ properties[propName] = { type: propType }
1061
+ }
1062
+ }
1063
+ }
1064
+
1065
+ return {
1066
+ type: 'object',
1067
+ properties
1068
+ }
1069
+ }
1070
+
1071
+ return { type: 'object' }
403
1072
  }
404
1073
 
405
1074
  // Export the core analysis function for reuse
406
1075
  export function analyzeTaskFile(sourceCode: string, filePath: string, _expectedTaskName?: string): TaskFingerprintOutput | null {
407
- const taskFingerprint = extractTaskFingerprints(sourceCode, filePath)[0]
408
- if (!taskFingerprint) {
409
- return null
1076
+ const errors: FingerprintError[] = []
1077
+ const analysisMetadata = {
1078
+ timestamp: new Date().toISOString(),
1079
+ filePath,
1080
+ success: true,
1081
+ analysisVersion: '1.0.0'
410
1082
  }
411
1083
 
412
- // Return simplified output without name, location, hash
413
- return {
414
- description: taskFingerprint.description,
415
- inputSchema: taskFingerprint.inputSchema,
416
- outputType: taskFingerprint.outputType,
417
- boundaries: taskFingerprint.boundaries
1084
+ try {
1085
+ const taskFingerprints = extractTaskFingerprintsWithErrors(sourceCode, filePath, errors)
1086
+ const taskFingerprint = taskFingerprints[0]
1087
+
1088
+ if (!taskFingerprint) {
1089
+ errors.push({
1090
+ type: 'analysis',
1091
+ message: 'No task fingerprint found in file',
1092
+ location: { file: filePath },
1093
+ details: { reason: 'No createTask calls detected' }
1094
+ })
1095
+ analysisMetadata.success = false
1096
+
1097
+ return {
1098
+ description: undefined,
1099
+ inputSchema: { type: 'object', properties: {} },
1100
+ outputType: { type: 'unknown' },
1101
+ boundaries: [],
1102
+ errors,
1103
+ analysisMetadata
1104
+ }
1105
+ }
1106
+
1107
+ // Return simplified output without name, location, hash
1108
+ return {
1109
+ description: taskFingerprint.description,
1110
+ inputSchema: taskFingerprint.inputSchema,
1111
+ outputType: taskFingerprint.outputType,
1112
+ boundaries: taskFingerprint.boundaries,
1113
+ errors,
1114
+ analysisMetadata
1115
+ }
1116
+ } catch (error) {
1117
+ errors.push({
1118
+ type: 'parsing',
1119
+ message: error instanceof Error ? error.message : 'Unknown parsing error',
1120
+ location: { file: filePath },
1121
+ details: { error: error instanceof Error ? error.stack : String(error) }
1122
+ })
1123
+ analysisMetadata.success = false
1124
+
1125
+ return {
1126
+ description: undefined,
1127
+ inputSchema: { type: 'object', properties: {} },
1128
+ outputType: { type: 'unknown' },
1129
+ boundaries: [],
1130
+ errors,
1131
+ analysisMetadata
1132
+ }
418
1133
  }
419
1134
  }