@codeyam/codeyam-cli 0.1.0-staging.323686 → 0.1.0-staging.483fdc2

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 (171) hide show
  1. package/analyzer-template/.build-info.json +7 -7
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +2 -2
  4. package/analyzer-template/packages/ai/index.ts +6 -1
  5. package/analyzer-template/packages/ai/src/lib/analyzeScope.ts +39 -17
  6. package/analyzer-template/packages/ai/src/lib/astScopes/astScopeAnalyzer.ts +67 -9
  7. package/analyzer-template/packages/ai/src/lib/astScopes/processExpression.ts +308 -50
  8. package/analyzer-template/packages/ai/src/lib/astScopes/types.ts +15 -6
  9. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +664 -242
  10. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.ts +16 -3
  11. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.ts +6 -4
  12. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +20 -1
  13. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.ts +35 -13
  14. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.ts +160 -0
  15. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.ts +40 -30
  16. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.ts +289 -83
  17. package/analyzer-template/packages/ai/src/lib/generateEntityScenarioData.ts +269 -1
  18. package/analyzer-template/packages/ai/src/lib/generateEntityScenarios.ts +9 -5
  19. package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +11 -3
  20. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionalEffects.ts +1 -1
  21. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionals.ts +297 -7
  22. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromJsxUsages.ts +1 -1
  23. package/analyzer-template/packages/ai/src/lib/mergeStatements.ts +90 -96
  24. package/analyzer-template/packages/ai/src/lib/promptGenerators/gatherAttributesMap.ts +10 -7
  25. package/analyzer-template/packages/ai/src/lib/resolvePathToControllable.ts +25 -13
  26. package/analyzer-template/packages/ai/src/lib/worker/SerializableDataStructure.ts +4 -3
  27. package/analyzer-template/packages/analyze/src/lib/FileAnalyzer.ts +65 -59
  28. package/analyzer-template/packages/analyze/src/lib/ProjectAnalyzer.ts +113 -26
  29. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.ts +19 -0
  30. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.ts +19 -0
  31. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllExports.ts +11 -0
  32. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.ts +8 -0
  33. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.ts +49 -1
  34. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.ts +2 -1
  35. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +20 -6
  36. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +14 -4
  37. package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +4 -2
  38. package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +0 -3
  39. package/analyzer-template/packages/analyze/src/lib/files/analyzeRemixRoute.ts +4 -5
  40. package/analyzer-template/packages/analyze/src/lib/files/getImportedExports.ts +14 -12
  41. package/analyzer-template/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.ts +57 -13
  42. package/analyzer-template/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.ts +29 -0
  43. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +35 -4
  44. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.ts +117 -9
  45. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +199 -17
  46. package/analyzer-template/packages/analyze/src/lib/files/scenarios/propagateArrayItemSchemas.ts +474 -0
  47. package/analyzer-template/packages/analyze/src/lib/files/setImportedExports.ts +2 -1
  48. package/analyzer-template/packages/analyze/src/lib/utils/getFileByPath.ts +19 -0
  49. package/analyzer-template/packages/aws/package.json +1 -1
  50. package/analyzer-template/packages/github/dist/types/src/types/ScenariosDataStructure.d.ts +5 -5
  51. package/analyzer-template/packages/github/dist/types/src/types/ScenariosDataStructure.d.ts.map +1 -1
  52. package/analyzer-template/packages/github/dist/types/src/types/ScopeAnalysis.d.ts +6 -1
  53. package/analyzer-template/packages/github/dist/types/src/types/ScopeAnalysis.d.ts.map +1 -1
  54. package/analyzer-template/packages/github/package.json +1 -1
  55. package/analyzer-template/packages/types/src/types/ScenariosDataStructure.ts +6 -5
  56. package/analyzer-template/packages/types/src/types/ScopeAnalysis.ts +6 -1
  57. package/analyzer-template/packages/utils/dist/types/src/types/ScenariosDataStructure.d.ts +5 -5
  58. package/analyzer-template/packages/utils/dist/types/src/types/ScenariosDataStructure.d.ts.map +1 -1
  59. package/analyzer-template/packages/utils/dist/types/src/types/ScopeAnalysis.d.ts +6 -1
  60. package/analyzer-template/packages/utils/dist/types/src/types/ScopeAnalysis.d.ts.map +1 -1
  61. package/analyzer-template/project/constructMockCode.ts +54 -9
  62. package/analyzer-template/project/writeMockDataTsx.ts +73 -2
  63. package/background/src/lib/virtualized/project/constructMockCode.js +45 -3
  64. package/background/src/lib/virtualized/project/constructMockCode.js.map +1 -1
  65. package/background/src/lib/virtualized/project/writeMockDataTsx.js +71 -2
  66. package/background/src/lib/virtualized/project/writeMockDataTsx.js.map +1 -1
  67. package/codeyam-cli/scripts/apply-setup.js +146 -0
  68. package/codeyam-cli/scripts/apply-setup.js.map +1 -1
  69. package/codeyam-cli/src/commands/debug.js +7 -5
  70. package/codeyam-cli/src/commands/debug.js.map +1 -1
  71. package/codeyam-cli/src/utils/install-skills.js +22 -0
  72. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  73. package/codeyam-cli/src/utils/reviewedRules.js +92 -0
  74. package/codeyam-cli/src/utils/reviewedRules.js.map +1 -0
  75. package/codeyam-cli/src/webserver/build/client/assets/globals-CX9f-5xM.css +1 -0
  76. package/codeyam-cli/src/webserver/build/client/assets/{manifest-7522edd4.js → manifest-bba56ec1.js} +1 -1
  77. package/codeyam-cli/src/webserver/build/client/assets/memory-DuTFSyJ2.js +92 -0
  78. package/codeyam-cli/src/webserver/build/client/assets/{root-eVAaavTS.js → root-DTfSQARG.js} +6 -6
  79. package/codeyam-cli/src/webserver/build/server/assets/{index-DVzYx8PN.js → index-TD1f-DHV.js} +1 -1
  80. package/codeyam-cli/src/webserver/build/server/assets/server-build-BQ-1XyEa.js +258 -0
  81. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  82. package/codeyam-cli/src/webserver/build-info.json +5 -5
  83. package/codeyam-cli/templates/codeyam:memory.md +174 -233
  84. package/codeyam-cli/templates/codeyam:new-rule.md +41 -2
  85. package/codeyam-cli/templates/rule-reflection-hook.py +161 -0
  86. package/codeyam-cli/templates/rules-instructions.md +126 -0
  87. package/package.json +1 -1
  88. package/packages/ai/index.js +2 -1
  89. package/packages/ai/index.js.map +1 -1
  90. package/packages/ai/src/lib/analyzeScope.js +29 -12
  91. package/packages/ai/src/lib/analyzeScope.js.map +1 -1
  92. package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js +54 -8
  93. package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js.map +1 -1
  94. package/packages/ai/src/lib/astScopes/processExpression.js +239 -43
  95. package/packages/ai/src/lib/astScopes/processExpression.js.map +1 -1
  96. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +503 -165
  97. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  98. package/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.js +13 -3
  99. package/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.js.map +1 -1
  100. package/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.js +6 -4
  101. package/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.js.map +1 -1
  102. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +22 -1
  103. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  104. package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js +34 -9
  105. package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js.map +1 -1
  106. package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js +159 -0
  107. package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js.map +1 -0
  108. package/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.js +37 -20
  109. package/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.js.map +1 -1
  110. package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js +237 -73
  111. package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js.map +1 -1
  112. package/packages/ai/src/lib/generateEntityScenarioData.js +195 -1
  113. package/packages/ai/src/lib/generateEntityScenarioData.js.map +1 -1
  114. package/packages/ai/src/lib/generateEntityScenarios.js +7 -1
  115. package/packages/ai/src/lib/generateEntityScenarios.js.map +1 -1
  116. package/packages/ai/src/lib/generateExecutionFlows.js +10 -2
  117. package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
  118. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js +209 -3
  119. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js.map +1 -1
  120. package/packages/ai/src/lib/mergeStatements.js +70 -51
  121. package/packages/ai/src/lib/mergeStatements.js.map +1 -1
  122. package/packages/ai/src/lib/promptGenerators/gatherAttributesMap.js +10 -4
  123. package/packages/ai/src/lib/promptGenerators/gatherAttributesMap.js.map +1 -1
  124. package/packages/ai/src/lib/resolvePathToControllable.js +24 -14
  125. package/packages/ai/src/lib/resolvePathToControllable.js.map +1 -1
  126. package/packages/ai/src/lib/worker/SerializableDataStructure.js.map +1 -1
  127. package/packages/analyze/src/lib/FileAnalyzer.js +60 -36
  128. package/packages/analyze/src/lib/FileAnalyzer.js.map +1 -1
  129. package/packages/analyze/src/lib/ProjectAnalyzer.js +96 -26
  130. package/packages/analyze/src/lib/ProjectAnalyzer.js.map +1 -1
  131. package/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.js +14 -0
  132. package/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.js.map +1 -1
  133. package/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.js +14 -0
  134. package/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.js.map +1 -1
  135. package/packages/analyze/src/lib/asts/sourceFiles/getAllExports.js +6 -0
  136. package/packages/analyze/src/lib/asts/sourceFiles/getAllExports.js.map +1 -1
  137. package/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.js +6 -0
  138. package/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.js.map +1 -1
  139. package/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.js +39 -1
  140. package/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.js.map +1 -1
  141. package/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.js +2 -1
  142. package/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.js.map +1 -1
  143. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +13 -5
  144. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  145. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +14 -4
  146. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  147. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +2 -1
  148. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
  149. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +0 -3
  150. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
  151. package/packages/analyze/src/lib/files/analyzeRemixRoute.js +3 -2
  152. package/packages/analyze/src/lib/files/analyzeRemixRoute.js.map +1 -1
  153. package/packages/analyze/src/lib/files/getImportedExports.js +11 -7
  154. package/packages/analyze/src/lib/files/getImportedExports.js.map +1 -1
  155. package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js +52 -10
  156. package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js.map +1 -1
  157. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js +25 -8
  158. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js.map +1 -1
  159. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +34 -4
  160. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  161. package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js +56 -8
  162. package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js.map +1 -1
  163. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +168 -9
  164. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  165. package/packages/analyze/src/lib/files/setImportedExports.js +2 -1
  166. package/packages/analyze/src/lib/files/setImportedExports.js.map +1 -1
  167. package/packages/analyze/src/lib/utils/getFileByPath.js +12 -0
  168. package/packages/analyze/src/lib/utils/getFileByPath.js.map +1 -0
  169. package/codeyam-cli/src/webserver/build/client/assets/globals-D3yhhV8x.css +0 -1
  170. package/codeyam-cli/src/webserver/build/client/assets/memory-yxFcrxBX.js +0 -92
  171. package/codeyam-cli/src/webserver/build/server/assets/server-build-4Cr0uToj.js +0 -257
