@codeyam/codeyam-cli 0.1.0-staging.7c30edc → 0.1.0-staging.8421896

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 (117) 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 +1 -1
  4. package/analyzer-template/packages/ai/index.ts +1 -0
  5. package/analyzer-template/packages/ai/src/lib/analyzeScope.ts +14 -0
  6. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.ts +29 -18
  7. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +18 -0
  8. package/analyzer-template/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.ts +50 -25
  9. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +153 -76
  10. package/analyzer-template/packages/database/src/lib/kysely/tables/debugReportsTable.ts +1 -1
  11. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/debugReportsTable.d.ts +1 -1
  12. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
  13. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +93 -2
  14. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  15. package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +108 -2
  16. package/codeyam-cli/scripts/apply-setup.js +1 -1
  17. package/codeyam-cli/src/commands/analyze.js +2 -2
  18. package/codeyam-cli/src/commands/analyze.js.map +1 -1
  19. package/codeyam-cli/src/commands/default.js +6 -1
  20. package/codeyam-cli/src/commands/default.js.map +1 -1
  21. package/codeyam-cli/src/commands/setup-simulations.js +1 -1
  22. package/codeyam-cli/src/commands/verify.js +12 -2
  23. package/codeyam-cli/src/commands/verify.js.map +1 -1
  24. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +11 -11
  25. package/codeyam-cli/src/utils/backgroundServer.js +7 -1
  26. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  27. package/codeyam-cli/src/utils/generateReport.js +2 -2
  28. package/codeyam-cli/src/utils/install-skills.js +13 -13
  29. package/codeyam-cli/src/utils/labsAutoCheck.js +0 -29
  30. package/codeyam-cli/src/utils/labsAutoCheck.js.map +1 -1
  31. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +7 -7
  32. package/codeyam-cli/src/webserver/backgroundServer.js +0 -7
  33. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  34. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-CQ-wF3Tv.js +1 -0
  35. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-B86KKU7e.js → EntityItem-HdckCi0m.js} +1 -1
  36. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeBadge-B5ctlSYt.js → EntityTypeBadge-Dh5RJMOE.js} +1 -1
  37. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-BqY8gDAW.js → EntityTypeIcon-BnjjBHJu.js} +1 -1
  38. package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-ClaLpuOo.js → InlineSpinner-CUSfu6W5.js} +1 -1
  39. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-BDhPilK7.js → InteractivePreview-Coll1aD6.js} +2 -2
  40. package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-VeqEBv9v.js → LibraryFunctionPreview-lYMY8h-y.js} +1 -1
  41. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-Bs7Nn1Jr.js → LoadingDots-ay8XeA59.js} +1 -1
  42. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-Bm3PmcCz.js → LogViewer-Dpul1_ik.js} +1 -1
  43. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-C6PKeMYR.js → ReportIssueModal-CRBCfV2W.js} +2 -2
  44. package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-Gq3Ocjo6.js → SafeScreenshot-DRTFDNFt.js} +1 -1
  45. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-BNLaXBHR.js → ScenarioViewer-d6PSFxhS.js} +2 -2
  46. package/codeyam-cli/src/webserver/build/client/assets/{TruncatedFilePath-CiwXDxLh.js → TruncatedFilePath-DDEOQ6Iw.js} +1 -1
  47. package/codeyam-cli/src/webserver/build/client/assets/{_index-B3TDXxnk.js → _index-CkziGg5F.js} +1 -1
  48. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BtBFH820.js → activity.(_tab)-B2v1pm9w.js} +1 -1
  49. package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-CN61MOMa.js → agent-transcripts-BXIaK8Md.js} +1 -1
  50. package/codeyam-cli/src/webserver/build/client/assets/{book-open-PttOB2SF.js → book-open-qbapxy6o.js} +1 -1
  51. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-TJp6ofnp.js → chevron-down-C-mKrwr1.js} +1 -1
  52. package/codeyam-cli/src/webserver/build/client/assets/{chunk-JZWAC4HX-JE9ZIoBl.js → chunk-JZWAC4HX-BAvUl1nT.js} +8 -8
  53. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-CXhHQYrI.js → circle-check-DlrT-SzI.js} +1 -1
  54. package/codeyam-cli/src/webserver/build/client/assets/{copy-6y9ALfGT.js → copy-clIxnCqQ.js} +1 -1
  55. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-Ca9fAY46.js → createLucideIcon-B931Etud.js} +1 -1
  56. package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-C5lqplTC.js → dev.empty-BoPM6KnE.js} +1 -1
  57. package/codeyam-cli/src/webserver/build/client/assets/entity._sha._-oVRMh9Hl.js +16 -0
  58. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-CBoafmVs.js → entity._sha.scenarios._scenarioId.fullscreen-BjyzwQ7H.js} +1 -1
  59. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-DGgZjdFg.js → entity._sha_.create-scenario-DxuyDmZA.js} +1 -1
  60. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-38yPijoD.js → entity._sha_.edit._scenarioId-DESSZGQp.js} +1 -1
  61. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-BSHEfydn.js → entry.client-D5Yb90Ad.js} +1 -1
  62. package/codeyam-cli/src/webserver/build/client/assets/{fileTableUtils-DCPhhSMo.js → fileTableUtils-DuObVYgh.js} +1 -1
  63. package/codeyam-cli/src/webserver/build/client/assets/{files-0N0YJQv7.js → files-CDfz4Y-i.js} +1 -1
  64. package/codeyam-cli/src/webserver/build/client/assets/{git-DXnyr8uP.js → git-D6jOlDQw.js} +1 -1
  65. package/codeyam-cli/src/webserver/build/client/assets/globals-CKT08Djd.css +1 -0
  66. package/codeyam-cli/src/webserver/build/client/assets/{index-ChN9-fAY.js → index-DvOt1KIt.js} +1 -1
  67. package/codeyam-cli/src/webserver/build/client/assets/{index-CcsFv748.js → index-WfQFdoWK.js} +1 -1
  68. package/codeyam-cli/src/webserver/build/client/assets/labs-BbGyC1RY.js +1 -0
  69. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-CTqLEAGU.js → loader-circle-Bb7Y9k5O.js} +1 -1
  70. package/codeyam-cli/src/webserver/build/client/assets/manifest-fd06e67a.js +1 -0
  71. package/codeyam-cli/src/webserver/build/client/assets/memory-BXebUPaL.js +78 -0
  72. package/codeyam-cli/src/webserver/build/client/assets/{pause-D6vreykR.js → pause-DaAHX2on.js} +1 -1
  73. package/codeyam-cli/src/webserver/build/client/assets/{root-F7e6dvys.js → root-CvNE9MaT.js} +9 -9
  74. package/codeyam-cli/src/webserver/build/client/assets/{search-B8VUL8nl.js → search-DIqAPIrO.js} +1 -1
  75. package/codeyam-cli/src/webserver/build/client/assets/{settings-BejnUJ6R.js → settings-DCIzBZM9.js} +1 -1
  76. package/codeyam-cli/src/webserver/build/client/assets/{simulations-CPoAg7Zo.js → simulations-C6n_fNQY.js} +1 -1
  77. package/codeyam-cli/src/webserver/build/client/assets/{terminal-BrCP7uQo.js → terminal-CmPsszJy.js} +1 -1
  78. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-BZz2NjYa.js → triangle-alert-Beg-oV50.js} +1 -1
  79. package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-DNwUduNu.js → useCustomSizes-D7TLbP3M.js} +1 -1
  80. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-COky1GVF.js → useLastLogLine-Ce5rnai3.js} +1 -1
  81. package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-CpZgwliL.js → useReportContext-B4D3wj27.js} +1 -1
  82. package/codeyam-cli/src/webserver/build/client/assets/{useToast-Bv9JFvUO.js → useToast-BDt_-DnY.js} +1 -1
  83. package/codeyam-cli/src/webserver/build/server/assets/{index-CFKHuovO.js → index-DDr9Cp9M.js} +1 -1
  84. package/codeyam-cli/src/webserver/build/server/assets/server-build-DjwiujaU.js +257 -0
  85. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  86. package/codeyam-cli/src/webserver/build-info.json +5 -5
  87. package/codeyam-cli/templates/{codeyam:debug.md → codeyam-debug.md} +1 -1
  88. package/codeyam-cli/templates/{codeyam:diagnose.md → codeyam-diagnose.md} +1 -1
  89. package/codeyam-cli/templates/{codeyam:memory.md → codeyam-memory.md} +2 -2
  90. package/codeyam-cli/templates/{codeyam:new-rule.md → codeyam-new-rule.md} +1 -1
  91. package/codeyam-cli/templates/{codeyam:setup.md → codeyam-setup.md} +1 -1
  92. package/codeyam-cli/templates/{codeyam:sim.md → codeyam-sim.md} +1 -1
  93. package/codeyam-cli/templates/{codeyam:test.md → codeyam-test.md} +1 -1
  94. package/codeyam-cli/templates/{codeyam:verify.md → codeyam-verify.md} +1 -1
  95. package/package.json +9 -9
  96. package/packages/ai/index.js +1 -1
  97. package/packages/ai/index.js.map +1 -1
  98. package/packages/ai/src/lib/analyzeScope.js +14 -0
  99. package/packages/ai/src/lib/analyzeScope.js.map +1 -1
  100. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js +24 -16
  101. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js.map +1 -1
  102. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +11 -1
  103. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  104. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js +42 -13
  105. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js.map +1 -1
  106. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +123 -67
  107. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  108. package/packages/utils/src/lib/fs/rsyncCopy.js +93 -2
  109. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  110. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-CA3JxPb7.js +0 -1
  111. package/codeyam-cli/src/webserver/build/client/assets/api.labs-survey-l0sNRNKZ.js +0 -1
  112. package/codeyam-cli/src/webserver/build/client/assets/entity._sha._-n38keI1k.js +0 -23
  113. package/codeyam-cli/src/webserver/build/client/assets/globals-CuCsBc3b.css +0 -1
  114. package/codeyam-cli/src/webserver/build/client/assets/labs-CB3MGcys.js +0 -1
  115. package/codeyam-cli/src/webserver/build/client/assets/manifest-de6ccaf4.js +0 -1
  116. package/codeyam-cli/src/webserver/build/client/assets/memory-DCA-kLYt.js +0 -81
  117. package/codeyam-cli/src/webserver/build/server/assets/server-build-BQe9Dh4p.js +0 -260
