@codeyam/codeyam-cli 0.1.0-staging.c9dc00c → 0.1.0-staging.d3e886e

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 (189) 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/astScopes/processExpression.ts +101 -0
  7. package/analyzer-template/packages/ai/src/lib/astScopes/sharedPatterns.ts +28 -0
  8. package/analyzer-template/packages/ai/src/lib/astScopes/types.ts +6 -0
  9. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +172 -8
  10. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.ts +70 -19
  11. package/analyzer-template/packages/ai/src/lib/dataStructureChunking.ts +40 -13
  12. package/analyzer-template/packages/ai/src/lib/generateEntityScenarioData.ts +32 -5
  13. package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +134 -2
  14. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionals.ts +359 -142
  15. package/analyzer-template/packages/ai/src/lib/mergeJsonTypeDefinitions.ts +5 -0
  16. package/analyzer-template/packages/ai/src/lib/promptGenerators/collapseNullableObjects.ts +118 -0
  17. package/analyzer-template/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.ts +24 -4
  18. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +18 -0
  19. package/analyzer-template/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.ts +50 -25
  20. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +153 -76
  21. package/analyzer-template/packages/database/src/lib/kysely/tables/debugReportsTable.ts +1 -1
  22. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/debugReportsTable.d.ts +1 -1
  23. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
  24. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +93 -2
  25. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  26. package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +108 -2
  27. package/analyzer-template/project/constructMockCode.ts +2 -2
  28. package/analyzer-template/project/writeScenarioComponents.ts +14 -0
  29. package/background/src/lib/virtualized/project/constructMockCode.js +2 -2
  30. package/background/src/lib/virtualized/project/constructMockCode.js.map +1 -1
  31. package/background/src/lib/virtualized/project/writeScenarioComponents.js +10 -0
  32. package/background/src/lib/virtualized/project/writeScenarioComponents.js.map +1 -1
  33. package/codeyam-cli/scripts/apply-setup.js +1 -1
  34. package/codeyam-cli/src/codeyam-cli.js +18 -2
  35. package/codeyam-cli/src/codeyam-cli.js.map +1 -1
  36. package/codeyam-cli/src/commands/analyze.js +2 -2
  37. package/codeyam-cli/src/commands/analyze.js.map +1 -1
  38. package/codeyam-cli/src/commands/default.js +18 -17
  39. package/codeyam-cli/src/commands/default.js.map +1 -1
  40. package/codeyam-cli/src/commands/init.js +27 -93
  41. package/codeyam-cli/src/commands/init.js.map +1 -1
  42. package/codeyam-cli/src/commands/memory.js +9 -9
  43. package/codeyam-cli/src/commands/memory.js.map +1 -1
  44. package/codeyam-cli/src/commands/setup-simulations.js +1 -1
  45. package/codeyam-cli/src/commands/verify.js +12 -2
  46. package/codeyam-cli/src/commands/verify.js.map +1 -1
  47. package/codeyam-cli/src/utils/__tests__/npmVersionCheck.test.js +179 -0
  48. package/codeyam-cli/src/utils/__tests__/npmVersionCheck.test.js.map +1 -0
  49. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +11 -11
  50. package/codeyam-cli/src/utils/backgroundServer.js +90 -23
  51. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  52. package/codeyam-cli/src/utils/generateReport.js +2 -2
  53. package/codeyam-cli/src/utils/install-skills.js +13 -13
  54. package/codeyam-cli/src/utils/labsAutoCheck.js +0 -29
  55. package/codeyam-cli/src/utils/labsAutoCheck.js.map +1 -1
  56. package/codeyam-cli/src/utils/npmVersionCheck.js +76 -0
  57. package/codeyam-cli/src/utils/npmVersionCheck.js.map +1 -0
  58. package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js +4 -4
  59. package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js.map +1 -1
  60. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js +2 -2
  61. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js.map +1 -1
  62. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js +4 -4
  63. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js.map +1 -1
  64. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js +3 -3
  65. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js.map +1 -1
  66. package/codeyam-cli/src/utils/rules/__tests__/ruleState.test.js +23 -23
  67. package/codeyam-cli/src/utils/rules/__tests__/ruleState.test.js.map +1 -1
  68. package/codeyam-cli/src/utils/rules/ruleState.js +10 -10
  69. package/codeyam-cli/src/utils/rules/ruleState.js.map +1 -1
  70. package/codeyam-cli/src/utils/rules/staleness.js +6 -6
  71. package/codeyam-cli/src/utils/rules/staleness.js.map +1 -1
  72. package/codeyam-cli/src/utils/serverState.js +37 -10
  73. package/codeyam-cli/src/utils/serverState.js.map +1 -1
  74. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +7 -7
  75. package/codeyam-cli/src/webserver/__tests__/dependency-smoke.test.js +66 -0
  76. package/codeyam-cli/src/webserver/__tests__/dependency-smoke.test.js.map +1 -0
  77. package/codeyam-cli/src/webserver/app/lib/dbNotifier.js.map +1 -1
  78. package/codeyam-cli/src/webserver/backgroundServer.js +26 -7
  79. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  80. package/codeyam-cli/src/webserver/bootstrap.js +11 -0
  81. package/codeyam-cli/src/webserver/bootstrap.js.map +1 -1
  82. package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-CA3JxPb7.js → CopyButton-jNYXRRNI.js} +1 -1
  83. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-B86KKU7e.js → EntityItem-bwuHPyTa.js} +1 -1
  84. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeBadge-B5ctlSYt.js → EntityTypeBadge-CvzqMxcu.js} +1 -1
  85. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-BqY8gDAW.js → EntityTypeIcon-BH0XDim7.js} +1 -1
  86. package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-ClaLpuOo.js → InlineSpinner-EhOseatT.js} +1 -1
  87. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-BDhPilK7.js → InteractivePreview-yjIHlOGa.js} +2 -2
  88. package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-VeqEBv9v.js → LibraryFunctionPreview-Cq5o8jL4.js} +1 -1
  89. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-Bs7Nn1Jr.js → LoadingDots-BvMu2i-g.js} +1 -1
  90. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-Bm3PmcCz.js → LogViewer-kgBTLoJD.js} +1 -1
  91. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-C6PKeMYR.js → ReportIssueModal-BzPgx-xO.js} +2 -2
  92. package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-Gq3Ocjo6.js → SafeScreenshot-CwZrv-Ok.js} +1 -1
  93. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-BNLaXBHR.js → ScenarioViewer-BX2Ny2Qj.js} +2 -2
  94. package/codeyam-cli/src/webserver/build/client/assets/{TruncatedFilePath-CiwXDxLh.js → TruncatedFilePath-CDpEprKa.js} +1 -1
  95. package/codeyam-cli/src/webserver/build/client/assets/{_index-B3TDXxnk.js → _index-BRx8ZGZo.js} +1 -1
  96. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BtBFH820.js → activity.(_tab)-4S4yPfFw.js} +1 -1
  97. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-DHKuQSmR.js +17 -0
  98. package/codeyam-cli/src/webserver/build/client/assets/{book-open-PttOB2SF.js → book-open-D4IPYH_y.js} +1 -1
  99. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-TJp6ofnp.js → chevron-down-CG65viiV.js} +1 -1
  100. package/codeyam-cli/src/webserver/build/client/assets/{chunk-JZWAC4HX-JE9ZIoBl.js → chunk-JZWAC4HX-DB3aFuEO.js} +9 -9
  101. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-CXhHQYrI.js → circle-check-igfMr5DY.js} +1 -1
  102. package/codeyam-cli/src/webserver/build/client/assets/{copy-6y9ALfGT.js → copy-Coc4o_8c.js} +1 -1
  103. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-Ca9fAY46.js → createLucideIcon-D1zB-pYc.js} +1 -1
  104. package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-C5lqplTC.js → dev.empty-JTAjQ54M.js} +1 -1
  105. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-n38keI1k.js → entity._sha._-B0h9AqE6.js} +2 -2
  106. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-CBoafmVs.js → entity._sha.scenarios._scenarioId.fullscreen-DjLxr2JB.js} +1 -1
  107. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-DGgZjdFg.js → entity._sha_.create-scenario-CtYowLOt.js} +1 -1
  108. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-38yPijoD.js → entity._sha_.edit._scenarioId-PePWg17F.js} +1 -1
  109. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-BSHEfydn.js → entry.client-I-Wo99C_.js} +1 -1
  110. package/codeyam-cli/src/webserver/build/client/assets/{fileTableUtils-DCPhhSMo.js → fileTableUtils-9sMMAiWJ.js} +1 -1
  111. package/codeyam-cli/src/webserver/build/client/assets/{files-0N0YJQv7.js → files-Co65J0s3.js} +1 -1
  112. package/codeyam-cli/src/webserver/build/client/assets/{git-DXnyr8uP.js → git-BdHOxVfg.js} +1 -1
  113. package/codeyam-cli/src/webserver/build/client/assets/globals-CCgBKWy4.css +1 -0
  114. package/codeyam-cli/src/webserver/build/client/assets/{index-ChN9-fAY.js → index-CUM5iXwc.js} +1 -1
  115. package/codeyam-cli/src/webserver/build/client/assets/{index-CcsFv748.js → index-_417gcQW.js} +1 -1
  116. package/codeyam-cli/src/webserver/build/client/assets/labs-BK0C1H1T.js +1 -0
  117. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-CTqLEAGU.js → loader-circle-TzRHMVog.js} +1 -1
  118. package/codeyam-cli/src/webserver/build/client/assets/manifest-390cb8fa.js +1 -0
  119. package/codeyam-cli/src/webserver/build/client/assets/memory-CzZySbBE.js +78 -0
  120. package/codeyam-cli/src/webserver/build/client/assets/{pause-D6vreykR.js → pause-hjzB7t2z.js} +1 -1
  121. package/codeyam-cli/src/webserver/build/client/assets/root-DnbDhvTU.js +62 -0
  122. package/codeyam-cli/src/webserver/build/client/assets/{search-B8VUL8nl.js → search-DcAwD_Ln.js} +1 -1
  123. package/codeyam-cli/src/webserver/build/client/assets/settings-CclxrcPK.js +1 -0
  124. package/codeyam-cli/src/webserver/build/client/assets/{simulations-CPoAg7Zo.js → simulations-DVNJVQgD.js} +1 -1
  125. package/codeyam-cli/src/webserver/build/client/assets/{terminal-BrCP7uQo.js → terminal-DbEAHMbA.js} +1 -1
  126. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-BZz2NjYa.js → triangle-alert-CAD5b1o_.js} +1 -1
  127. package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-DNwUduNu.js → useCustomSizes-BqgrAzs3.js} +1 -1
  128. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-COky1GVF.js → useLastLogLine-DAFqfEDH.js} +1 -1
  129. package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-CpZgwliL.js → useReportContext-DZlYx2c4.js} +1 -1
  130. package/codeyam-cli/src/webserver/build/client/assets/{useToast-Bv9JFvUO.js → useToast-ihdMtlf6.js} +1 -1
  131. package/codeyam-cli/src/webserver/build/server/assets/{index-Cz2RkDCa.js → index-CxaRxKVt.js} +1 -1
  132. package/codeyam-cli/src/webserver/build/server/assets/server-build-D4DT0nM_.js +259 -0
  133. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  134. package/codeyam-cli/src/webserver/build-info.json +5 -5
  135. package/codeyam-cli/templates/{codeyam:debug.md → codeyam-debug.md} +1 -1
  136. package/codeyam-cli/templates/{codeyam:diagnose.md → codeyam-diagnose.md} +1 -1
  137. package/codeyam-cli/templates/codeyam-memory-hook.sh +14 -14
  138. package/codeyam-cli/templates/{codeyam:memory.md → codeyam-memory.md} +16 -23
  139. package/codeyam-cli/templates/{codeyam:new-rule.md → codeyam-new-rule.md} +1 -1
  140. package/codeyam-cli/templates/{codeyam:setup.md → codeyam-setup.md} +1 -1
  141. package/codeyam-cli/templates/{codeyam:sim.md → codeyam-sim.md} +1 -1
  142. package/codeyam-cli/templates/{codeyam:test.md → codeyam-test.md} +1 -1
  143. package/codeyam-cli/templates/{codeyam:verify.md → codeyam-verify.md} +1 -1
  144. package/codeyam-cli/templates/rule-reflection-hook.py +5 -5
  145. package/codeyam-cli/templates/rules-instructions.md +11 -15
  146. package/package.json +8 -8
  147. package/packages/ai/index.js +1 -1
  148. package/packages/ai/index.js.map +1 -1
  149. package/packages/ai/src/lib/analyzeScope.js +14 -0
  150. package/packages/ai/src/lib/analyzeScope.js.map +1 -1
  151. package/packages/ai/src/lib/astScopes/processExpression.js +78 -1
  152. package/packages/ai/src/lib/astScopes/processExpression.js.map +1 -1
  153. package/packages/ai/src/lib/astScopes/sharedPatterns.js +25 -0
  154. package/packages/ai/src/lib/astScopes/sharedPatterns.js.map +1 -1
  155. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +124 -7
  156. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  157. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js +59 -17
  158. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js.map +1 -1
  159. package/packages/ai/src/lib/dataStructureChunking.js +30 -11
  160. package/packages/ai/src/lib/dataStructureChunking.js.map +1 -1
  161. package/packages/ai/src/lib/generateEntityScenarioData.js +22 -3
  162. package/packages/ai/src/lib/generateEntityScenarioData.js.map +1 -1
  163. package/packages/ai/src/lib/generateExecutionFlows.js +97 -2
  164. package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
  165. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js +242 -81
  166. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js.map +1 -1
  167. package/packages/ai/src/lib/mergeJsonTypeDefinitions.js +5 -0
  168. package/packages/ai/src/lib/mergeJsonTypeDefinitions.js.map +1 -1
  169. package/packages/ai/src/lib/promptGenerators/collapseNullableObjects.js +97 -0
  170. package/packages/ai/src/lib/promptGenerators/collapseNullableObjects.js.map +1 -0
  171. package/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.js +17 -2
  172. package/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.js.map +1 -1
  173. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +11 -1
  174. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  175. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js +42 -13
  176. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js.map +1 -1
  177. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +123 -67
  178. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  179. package/packages/utils/src/lib/fs/rsyncCopy.js +93 -2
  180. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  181. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-CN61MOMa.js +0 -11
  182. package/codeyam-cli/src/webserver/build/client/assets/api.labs-survey-l0sNRNKZ.js +0 -1
  183. package/codeyam-cli/src/webserver/build/client/assets/globals-EVn6Z9pz.css +0 -1
  184. package/codeyam-cli/src/webserver/build/client/assets/labs-CmBYA0PH.js +0 -1
  185. package/codeyam-cli/src/webserver/build/client/assets/manifest-aa4ff97b.js +0 -1
  186. package/codeyam-cli/src/webserver/build/client/assets/memory-BSlqS1QA.js +0 -81
  187. package/codeyam-cli/src/webserver/build/client/assets/root-DVAbJY8B.js +0 -62
  188. package/codeyam-cli/src/webserver/build/client/assets/settings-BK-cnzp-.js +0 -1
  189. package/codeyam-cli/src/webserver/build/server/assets/server-build-CUVsWicu.js +0 -260