@@ -205,16 +205,88 @@ export default function fillInSchemaGapsAndUnknowns(
205
205
  }
206
206
  }
207
207
 
208
+ /**
209
+ * Pre-built indexes that can be reused across multiple calls to fillInDirectSchemaGapsAndUnknowns.
210
+ * Build once with buildSchemaIndexes(), then pass to all calls processing the same schema.
211
+ */
212
+ export interface SchemaIndexes {
213
+ functionKeysMapping: Record<string, string[]>;
214
+ prefixMaxDepth: Map<string, number>;
215
+ }
216
+
217
+ /**
218
+ * Build indexes for a schema that can be reused across multiple calls.
219
+ * This avoids rebuilding indexes (~1-2 seconds for 18k keys) on every call.
220
+ *
221
+ * @param schema - The schema to build indexes from (typically the full dependency schema)
222
+ * @returns Indexes that can be passed to fillInDirectSchemaGapsAndUnknowns
223
+ */
224
+ export function buildSchemaIndexes(
225
+ schema: Record<string, string>,
226
+ ): SchemaIndexes {
227
+ // Build functionKeysMapping + prefixMaxDepth in a single pass
228
+ // Avoid repeated split/join work by caching parts and building prefixes incrementally.
229
+ const functionKeysMapping: Record<string, string[]> = {};
230
+ const prefixMaxDepth = new Map<string, number>();
231
+ const partsCache = new Map<string, string[]>();
232
+
233
+ for (const key in schema) {
234
+ const parts = getCachedParts(partsCache, key);
235
+ const lastPart = parts[parts.length - 1];
236
+ const prefixes = buildPrefixParts(parts);
237
+
238
+ if (
239
+ key.endsWith(')') ||
240
+ schema[key] === 'function' ||
241
+ lastPart === 'length'
242
+ ) {
243
+ const allButLastPart = parts.length > 1 ? prefixes[parts.length - 2] : '';
244
+ functionKeysMapping[allButLastPart] ||= [];
245
+ functionKeysMapping[allButLastPart].push(lastPart);
246
+ }
247
+
248
+ for (let i = 0; i < prefixes.length; i++) {
249
+ const prefix = prefixes[i];
250
+ const existingMax = prefixMaxDepth.get(prefix) ?? 0;
251
+ if (parts.length > existingMax) {
252
+ prefixMaxDepth.set(prefix, parts.length);
253
+ }
254
+ }
255
+ }
256
+
257
+ return { functionKeysMapping, prefixMaxDepth };
258
+ }
259
+
208
260
  export function fillInDirectSchemaGapsAndUnknowns({
209
261
  scopeName,
210
262
  schema,
211
263
  mergedSchema,
212
264
  attempts = 0,
265
+ // Pre-built indexes from buildSchemaIndexes() - use for cross-call caching
266
+ prebuiltIndexes,
267
+ // Internal: pre-computed indexes to avoid rebuilding on recursion
268
+ _functionKeysMapping,
269
+ _prefixMaxDepth,
270
+ _partsCache,
271
+ _prefixPartsCache,
272
+ _knownTypeCache,
273
+ _guessTypeCache,
274
+ _nameHintCache,
213
275
  }: {
214
276
  scopeName?: string;
215
277
  schema: Record<string, string>;
216
278
  mergedSchema?: Record<string, string>;
217
279
  attempts?: number;
280
+ /** Pre-built indexes from buildSchemaIndexes() - pass to reuse across multiple calls */
281
+ prebuiltIndexes?: SchemaIndexes;
282
+ // Internal: pre-computed indexes to avoid rebuilding on recursion (used by recursive calls)
283
+ _functionKeysMapping?: Record<string, string[]>;
284
+ _prefixMaxDepth?: Map<string, number>;
285
+ _partsCache?: Map<string, string[]>;
286
+ _prefixPartsCache?: Map<string, string[]>;
287
+ _knownTypeCache?: Map<string, string | undefined>;
288
+ _guessTypeCache?: Map<string, string | undefined>;
289
+ _nameHintCache?: Map<string, string | undefined>;
218
290
  }) {
219
291
  try {
220
292
  const existingSchema = (path: any) => {
@@ -225,105 +297,204 @@ export function fillInDirectSchemaGapsAndUnknowns({
225
297
  }
226
298
  };
227
299
 
228
- const functionKeysMapping = [...Object.keys(schema)]
229
- .filter((key) => {
230
- if (key.endsWith(')') || schema[key] === 'function') return true;
231
- const parts = splitOutsideParenthesesAndArrays(key);
232
- if (parts[parts.length - 1] === 'length') return true;
233
- })
234
- .reduce(
235
- (acc, key) => {
236
- const parts = splitOutsideParenthesesAndArrays(key);
237
- const allButLastPart = joinParenthesesAndArrays(parts.slice(0, -1));
300
+ const partsCache = _partsCache ?? new Map<string, string[]>();
301
+ const prefixPartsCache = _prefixPartsCache ?? new Map<string, string[]>();
302
+ const knownTypeCache =
303
+ _knownTypeCache ?? new Map<string, string | undefined>();
304
+ const guessTypeCache =
305
+ _guessTypeCache ?? new Map<string, string | undefined>();
306
+ const nameHintCache =
307
+ _nameHintCache ?? new Map<string, string | undefined>();
308
+
309
+ const getParts = (path: string) => getCachedParts(partsCache, path);
310
+ const getPrefixParts = (path: string) => {
311
+ const cached = prefixPartsCache.get(path);
312
+ if (cached) return cached;
313
+ const prefixes = buildPrefixParts(getParts(path));
314
+ prefixPartsCache.set(path, prefixes);
315
+ return prefixes;
316
+ };
317
+
318
+ // Index resolution priority:
319
+ // 1. _functionKeysMapping/_prefixMaxDepth (from recursive calls within this invocation)
320
+ // 2. prebuiltIndexes (from cross-call caching - built once, reused across multiple calls)
321
+ // 3. Build new indexes (fallback - expensive for large schemas)
322
+ const functionKeysMapping =
323
+ _functionKeysMapping ??
324
+ prebuiltIndexes?.functionKeysMapping ??
325
+ (() => {
326
+ const mapping: Record<string, string[]> = {};
327
+ for (const key in schema) {
328
+ const parts = getParts(key);
238
329
  const lastPart = parts[parts.length - 1];
239
- acc[allButLastPart] ||= [];
240
- acc[allButLastPart].push(lastPart);
241
- return acc;
242
- },
243
- {} as Record<string, string[]>,
244
- );
330
+ if (
331
+ key.endsWith(')') ||
332
+ schema[key] === 'function' ||
333
+ lastPart === 'length'
334
+ ) {
335
+ const prefixes = getPrefixParts(key);
336
+ const allButLastPart =
337
+ parts.length > 1 ? prefixes[parts.length - 2] : '';
338
+ mapping[allButLastPart] ||= [];
339
+ mapping[allButLastPart].push(lastPart);
340
+ }
341
+ }
342
+ return mapping;
343
+ })();
344
+
345
+ // Build prefix index for O(1) lookups in checkIfObjectOrFunction
346
+ // Maps each prefix to the max depth of keys that have this prefix
347
+ // This replaces O(n) scans with O(1) lookups, fixing the O(n²) bottleneck
348
+ let prefixMaxDepth =
349
+ _prefixMaxDepth ?? prebuiltIndexes?.prefixMaxDepth ?? null;
350
+ if (!prefixMaxDepth) {
351
+ prefixMaxDepth = new Map<string, number>();
352
+ const targetSchema = mergedSchema ?? schema;
353
+ for (const key in targetSchema) {
354
+ const keyParts = getParts(key);
355
+ const prefixes = buildPrefixParts(keyParts);
356
+ // Record each prefix of this key with its max depth
357
+ for (let i = 0; i < prefixes.length; i++) {
358
+ const prefix = prefixes[i];
359
+ const existingMax = prefixMaxDepth.get(prefix) ?? 0;
360
+ if (keyParts.length > existingMax) {
361
+ prefixMaxDepth.set(prefix, keyParts.length);
362
+ }
363
+ }
364
+ }
365
+ }
245
366
 
246
- let changeMade = false;
247
- const guessTypeForPath = (path: string, defaultType = 'string') => {
248
- let knownType = checkIfKnownType(path, functionKeysMapping);
249
- if (!knownType) {
250
- // Use mergedSchema for type inference if available (has more complete nested paths)
251
- // Fall back to schema if mergedSchema is not provided
252
- knownType = checkIfObjectOrFunction(path, mergedSchema ?? schema);
367
+ // O(1) replacement for checkIfObjectOrFunction
368
+ const checkIfObjectOrFunctionFast = (path: string): string | undefined => {
369
+ const maxDepth = prefixMaxDepth!.get(path);
370
+
371
+ if (maxDepth === undefined) return undefined; // No key starts with this path
372
+
373
+ const pathParts = getParts(path);
374
+ // Check if path's last part ends with ')' → function
375
+ const lastPart = pathParts[pathParts.length - 1];
376
+ if (lastPart?.endsWith(')')) {
377
+ return 'function';
253
378
  }
254
379
 
255
- if (knownType && schema[path] !== knownType) {
256
- return knownType;
380
+ // Check if there are deeper keys
381
+ if (maxDepth > pathParts.length) {
382
+ return 'object';
257
383
  }
258
384
 
259
- const keyParts = splitOutsideParenthesesAndArrays(path);
260
- const lastPart = keyParts[keyParts.length - 1];
385
+ return undefined;
386
+ };
387
+
388
+ let changeMade = false;
389
+ const getKnownType = (path: string) => {
390
+ if (knownTypeCache.has(path)) return knownTypeCache.get(path);
391
+ const known = checkIfKnownType(path, functionKeysMapping);
392
+ knownTypeCache.set(path, known);
393
+ return known;
394
+ };
395
+ const guessTypeFromName = (lastPart: string, defaultType: string) => {
396
+ if (nameHintCache.has(lastPart)) {
397
+ return nameHintCache.get(lastPart) ?? defaultType;
398
+ }
261
399
 
400
+ let hint: string | undefined;
262
401
  if (isLikelyBoolean(lastPart)) {
263
- return 'boolean';
402
+ hint = 'boolean';
264
403
  } else if (isLikelyDate(lastPart)) {
265
- return 'date';
404
+ hint = 'date';
266
405
  } else if (isLikelyAction(lastPart)) {
267
- return 'function';
406
+ hint = 'function';
268
407
  } else if (pluralize.isPlural(lastPart)) {
269
- return 'number';
270
- } else {
271
- return defaultType;
408
+ // Default to number instead of array - safer for JSX rendering
409
+ // Arrays cause "Objects are not valid as a React child" errors when rendered directly
410
+ hint = 'number';
411
+ }
412
+
413
+ nameHintCache.set(lastPart, hint);
414
+ return hint ?? defaultType;
415
+ };
416
+ const guessTypeForPath = (path: string, defaultType = 'string') => {
417
+ const cacheKey = `${path}|${defaultType}`;
418
+ if (guessTypeCache.has(cacheKey)) {
419
+ return guessTypeCache.get(cacheKey);
420
+ }
421
+
422
+ let knownType = getKnownType(path);
423
+ if (!knownType) {
424
+ // Use optimized O(1) prefix lookup instead of O(n) scan
425
+ knownType = checkIfObjectOrFunctionFast(path);
426
+ }
427
+
428
+ if (knownType) {
429
+ guessTypeCache.set(cacheKey, knownType);
430
+ return knownType;
272
431
  }
432
+
433
+ const keyParts = getParts(path);
434
+ const lastPart = keyParts[keyParts.length - 1];
435
+ const guessed = guessTypeFromName(lastPart, defaultType);
436
+ guessTypeCache.set(cacheKey, guessed);
437
+ return guessed;
273
438
  };
274
439
 
275
440
  for (const key in schema) {
276
441
  if (!schema[key]) schema[key] = 'unknown';
277
442
  if (schema[key].includes(' | unknown')) {
278
- schema[key] = schema[key].replace(' | unknown', '');
279
- changeMade = true;
443
+ const cleaned = schema[key].replace(' | unknown', '');
444
+ if (schema[key] !== cleaned) {
445
+ schema[key] = cleaned;
446
+ changeMade = true;
447
+ }
280
448
  } else if (schema[key].startsWith('unknown | ')) {
281
449
  // Handle "unknown | undefined" -> "string | undefined"
282
450
  const remainingType = schema[key].substring('unknown | '.length);
283
451
  const newType = guessTypeForPath(key, 'string');
284
452
  if (newType) {
285
- schema[key] = `${newType} | ${remainingType}`;
286
- changeMade = true;
453
+ const replacement = `${newType} | ${remainingType}`;
454
+ if (schema[key] !== replacement) {
455
+ schema[key] = replacement;
456
+ changeMade = true;
457
+ }
287
458
  }
288
459
  } else if (schema[key] === 'unknown') {
289
460
  const newType = guessTypeForPath(key, 'string');
290
- if (newType) {
461
+ if (newType && schema[key] !== newType) {
291
462
  schema[key] = newType;
292
463
  changeMade = true;
293
464
  }
294
465
  }
295
466
 
296
- const keyParts = splitOutsideParenthesesAndArrays(key);
467
+ const keyParts = getParts(key);
468
+ const prefixParts = getPrefixParts(key);
297
469
  for (let i = 0; i < keyParts.length; i++) {
298
- const subPathParts = keyParts.slice(0, i + 1);
299
- const lastSubPathPart = subPathParts[subPathParts.length - 1];
300
-
301
- const previousSubPathParts = subPathParts.slice(0, i);
302
- if (previousSubPathParts.length === 0) continue;
303
-
304
- const previousSubPath = joinParenthesesAndArrays(previousSubPathParts);
305
-
306
- if (
307
- cleanOutBoundary(lastSubPathPart).match(/\[\d*\]/) &&
308
- !lastSubPathPart.match(/signature\[\d+\]/)
309
- ) {
310
- // Fix 39: Don't overwrite explicit 'object' types with 'array'
311
- // This handles spurious [] paths from components like JsonNode that handle
312
- // both arrays and objects. When metadata is explicitly typed as 'object',
313
- // paths like metadata[] (from dynamic iteration) should not change it.
314
- if (
315
- schema[previousSubPath] !== 'array' &&
316
- schema[previousSubPath] !== 'object'
317
- ) {
318
- schema[previousSubPath] = 'array';
319
- changeMade = true;
470
+ if (i === 0) continue;
471
+ const lastSubPathPart = keyParts[i];
472
+ const previousSubPath = prefixParts[i - 1];
473
+ const isSignatureIndex =
474
+ lastSubPathPart.includes('signature[') &&
475
+ SIGNATURE_INDEX_REGEX.test(lastSubPathPart);
476
+
477
+ if (lastSubPathPart.includes('[')) {
478
+ const cleaned = cleanOutBoundary(lastSubPathPart);
479
+ if (cleaned.match(/\[\d*\]/) && !isSignatureIndex) {
480
+ // Fix 39: Don't overwrite explicit 'object' types with 'array'
481
+ // This handles spurious [] paths from components like JsonNode that handle
482
+ // both arrays and objects. When metadata is explicitly typed as 'object',
483
+ // paths like metadata[] (from dynamic iteration) should not change it.
484
+ if (
485
+ schema[previousSubPath] !== 'array' &&
486
+ schema[previousSubPath] !== 'object'
487
+ ) {
488
+ schema[previousSubPath] = 'array';
489
+ changeMade = true;
490
+ }
320
491
  }
321
492
  }
322
493
 
323
494
  const isFunction =
324
495
  lastSubPathPart.endsWith(')') ||
325
496
  lastSubPathPart === 'functionCallReturnValue' ||
326
- lastSubPathPart.match(/signature\[\d+\]/);
497
+ isSignatureIndex;
327
498
 
328
499
  if (
329
500
  !isFunction &&
@@ -331,8 +502,7 @@ export function fillInDirectSchemaGapsAndUnknowns({
331
502
  schema[previousSubPath],
332
503
  )
333
504
  ) {
334
- const newValue =
335
- checkIfKnownType(previousSubPath, functionKeysMapping) ?? 'object';
505
+ const newValue = getKnownType(previousSubPath) ?? 'object';
336
506
 
337
507
  if (
338
508
  !schema[previousSubPath] ||
@@ -347,8 +517,7 @@ export function fillInDirectSchemaGapsAndUnknowns({
347
517
  if (isFunction && !existingSchema(previousSubPath)) {
348
518
  schema[previousSubPath] = previousSubPath.endsWith(')')
349
519
  ? 'function'
350
- : (checkIfKnownType(previousSubPath, functionKeysMapping) ??
351
- 'object');
520
+ : (getKnownType(previousSubPath) ?? 'object');
352
521
  changeMade = true;
353
522
  }
354
523
 
@@ -358,10 +527,12 @@ export function fillInDirectSchemaGapsAndUnknowns({
358
527
  const functionReturnValuePath = `${previousSubPath}.functionCallReturnValue`;
359
528
 
360
529
  // Extract the method name from lastSubPathPart (e.g., "then()" -> "then")
361
- const methodName = lastSubPathPart.split('(')[0];
362
- const isPromiseMethod = ['then', 'catch', 'finally'].includes(
363
- methodName,
364
- );
530
+ const parenIndex = lastSubPathPart.indexOf('(');
531
+ const methodName =
532
+ parenIndex === -1
533
+ ? lastSubPathPart
534
+ : lastSubPathPart.slice(0, parenIndex);
535
+ const isPromiseMethod = PROMISE_METHODS.has(methodName);
365
536
  const isPreviousAsync = schema[previousSubPath] === 'async-function';
366
537
 
367
538
  // Don't set to 'function' if this is a promise method on an async function
@@ -386,6 +557,15 @@ export function fillInDirectSchemaGapsAndUnknowns({
386
557
  schema,
387
558
  mergedSchema,
388
559
  attempts: ++attempts,
560
+ prebuiltIndexes,
561
+ // Pass pre-computed indexes to avoid rebuilding on each recursive call
562
+ _functionKeysMapping: functionKeysMapping,
563
+ _prefixMaxDepth: prefixMaxDepth,
564
+ _partsCache: partsCache,
565
+ _prefixPartsCache: prefixPartsCache,
566
+ _knownTypeCache: knownTypeCache,
567
+ _guessTypeCache: guessTypeCache,
568
+ _nameHintCache: nameHintCache,
389
569
  });
390
570
  }
391
571
 
@@ -395,9 +575,10 @@ export function fillInDirectSchemaGapsAndUnknowns({
395
575
  // However, if the parent could be an 'object' or other type, keep .length as it may be
396
576
  // a custom property.
397
577
  for (const key of Object.keys(schema)) {
398
- const parts = splitOutsideParenthesesAndArrays(key);
578
+ const parts = getParts(key);
399
579
  if (parts[parts.length - 1] === 'length') {
400
- const parentPath = joinParenthesesAndArrays(parts.slice(0, -1));
580
+ const prefixes = getPrefixParts(key);
581
+ const parentPath = parts.length > 1 ? prefixes[parts.length - 2] : '';
401
582
  const parentType = schema[parentPath];
402
583
  if (parentType) {
403
584
  // Split union types and filter out nullable parts
@@ -434,6 +615,35 @@ export function fillInDirectSchemaGapsAndUnknowns({
434
615
  }
435
616
  }
436
617
 
618
+ const SIGNATURE_INDEX_REGEX = /signature\[\d+\]/;
619
+ const PROMISE_METHODS = new Set(['then', 'catch', 'finally']);
620
+
621
+ function getCachedParts(cache: Map<string, string[]>, path: string): string[] {
622
+ const cached = cache.get(path);
623
+ if (cached) return cached;
624
+ const parts = splitOutsideParenthesesAndArrays(path);
625
+ cache.set(path, parts);
626
+ return parts;
627
+ }
628
+
629
+ function buildPrefixParts(parts: string[]): string[] {
630
+ if (parts.length === 0) return [];
631
+ const prefixes = new Array(parts.length);
632
+ let prefix = '';
633
+ for (let i = 0; i < parts.length; i++) {
634
+ const part = parts[i];
635
+ if (i === 0) {
636
+ prefix = part;
637
+ } else if (part.startsWith('[') || part.startsWith('(')) {
638
+ prefix += part;
639
+ } else {
640
+ prefix += `.${part}`;
641
+ }
642
+ prefixes[i] = prefix;
643
+ }
644
+ return prefixes;
645
+ }
646
+
437
647
  function checkIfObjectOrFunction(path: string, schema: ScopeNode['schema']) {
438
648
  const pathParts = splitOutsideParenthesesAndArrays(path);
439
649
  for (const structureKey in schema) {
@@ -506,20 +716,20 @@ function checkIfKnownType(
506
716
  let hasStrongArrayEvidence = false;
507
717
  let hasOnlyAmbiguousArrayMethods = true;
508
718
  let hasLengthAccess = false;
509
- let hasArrayStringAmbiguousMethod = false;
510
719
  let hasIncludesOrIndexOfWithVariable = false;
511
720
 
512
- for (const functionOrAttributeName of functionAndAttributesKeysMapping[key] ??
513
- []) {
721
+ const entries = functionAndAttributesKeysMapping[key];
722
+ if (!entries || entries.length === 0) return;
723
+
724
+ for (const functionOrAttributeName of entries) {
514
725
  // Extract the method name without arguments for checking against ambiguous list
515
726
  const methodName = functionOrAttributeName.split('(')[0].trim();
516
727
 
728
+ const knownType = knownMethodCalls(functionOrAttributeName);
517
729
  const couldBeArray =
518
- knownMethodCalls(functionOrAttributeName).includes('array') ||
519
- functionOrAttributeName === 'length';
730
+ knownType.includes('array') || functionOrAttributeName === 'length';
520
731
  const couldBeString =
521
- knownMethodCalls(functionOrAttributeName).includes('string') ||
522
- functionOrAttributeName === 'length';
732
+ knownType.includes('string') || functionOrAttributeName === 'length';
523
733
 
524
734
  // Track .length access
525
735
  if (functionOrAttributeName === 'length') {
@@ -527,10 +737,6 @@ function checkIfKnownType(
527
737
  }
528
738
 
529
739
  // Track methods that exist on both arrays and strings (like includes, indexOf)
530
- if (couldBeArray && couldBeString && functionOrAttributeName !== 'length') {
531
- hasArrayStringAmbiguousMethod = true;
532
- }
533
-
534
740
  // Check if .includes() or .indexOf() is called with a variable argument (not a string literal).
535
741
  // Pattern: arr.includes(item) -> likely array (checking if item exists)
536
742
  // Pattern: str.includes('substring') -> likely string (checking for substring)