@@ -1,10 +1,10 @@
1
1
  {
2
- "buildTimestamp": "2026-02-12T15:28:10.920Z",
3
- "buildTime": 1770910090920,
4
- "gitCommit": "7c30edc8b3c3c3d584502dd7762988219e5dabbb",
2
+ "buildTimestamp": "2026-02-13T11:24:56.052Z",
3
+ "buildTime": 1770981896052,
4
+ "gitCommit": "84218967703e4bac51f7219e6e447b209b350d5f",
5
5
  "nodeVersion": "v20.20.0",
6
- "contentHash": "1614bacb94f772727a12f702e776ac1fe8e99792dd6cf286ea389f7c8026f245",
7
- "buildNumber": 617,
8
- "semanticVersion": "0.1.617",
9
- "version": "0.1.617 (2026-02-12T15:28+1614bac)"
6
+ "contentHash": "4e94e73e9be6ff27f80a8345110fe1d31d9863861a8966e4b17887ad7c6eff67",
7
+ "buildNumber": 619,
8
+ "semanticVersion": "0.1.619",
9
+ "version": "0.1.619 (2026-02-13T11:24+4e94e73)"
10
10
  }
@@ -1,7 +1,7 @@
1
1
 
2
- [2/12/2026, 3:28:10 PM] > codeyam-combo@1.0.0 mergeDependencies
3
- [2/12/2026, 3:28:10 PM] > node ./scripts/mergePackageJsonFiles.cjs
2
+ [2/13/2026, 11:24:55 AM] > codeyam-combo@1.0.0 mergeDependencies
3
+ [2/13/2026, 11:24:55 AM] > node ./scripts/mergePackageJsonFiles.cjs
4
4
 