@@ -11,6 +11,11 @@ export default function mergeJsonTypeDefinitions(
11
11
  if (!typeDef) continue;
12
12
  if (typeof typeDef === 'string') continue;
13
13
  for (const [key, value] of Object.entries(typeDef)) {
14
+ // Preserve _nullable boolean markers set by convertDotNotation
15
+ if (key === '_nullable' && typeof value === 'boolean') {
16
+ mergedDef[key] = value;
17
+ continue;
18
+ }
14
19
  if (typeof value === 'string') {
15
20
  if (!mergedDef[key]) {
16
21
  mergedDef[key] = value;
@@ -0,0 +1,118 @@
1
+ import { JsonTypeDefinition } from '~codeyam/types';
2
+
3
+ /**
4
+ * Check whether all non-`_nullable` children of an object are simple strings.
5
+ * "Leaf-like" nullable objects (all-string children) get collapsed to
6
+ * `"object | null"` so the LLM knows the field can be null.
7
+ * Objects with nested structure (object/array children) keep their shape
8
+ * so the LLM can still generate proper data when the field is truthy.
9
+ */
10
+ function isLeafLikeObject(obj: Record<string, any>): boolean {
11
+ for (const key of Object.keys(obj)) {
12
+ if (key === '_nullable') continue;
13
+ const value = obj[key];
14
+ if (typeof value !== 'string') return false;
15
+ }
16
+ return true;
17
+ }
18
+
19
+ /**
20
+ * Collapse *leaf-like* nullable objects/arrays in the data structure to type strings.
21
+ *
22
+ * When convertDotNotation marks an object or array as `_nullable: true`
23
+ * (meaning the schema had `object | undefined`, `object | null`, etc.),
24
+ * we decide whether to collapse it:
25
+ *
26
+ * - **Leaf-like** (all children are simple strings): collapse to `"object | null"`.
27
+ * Example: `{ _nullable: true, type: "string", path: "string" }` → `"object | null"`
28
+ *
29
+ * - **Rich** (has object/array children): strip `_nullable` and keep the structure
30
+ * so the LLM can generate proper nested data when the field is truthy.
31
+ * Example: `{ _nullable: true, items: [...], name: "string" }` → `{ items: [...], name: "string" }`
32
+ *
33
+ * Non-nullable objects are recursively processed but left as nested structures.
34
+ */
35
+ export default function collapseNullableObjects(
36
+ data: JsonTypeDefinition,
37
+ ): JsonTypeDefinition {
38
+ if (typeof data !== 'object' || data === null) {
39
+ return data;
40
+ }
41
+
42
+ if (Array.isArray(data)) {
43
+ return data.map((item: any) =>
44
+ typeof item === 'object' && item !== null
45
+ ? collapseNullableObjects(item as JsonTypeDefinition)
46
+ : item,
47
+ ) as any as JsonTypeDefinition;
48
+ }
49
+
50
+ const result: JsonTypeDefinition = {};
51
+
52
+ for (const key of Object.keys(data)) {
53
+ // Skip the _nullable marker itself (shouldn't appear at top level, but be safe)
54
+ if (key === '_nullable') continue;
55
+
56
+ const value = data[key];
57
+
58
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
59
+ const hasNullable = (value as any)._nullable === true;
60
+ if (hasNullable && isLeafLikeObject(value as Record<string, any>)) {
61
+ // Leaf-like nullable object → collapse to type string
62
+ const childKeys = Object.keys(value as any).filter(
63
+ (k) => k !== '_nullable',
64
+ );
65
+ console.log(
66
+ `[collapseNullableObjects] collapsed "${key}" → "object | null" (leaf-like, children: [${childKeys.join(', ')}])`,
67
+ );
68
+ result[key] = 'object | null';
69
+ } else if (hasNullable) {
70
+ // Rich nullable object → strip _nullable, keep structure
71
+ const childKeys = Object.keys(value as any).filter(
72
+ (k) => k !== '_nullable',
73
+ );
74
+ console.log(
75
+ `[collapseNullableObjects] kept structure for nullable "${key}" (rich, children: [${childKeys.join(', ')}])`,
76
+ );
77
+ result[key] = collapseNullableObjects(value as JsonTypeDefinition);
78
+ } else {
79
+ // Non-nullable → recurse
80
+ result[key] = collapseNullableObjects(value as JsonTypeDefinition);
81
+ }
82
+ } else if (Array.isArray(value)) {
83
+ const hasNullable = (value as any)._nullable === true;
84
+ if (
85
+ hasNullable &&
86
+ value.length <= 1 &&
87
+ value.every((v) => typeof v === 'string')
88
+ ) {
89
+ // Leaf-like nullable array → collapse to type string
90
+ console.log(
91
+ `[collapseNullableObjects] collapsed "${key}" → "array | null" (leaf-like)`,
92
+ );
93
+ result[key] = 'array | null';
94
+ } else if (hasNullable) {
95
+ // Rich nullable array → strip _nullable, keep structure
96
+ console.log(
97
+ `[collapseNullableObjects] kept structure for nullable array "${key}" (${value.length} elements)`,
98
+ );
99
+ result[key] = value.map((item) =>
100
+ typeof item === 'object' && item !== null
101
+ ? collapseNullableObjects(item as JsonTypeDefinition)
102
+ : item,
103
+ ) as JsonTypeDefinition[];
104
+ } else {
105
+ // Non-nullable → recurse elements
106
+ result[key] = value.map((item) =>
107
+ typeof item === 'object' && item !== null
108
+ ? collapseNullableObjects(item as JsonTypeDefinition)
109
+ : item,
110
+ ) as JsonTypeDefinition[];
111
+ }
112
+ } else {
113
+ result[key] = value;
114
+ }
115
+ }
116
+
117
+ return result;
118
+ }
@@ -7,6 +7,7 @@ import {
7
7
  } from '~codeyam/types';
8
8
  import noErrorAttributes from './noErrorAttributes';
9
9
  import simplifyKeysForLLM from './simplifyKeysForLLM';
10
+ import collapseNullableObjects from './collapseNullableObjects';
10
11
 
11
12
  /**
12
13
  * Sort object keys by the size of their JSON-stringified values (smallest first).
@@ -99,11 +100,17 @@ ${JSON.stringify(defaultScenarioData.data, null, 2)}
99
100
  // while the reconcileMockDataKeys function will still match the simplified response back to original keys.
100
101
  // Then sort keys by size (smallest first) so small keys appear before large keys,
101
102
  // increasing the chance the LLM generates all keys for large schemas.
102
- const cleanedDataForMocks = structure.dataForMocks
103
- ? simplifyKeysForLLM(
104
- noErrorAttributes(structure.dataForMocks) as JsonTypeDefinition,
105
- )
103
+
104
+ const afterNoError = structure.dataForMocks
105
+ ? (noErrorAttributes(structure.dataForMocks) as JsonTypeDefinition)
106
+ : null;
107
+
108
+ const afterSimplify = afterNoError ? simplifyKeysForLLM(afterNoError) : null;
109
+
110
+ const cleanedDataForMocks = afterSimplify
111
+ ? collapseNullableObjects(afterSimplify)
106
112
  : null;
113
+
107
114
  const sortedDataForMocks =
108
115
  cleanedDataForMocks &&
109
116
  typeof cleanedDataForMocks === 'object' &&
@@ -111,6 +118,19 @@ ${JSON.stringify(defaultScenarioData.data, null, 2)}
111
118
  ? sortKeysBySize(cleanedDataForMocks as Record<string, any>)
112
119
  : cleanedDataForMocks;
113
120
 
121
+ // Log nullable-related content in final prompt JSON
122
+ if (sortedDataForMocks) {
123
+ const finalJson = JSON.stringify(sortedDataForMocks, null, 2);
124
+ const hasNullableMarker = finalJson.includes('_nullable');
125
+ const nullCollapsed = (finalJson.match(/"object \| null"/g) || []).length;
126
+ const arrayCollapsed = (finalJson.match(/"array \| null"/g) || []).length;
127
+ if (hasNullableMarker || nullCollapsed > 0 || arrayCollapsed > 0) {
128
+ console.log(
129
+ `[generateEntityScenarioDataGenerator] final prompt: ${nullCollapsed} "object | null", ${arrayCollapsed} "array | null"${hasNullableMarker ? ', WARNING: _nullable still present!' : ''}`,
130
+ );
131
+ }
132
+ }
133
+
114
134
  // When mockData has already been generated via chunks, skip it in the main call
115
135
  const mockDataSection = options?.mockDataAlreadyGenerated
116
136
  ? '## mockData Structure\nmockData has been pre-generated—return `{}` for mockData.'
@@ -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"}