5
5
 
6
- [2/12/2026, 3:28:10 PM] Merged dependencies into root package.json
6
+ [2/13/2026, 11:24:56 AM] Merged dependencies into root package.json
7
7
 
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@aws-sdk/client-cloudwatch-logs": "^3.980.0",
11
- "@aws-sdk/client-cloudfront": "^3.966.0",
11
+ "@aws-sdk/client-cloudfront": "^3.985.0",
12
12
  "@aws-sdk/client-codebuild": "^3.948.0",
13
13
  "@aws-sdk/client-dynamodb": "^3.956.0",
14
14
  "@aws-sdk/client-ec2": "^3.899.0",
@@ -43,6 +43,7 @@ export { default as isolateScopes } from './src/lib/isolateScopes';
43
43
  export {
44
44
  default as analyzeScope,
45
45
  destroyWorkerPool,
46
+ skipWorkerPool,
46
47
  } from './src/lib/analyzeScope';
47
48
  export { default as logOrderedMap } from './src/lib/logOrderedMap';
48
49
  export {
@@ -50,6 +50,19 @@ let workerPool: Piscina<
50
50
  AnalyzeScopeWorkerOutput
51
51
  > | null = null;
52
52
  let workerPoolDestroyed = false;
53
+ let workerPoolSkipped = false;
54
+
55
+ /**
56
+ * Skip worker pool initialization for the current session.
57
+ *
58
+ * The worker thread creates its own ProjectAnalyzer (ts.Program), which takes
59
+ * 50-70s — far longer than the actual entity processing (<2s for typical batches).
60
+ * When the entity count is small, running analyzeScopeLocal on the main thread
61
+ * reuses the existing ProjectAnalyzer and avoids this overhead entirely.
62
+ */
63
+ export function skipWorkerPool() {
64
+ workerPoolSkipped = true;
65
+ }
53
66
 
54
67
  /**
55
68
  * Check if we're in Node.js main thread
@@ -108,6 +121,7 @@ function ensureWorkerPool() {
108
121
  'analyzeScope should not be called after data structure preparation completes.',
109
122
  );
110
123
  }
124
+ if (workerPoolSkipped) return;
111
125
  if (workerPool !== null) return;
112
126
 
113
127
  // Only initialize worker pool in Node.js environment
@@ -14,35 +14,46 @@ const isStandaloneIndex = (s?: string) => !!s && STANDALONE_INDEX_RE.test(s);
14
14
  // The regex matches any path ending with .length that has [] somewhere before it
15
15
  const DYNAMIC_LENGTH_RE = /\[\].*\.length$/;
16
16
 
17
- // Treat these as structural placeholders (don't commit them as concrete leaves)
18
- function isSkippableLeafType(t: string) {
19
- // 'unknown' by itself is a placeholder (but 'boolean | unknown' is not)
20
- // Also handles optional variants like 'object | undefined', 'object | null',
21
- // 'array | undefined', etc. these are still structural placeholders that
22
- // should not overwrite already-populated structures.
17
+ // Cache for type string analysis to avoid repeated split/filter operations.
18
+ // These functions are called multiple times per path segment across thousands of paths.
19
+ const typeAnalysisCache = new Map<
20
+ string,
21
+ { isSkippable: boolean; baseType: string; isNullable: boolean }
22
+ >();
23
+
24
+ function getTypeAnalysis(t: string) {
25
+ const cached = typeAnalysisCache.get(t);
26
+ if (cached) return cached;
23
27
  const parts = t.split('|').map((s) => s.trim());
24
28
  const base = parts.filter((s) => s !== 'undefined' && s !== 'null');
25
- return (
26
- base.length === 1 &&
27
- (base[0] === 'object' ||
28
- base[0] === 'array' ||
29
- base[0] === 'function' ||
30
- base[0] === 'unknown')
31
- );
29
+ const result = {
30
+ isSkippable:
31
+ base.length === 1 &&
32
+ (base[0] === 'object' ||
33
+ base[0] === 'array' ||
34
+ base[0] === 'function' ||
35
+ base[0] === 'unknown'),
36
+ baseType: base[0],
37
+ isNullable: parts.includes('undefined') || parts.includes('null'),
38
+ };
39
+ typeAnalysisCache.set(t, result);
40
+ return result;
41
+ }
42
+
43
+ // Treat these as structural placeholders (don't commit them as concrete leaves)
44
+ function isSkippableLeafType(t: string) {
45
+ return getTypeAnalysis(t).isSkippable;
32
46
  }
33
47
 
34
48
  // Extract the base structural type from a potentially nullable type string.
35
49
  // e.g., 'object | undefined' → 'object', 'array | null' → 'array'
36
50
  function getBaseSkippableType(t: string): string {
37
- const parts = t.split('|').map((s) => s.trim());
38
- const base = parts.filter((s) => s !== 'undefined' && s !== 'null');
39
- return base[0];
51
+ return getTypeAnalysis(t).baseType;
40
52
  }
41
53
 
42
54
  // Check if a type string has nullable annotations (| undefined or | null)
43
55
  function isNullableType(t: string): boolean {
44
- const parts = t.split('|').map((s) => s.trim());
45
- return parts.includes('undefined') || parts.includes('null');
56
+ return getTypeAnalysis(t).isNullable;
46
57
  }
47
58
 
48
59
  // Matches paths containing [][] — e.g., "items[][]" or "items[][].text"
@@ -18,6 +18,7 @@ import {
18
18
  getCompoundConditionals,
19
19
  getChildBoundaryGatingConditions,
20
20
  getJsxRenderingUsages,
21
+ skipWorkerPool,
21
22
  } from '~codeyam/ai';
22
23
  import type { AnalysisOptions } from '../analyzeEntities';
23
24
 
@@ -271,6 +272,23 @@ export default async function prepareEntityDataStructures(
271
272
  };
272
273
 
273
274
  const entities = Array.from(allEntities);
275
+
276
+ // The worker thread creates its own ProjectAnalyzer (ts.Program) on first use,
277
+ // which takes 50-70s. For small entity counts the overhead far exceeds the actual
278
+ // processing time (<2s). Skip the worker and run on the main thread instead,
279
+ // reusing the existing ProjectAnalyzer.
280
+ const entitiesNeedingGeneration = entities.filter(
281
+ (e) =>
282
+ !e.metadata?.isolatedDataStructure || options.force || options.forceAll,
283
+ ).length;
284
+ const WORKER_ENTITY_THRESHOLD = 500;
285
+ if (entitiesNeedingGeneration < WORKER_ENTITY_THRESHOLD) {
286
+ console.log(
287
+ `CodeYam: Skipping worker thread for DS prep (${entitiesNeedingGeneration} entities < ${WORKER_ENTITY_THRESHOLD} threshold)`,
288
+ );
289
+ skipWorkerPool();
290
+ }
291
+
274
292
  const sequential =
275
293
  DEBUG_SEQUENTIAL_EXECUTION || options.sequentialDataStructurePrep;
276
294
 
@@ -353,8 +353,6 @@ function processCall(
353
353
  // Preprocess to filter signatures and extract functionCallReturnValue
354
354
  let preprocessedSchema = preprocessSchemaForMocks(mergedSchema);
355
355
 
356
- const functionNames: string[] = [];
357
-
358
356
  const isFunctionNameWithoutFunction = (
359
357
  path: string,
360
358
  functionName: string,
@@ -439,31 +437,58 @@ function processCall(
439
437
  }
440
438
  }
441
439
 
442
- const relevantMergedDependencySchema = Object.keys(preprocessedSchema).reduce(
443
- (acc, path) => {
444
- if (path.endsWith(')')) {
445
- const pathParts = splitOutsideParenthesesAndArrays(path);
446
- const functionName = joinParenthesesAndArrays([
447
- ...pathParts.slice(0, -1),
448
- pathParts[pathParts.length - 1].split('(')[0],
449
- ]);
450
- functionNames.push(functionName);
451
- for (const existingPath in acc) {
452
- if (isFunctionNameWithoutFunction(existingPath, functionName)) {
453
- delete acc[existingPath];
454
- }
455
- }
456
- }
440
+ // Two-pass function name filtering to avoid O() retroactive deletion.
441
+ // Pass 1: Discover all unique function names from paths ending with ')'
442
+ const functionNameSet = new Set<string>();
443
+ const schemaKeys = Object.keys(preprocessedSchema);
444
+ for (const path of schemaKeys) {
445
+ if (path.endsWith(')')) {
446
+ const pathParts = splitOutsideParenthesesAndArrays(path);
447
+ const functionName = joinParenthesesAndArrays([
448
+ ...pathParts.slice(0, -1),
449
+ pathParts[pathParts.length - 1].split('(')[0],
450
+ ]);
451
+ functionNameSet.add(functionName);
452
+ }
453
+ }
457
454
 
458
- if (functionNames.some((fn) => isFunctionNameWithoutFunction(path, fn))) {
459
- return acc;
460
- }
455
+ // Index function names by first segment for O(1) bucket lookup instead of
456
+ // checking all function names for every path. Most paths only need to check
457
+ // 1-2 function names instead of all F.
458
+ const fnByFirstSegment = new Map<string, string[]>();
459
+ for (const fn of functionNameSet) {
460
+ const dotIdx = fn.indexOf('.');
461
+ const firstSeg = dotIdx >= 0 ? fn.slice(0, dotIdx) : fn;
462
+ let bucket = fnByFirstSegment.get(firstSeg);
463
+ if (!bucket) {
464
+ bucket = [];
465
+ fnByFirstSegment.set(firstSeg, bucket);
466
+ }
467
+ bucket.push(fn);
468
+ }
461
469
 
462
- acc[path] = preprocessedSchema[path];
463
- return acc;
464
- },
465
- {} as Record<string, string>,
466
- );
470
+ // Pass 2: Filter paths that match any discovered function name prefix.
471
+ // Use indexed lookup: extract path's first segment, check only matching function names.
472
+ const relevantMergedDependencySchema: Record<string, string> = {};
473
+ for (const path of schemaKeys) {
474
+ // Extract first segment of path (before first '.', '(', or '<')
475
+ const dotIdx = path.indexOf('.');
476
+ const parenIdx = path.indexOf('(');
477
+ const angleIdx = path.indexOf('<');
478
+ let minIdx = path.length;
479
+ if (dotIdx >= 0 && dotIdx < minIdx) minIdx = dotIdx;
480
+ if (parenIdx >= 0 && parenIdx < minIdx) minIdx = parenIdx;
481
+ if (angleIdx >= 0 && angleIdx < minIdx) minIdx = angleIdx;
482
+ const firstSeg = path.slice(0, minIdx);
483
+
484
+ const candidates = fnByFirstSegment.get(firstSeg);
485
+ if (
486
+ !candidates ||
487
+ !candidates.some((fn) => isFunctionNameWithoutFunction(path, fn))
488
+ ) {
489
+ relevantMergedDependencySchema[path] = preprocessedSchema[path];
490
+ }
491
+ }
467
492
 
468
493
  const filledSchema = fillInDirectSchemaGapsAndUnknowns({
469
494
  scopeName: importedExport.name,
@@ -210,6 +210,23 @@ export default function mergeInDependentDataStructure({
210
210
  equivalentPostfixes: Record<string, string>;
211
211
  }[] = [];
212
212
 
213
+ // O(1) index for findOrCreateEquivalentSchemaPathsEntry.
214
+ // Maps "(rootPath)::(normalizedFuncName)" → the entry containing that root.
215
+ // This replaces the O(E) linear search that was causing O(E²) gather performance.
216
+ const espIndex = new Map<string, (typeof equivalentSchemaPaths)[0]>();
217
+ const espIndexKey = (path: string, functionName: string | undefined) => {
218
+ const normalized = cleanFunctionName(functionName);
219
+ const funcKey =
220
+ normalized === rootScopeName ? '__self__' : normalized || '__self__';
221
+ return `${path}::${funcKey}`;
222
+ };
223
+ const updateEspIndex = (entry: (typeof equivalentSchemaPaths)[0]) => {
224
+ for (const root of entry.equivalentRoots) {
225
+ const funcName = root.function?.name ?? rootScopeName;
226
+ espIndex.set(espIndexKey(root.schemaRootPath, funcName), entry);
227
+ }
228
+ };
229
+
213
230
  // Pre-build a lookup map from cleaned function name to dependency for O(1) lookups.
214
231
  // This avoids O(n) linear search in findRelevantDependency which was causing O(n²) performance.
215
232
  const dependencyByCleanedName = new Map<
@@ -324,6 +341,54 @@ export default function mergeInDependentDataStructure({
324
341
  ) => {
325
342
  if (!sourceAndUsageEquivalencies) return;
326
343
 
344
+ // Pre-computed normalized schema index cache.
345
+ // Avoids repeated splitOutsideParenthesesAndArrays calls and function-name
346
+ // normalization for the same schema paths across multiple equivalency iterations.
347
+ // The normalization depends on `functionName` (constant per gatherAllEquivalentSchemaPaths call),
348
+ // so this cache is scoped to this call.
349
+ type NormalizedEntry = { path: string; parts: string[] };
350
+ const normalizedSchemaCache = new Map<
351
+ object,
352
+ {
353
+ byFirstPart: Map<string, NormalizedEntry[]>;
354
+ }
355
+ >();
356
+ const getSchemaIndex = (
357
+ schema: Record<string, string> | undefined,
358
+ ): { byFirstPart: Map<string, NormalizedEntry[]> } => {
359
+ if (!schema) return { byFirstPart: new Map() };
360
+ const cached = normalizedSchemaCache.get(schema);
361
+ if (cached) return cached;
362
+ const byFirstPart = new Map<string, NormalizedEntry[]>();
363
+ for (const path in schema) {
364
+ let parts = splitOutsideParenthesesAndArrays(path);
365
+ if (parts[0].startsWith(functionName)) {
366
+ const baseName = cleanFunctionName(parts[0]);
367
+ if (!functionsWithMultipleTypeParams.has(baseName)) {
368
+ parts =
369
+ parts[1] === 'functionCallReturnValue'
370
+ ? ['returnValue', ...parts.slice(2)]
371
+ : parts.slice(1);
372
+ }
373
+ }
374
+ const entry: NormalizedEntry = { path, parts };
375
+ // Index by the base of the first part (before any function call args)
376
+ const firstPart = parts[0] ?? '';
377
+ const parenIdx = firstPart.indexOf('(');
378
+ const firstPartBase =
379
+ parenIdx >= 0 ? firstPart.slice(0, parenIdx) : firstPart;
380
+ let bucket = byFirstPart.get(firstPartBase);
381
+ if (!bucket) {
382
+ bucket = [];
383
+ byFirstPart.set(firstPartBase, bucket);
384
+ }
385
+ bucket.push(entry);
386
+ }
387
+ const result = { byFirstPart };
388
+ normalizedSchemaCache.set(schema, result);
389
+ return result;
390
+ };
391
+
327
392
  const findOrCreateEquivalentSchemaPathsEntry = (
328
393
  allPaths: { path: string; functionName?: string }[],
329
394
  ) => {
@@ -354,62 +419,52 @@ export default function mergeInDependentDataStructure({
354
419
  }
355
420
  }
356
421
 
422
+ // Use espIndex Map for O(1) lookup instead of O(E) linear search.
423
+ // Falls back to linear search only when Map hit has a signature index conflict.
357
424
  for (const pathInfo of allPaths) {
358
- if (!equivalentSchemaPathsEntry) {
359
- equivalentSchemaPathsEntry = equivalentSchemaPaths.find((esp) => {
360
- // First check: does this entry have a matching root?
361
- const hasMatchingRoot = esp.equivalentRoots.some(
362
- (er) =>
363
- er.schemaRootPath === pathInfo.path &&
364
- (er.function?.name ===
365
- cleanFunctionName(pathInfo.functionName) ||
366
- (!er.function &&
367
- cleanFunctionName(pathInfo.functionName) ===
368
- rootScopeName)),
369
- );
370
- if (!hasMatchingRoot) return false;
371
-
372
- // Second check: would adding our new roots create a signature index conflict?
373
- // An entry should NOT contain roots with different signature indices from the same function.
374
- if (newRootSignatureIndices.size > 0) {
375
- // Get all signature indices in the existing entry (grouped by function)
376
- const existingIndicesByFunction = new Map<string, Set<number>>();
377
- for (const er of esp.equivalentRoots) {
378
- const funcKey = er.function
379
- ? `${er.function.name}::${er.function.filePath}`
380
- : '__self__';
381
- const idx = extractSignatureIndex(er.schemaRootPath);
382
- if (idx !== undefined) {
383
- if (!existingIndicesByFunction.has(funcKey)) {
384
- existingIndicesByFunction.set(funcKey, new Set());
385
- }
386
- existingIndicesByFunction.get(funcKey)!.add(idx);
387
- }
425
+ if (equivalentSchemaPathsEntry) break;
426
+ const candidate = espIndex.get(
427
+ espIndexKey(pathInfo.path, pathInfo.functionName),
428
+ );
429
+ if (!candidate) continue;
430
+
431
+ // Verify no signature index conflict with the candidate entry
432
+ if (newRootSignatureIndices.size > 0) {
433
+ const existingIndicesByFunction = new Map<string, Set<number>>();
434
+ for (const er of candidate.equivalentRoots) {
435
+ const funcKey = er.function
436
+ ? `${er.function.name}::${er.function.filePath}`
437
+ : '__self__';
438
+ const idx = extractSignatureIndex(er.schemaRootPath);
439
+ if (idx !== undefined) {
440
+ if (!existingIndicesByFunction.has(funcKey)) {
441
+ existingIndicesByFunction.set(funcKey, new Set());
388
442
  }
443
+ existingIndicesByFunction.get(funcKey)!.add(idx);
444
+ }
445
+ }
389
446
 
390
- // Check if adding our new roots would create a conflict
391
- for (const newRoot of equivalentRoots) {
392
- const funcKey = newRoot.function
393
- ? `${newRoot.function.name}::${newRoot.function.filePath}`
394
- : '__self__';
395
- const newIdx = extractSignatureIndex(newRoot.schemaRootPath);
396
- if (newIdx !== undefined) {
397
- const existingIndices =
398
- existingIndicesByFunction.get(funcKey);
399
- if (existingIndices && existingIndices.size > 0) {
400
- // If this function already has signature indices, check for conflict
401
- if (!existingIndices.has(newIdx)) {
402
- // Conflict: entry has indices like [1] but we want to add [2]
403
- return false;
404
- }
405
- }
447
+ let hasConflict = false;
448
+ for (const newRoot of equivalentRoots) {
449
+ const funcKey = newRoot.function
450
+ ? `${newRoot.function.name}::${newRoot.function.filePath}`
451
+ : '__self__';
452
+ const newIdx = extractSignatureIndex(newRoot.schemaRootPath);
453
+ if (newIdx !== undefined) {
454
+ const existingIndices = existingIndicesByFunction.get(funcKey);
455
+ if (existingIndices && existingIndices.size > 0) {
456
+ if (!existingIndices.has(newIdx)) {
457
+ hasConflict = true;
458
+ break;
406
459
  }
407
460
  }
408
461
  }
462
+ }
409
463
 
410
- return true;
411
- });
464
+ if (hasConflict) continue;
412
465
  }
466
+
467
+ equivalentSchemaPathsEntry = candidate;
413
468
  }
414
469
 
415
470
  if (!equivalentSchemaPathsEntry) {
@@ -467,6 +522,9 @@ export default function mergeInDependentDataStructure({
467
522
  return true;
468
523
  });
469
524
 
525
+ // Keep the espIndex in sync after adding/deduplicating roots
526
+ updateEspIndex(equivalentSchemaPathsEntry);
527
+
470
528
  return equivalentSchemaPathsEntry;
471
529
  };
472
530
 
@@ -535,6 +593,8 @@ export default function mergeInDependentDataStructure({
535
593
  );
536
594
 
537
595
  const derivedBasePaths: { path: string; functionName?: string }[] = [];
596
+ const allPathSet = new Set(allPaths.map((p) => p.path));
597
+ const derivedBasePathSet = new Set<string>();
538
598
 
539
599
  // For each child path, find its equivalent parent path and derive bases
540
600
  for (const childPathInfo of childPaths) {
@@ -611,26 +671,28 @@ export default function mergeInDependentDataStructure({
611
671
  !childHasArrayIterator &&
612
672
  !childBaseIsGenericSignature
613
673
  ) {
614
- // Add child base if not already present
615
- const childBaseExists =
616
- allPaths.some((p) => p.path === childBase) ||
617
- derivedBasePaths.some((p) => p.path === childBase);
618
- if (!childBaseExists) {
674
+ // Add child base if not already present (O(1) Set lookup)
675
+ if (
676
+ !allPathSet.has(childBase) &&
677
+ !derivedBasePathSet.has(childBase)
678
+ ) {
619
679
  derivedBasePaths.push({
620
680
  path: childBase,
621
681
  functionName: childPathInfo.functionName,
622
682
  });
683
+ derivedBasePathSet.add(childBase);
623
684
  }
624
685
 
625
- // Add parent base if not already present
626
- const parentBaseExists =
627
- allPaths.some((p) => p.path === parentBase) ||
628
- derivedBasePaths.some((p) => p.path === parentBase);
629
- if (!parentBaseExists) {
686
+ // Add parent base if not already present (O(1) Set lookup)
687
+ if (
688
+ !allPathSet.has(parentBase) &&
689
+ !derivedBasePathSet.has(parentBase)
690
+ ) {
630
691
  derivedBasePaths.push({
631
692
  path: parentBase,
632
693
  functionName: parentPathInfo.functionName,
633
694
  });
695
+ derivedBasePathSet.add(parentBase);
634
696
  }
635
697
  }
636
698
  }
@@ -708,21 +770,20 @@ export default function mergeInDependentDataStructure({
708
770
  }
709
771
 
710
772
  for (const schema of schemas) {
711
- for (const schemaPath in schema) {
712
- let schemaPathParts =
713
- splitOutsideParenthesesAndArrays(schemaPath);
714
-
715
- if (schemaPathParts[0].startsWith(functionName)) {
716
- // Only normalize if the function doesn't have multiple different type parameters
717
- const baseName = cleanFunctionName(schemaPathParts[0]);
718
- if (!functionsWithMultipleTypeParams.has(baseName)) {
719
- schemaPathParts =
720
- schemaPathParts[1] === 'functionCallReturnValue'
721
- ? ['returnValue', ...schemaPathParts.slice(2)]
722
- : schemaPathParts.slice(1);
723
- }
724
- }
725
-
773
+ // Use pre-computed index to only iterate schema entries whose
774
+ // normalized first part matches pathParts[0], instead of all entries.
775
+ const schemaIndex = getSchemaIndex(schema);
776
+ const lookupPart = pathParts[0] ?? '';
777
+ const lookupParenIdx = lookupPart.indexOf('(');
778
+ const lookupBase =
779
+ lookupParenIdx >= 0
780
+ ? lookupPart.slice(0, lookupParenIdx)
781
+ : lookupPart;
782
+ const candidates = schemaIndex.byFirstPart.get(lookupBase) || [];
783
+ for (const {
784
+ path: schemaPath,
785
+ parts: schemaPathParts,
786
+ } of candidates) {
726
787
  if (schemaPathParts.length < pathParts.length) continue;
727
788
 
728
789
  // Check if all path parts match (allowing function call variants)
@@ -850,10 +911,17 @@ export default function mergeInDependentDataStructure({
850
911
  const entry = findOrCreateEquivalentSchemaPathsEntry([
851
912
  { path: translatedBasePath, functionName: functionName },
852
913
  ]);
853
- entry.equivalentRoots.push({
914
+ const newRoot = {
854
915
  schemaRootPath: translatedBasePath,
855
916
  function: findRelevantDependency(functionName),
856
- });
917
+ };
918
+ entry.equivalentRoots.push(newRoot);
919
+ // Update index for the newly added root
920
+ const newRootFuncName = newRoot.function?.name ?? rootScopeName;
921
+ espIndex.set(
922
+ espIndexKey(newRoot.schemaRootPath, newRootFuncName),
923
+ entry,
924
+ );
857
925
 
858
926
  const basePathParts = splitOutsideParenthesesAndArrays(basePath);
859
927
  for (const schemaPath in dataStructure.returnValueSchema) {
@@ -1113,6 +1181,10 @@ export default function mergeInDependentDataStructure({
1113
1181
 
1114
1182
  equivalentSchemaPaths = mergeAllEquivalentSchemaPaths();
1115
1183
 
1184
+ // Collect schemas that need cleaning — batch the calls for the end instead of
1185
+ // calling cleanSchema inside the inner root loop (which was O(roots * schemaSize)).
1186
+ const schemasToClean = new Set<{ [key: string]: string }>();
1187
+
1116
1188
  for (const esp of equivalentSchemaPaths) {
1117
1189
  // Pre-compute which postfixes have children to avoid O(n²) lookups in the inner loop.
1118
1190
  // A postfix "has children" if there are other postfixes that extend it.
@@ -1290,10 +1362,15 @@ export default function mergeInDependentDataStructure({
1290
1362
  schema[newSchemaPath] = postfixValue;
1291
1363
  }
1292
1364
 
1293
- cleanSchema(schema, { stage: 'afterMergePostfix' });
1365
+ schemasToClean.add(schema);
1294
1366
  }
1295
1367
  }
1296
1368
 
1369
+ // Batch-clean all modified schemas once (instead of once per root per ESP entry)
1370
+ for (const schema of schemasToClean) {
1371
+ cleanSchema(schema, { stage: 'afterMergePostfix' });
1372
+ }
1373
+
1297
1374
  // Propagate equivalency-derived attributes to generic function call variants.
1298
1375
  // When attributes are traced via equivalencies (e.g., fileComparisons from buildDataMap.signature[2]),
1299
1376
  // they get written to non-generic paths (returnValue.data.x or funcName().functionCallReturnValue.data.x).
@@ -6,7 +6,7 @@ import { schemaField, defaultNow } from '../schemaHelpers';
6
6
  /**
7
7
  * Metadata for a debug bundle upload.
8
8
  * Note: "bundle" refers to the tarball upload (base + delta).
9
- * The markdown "debug report" from /codeyam:diagnose is stored in feedback.debugReport.
9
+ * The markdown "debug report" from /codeyam-diagnose is stored in feedback.debugReport.
10
10
  */
11
11
  interface DebugBundleMetadata {
12
12
  timestamp: string;
@@ -3,7 +3,7 @@ import type { Generated, JSONColumnType } from 'kysely';
3
3
  /**
4
4
  * Metadata for a debug bundle upload.
5
5
  * Note: "bundle" refers to the tarball upload (base + delta).
6
- * The markdown "debug report" from /codeyam:diagnose is stored in feedback.debugReport.
6
+ * The markdown "debug report" from /codeyam-diagnose is stored in feedback.debugReport.
7
7
  */
8
8
  interface DebugBundleMetadata {
9
9
  timestamp: string;
@@ -1 +1 @@
1
- {"version":3,"file":"rsyncCopy.d.ts","sourceRoot":"","sources":["../../../../../src/lib/fs/rsyncCopy.ts"],"names":[],"mappings":"AAEA,wBAA8B,SAAS,CAAC,EACtC,UAAU,EACV,eAAe,EACf,QAAa,EACb,YAAoB,EACpB,MAAc,EACd,SAAc,GACf,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+ChB"}
1
+ {"version":3,"file":"rsyncCopy.d.ts","sourceRoot":"","sources":["../../../../../src/lib/fs/rsyncCopy.ts"],"names":[],"mappings":"AAqFA,wBAA8B,SAAS,CAAC,EACtC,UAAU,EACV,eAAe,EACf,QAAa,EACb,YAAoB,EACpB,MAAc,EACd,SAAc,GACf,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsEhB"}