@codeyam/codeyam-cli 0.1.0-staging.79ef713 → 0.1.0-staging.7c30edc

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 (195) hide show
  1. package/analyzer-template/.build-info.json +7 -7
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/packages/ai/src/lib/astScopes/processExpression.ts +101 -0
  4. package/analyzer-template/packages/ai/src/lib/astScopes/sharedPatterns.ts +28 -0
  5. package/analyzer-template/packages/ai/src/lib/astScopes/types.ts +6 -0
  6. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +176 -8
  7. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.ts +46 -6
  8. package/analyzer-template/packages/ai/src/lib/dataStructureChunking.ts +33 -15
  9. package/analyzer-template/packages/ai/src/lib/generateEntityScenarioData.ts +32 -5
  10. package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +38 -2
  11. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionals.ts +359 -142
  12. package/analyzer-template/packages/ai/src/lib/mergeJsonTypeDefinitions.ts +5 -0
  13. package/analyzer-template/packages/ai/src/lib/promptGenerators/collapseNullableObjects.ts +118 -0
  14. package/analyzer-template/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.ts +24 -4
  15. package/analyzer-template/packages/database/src/lib/analysisBranchToDb.ts +1 -1
  16. package/analyzer-template/packages/database/src/lib/analysisToDb.ts +1 -1
  17. package/analyzer-template/packages/database/src/lib/branchToDb.ts +1 -1
  18. package/analyzer-template/packages/database/src/lib/commitBranchToDb.ts +1 -1
  19. package/analyzer-template/packages/database/src/lib/commitToDb.ts +1 -1
  20. package/analyzer-template/packages/database/src/lib/fileToDb.ts +1 -1
  21. package/analyzer-template/packages/database/src/lib/kysely/db.ts +6 -0
  22. package/analyzer-template/packages/database/src/lib/kysely/tables/labsRequestsTable.ts +52 -0
  23. package/analyzer-template/packages/database/src/lib/projectToDb.ts +1 -1
  24. package/analyzer-template/packages/database/src/lib/saveFiles.ts +1 -1
  25. package/analyzer-template/packages/database/src/lib/scenarioToDb.ts +1 -1
  26. package/analyzer-template/packages/database/src/lib/userScenarioToDb.ts +1 -1
  27. package/analyzer-template/packages/github/dist/database/src/lib/analysisBranchToDb.js +1 -1
  28. package/analyzer-template/packages/github/dist/database/src/lib/analysisBranchToDb.js.map +1 -1
  29. package/analyzer-template/packages/github/dist/database/src/lib/analysisToDb.js +1 -1
  30. package/analyzer-template/packages/github/dist/database/src/lib/analysisToDb.js.map +1 -1
  31. package/analyzer-template/packages/github/dist/database/src/lib/branchToDb.js +1 -1
  32. package/analyzer-template/packages/github/dist/database/src/lib/branchToDb.js.map +1 -1
  33. package/analyzer-template/packages/github/dist/database/src/lib/commitBranchToDb.js +1 -1
  34. package/analyzer-template/packages/github/dist/database/src/lib/commitBranchToDb.js.map +1 -1
  35. package/analyzer-template/packages/github/dist/database/src/lib/commitToDb.js +1 -1
  36. package/analyzer-template/packages/github/dist/database/src/lib/commitToDb.js.map +1 -1
  37. package/analyzer-template/packages/github/dist/database/src/lib/fileToDb.js +1 -1
  38. package/analyzer-template/packages/github/dist/database/src/lib/fileToDb.js.map +1 -1
  39. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.d.ts +2 -0
  40. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.d.ts.map +1 -1
  41. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.js +3 -0
  42. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.js.map +1 -1
  43. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/labsRequestsTable.d.ts +23 -0
  44. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/labsRequestsTable.d.ts.map +1 -0
  45. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/labsRequestsTable.js +35 -0
  46. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/labsRequestsTable.js.map +1 -0
  47. package/analyzer-template/packages/github/dist/database/src/lib/projectToDb.js +1 -1
  48. package/analyzer-template/packages/github/dist/database/src/lib/projectToDb.js.map +1 -1
  49. package/analyzer-template/packages/github/dist/database/src/lib/saveFiles.js +1 -1
  50. package/analyzer-template/packages/github/dist/database/src/lib/saveFiles.js.map +1 -1
  51. package/analyzer-template/packages/github/dist/database/src/lib/scenarioToDb.js +1 -1
  52. package/analyzer-template/packages/github/dist/database/src/lib/scenarioToDb.js.map +1 -1
  53. package/analyzer-template/packages/github/dist/types/src/types/ProjectMetadata.d.ts +4 -0
  54. package/analyzer-template/packages/github/dist/types/src/types/ProjectMetadata.d.ts.map +1 -1
  55. package/analyzer-template/packages/types/src/types/ProjectMetadata.ts +7 -1
  56. package/analyzer-template/packages/utils/dist/types/src/types/ProjectMetadata.d.ts +4 -0
  57. package/analyzer-template/packages/utils/dist/types/src/types/ProjectMetadata.d.ts.map +1 -1
  58. package/background/src/lib/local/createLocalAnalyzer.js +1 -1
  59. package/background/src/lib/local/createLocalAnalyzer.js.map +1 -1
  60. package/codeyam-cli/src/cli.js +2 -0
  61. package/codeyam-cli/src/cli.js.map +1 -1
  62. package/codeyam-cli/src/codeyam-cli.js +18 -2
  63. package/codeyam-cli/src/codeyam-cli.js.map +1 -1
  64. package/codeyam-cli/src/commands/analyze.js +2 -0
  65. package/codeyam-cli/src/commands/analyze.js.map +1 -1
  66. package/codeyam-cli/src/commands/baseline.js +2 -0
  67. package/codeyam-cli/src/commands/baseline.js.map +1 -1
  68. package/codeyam-cli/src/commands/debug.js +2 -0
  69. package/codeyam-cli/src/commands/debug.js.map +1 -1
  70. package/codeyam-cli/src/commands/default.js +25 -19
  71. package/codeyam-cli/src/commands/default.js.map +1 -1
  72. package/codeyam-cli/src/commands/detect-universal-mocks.js +2 -0
  73. package/codeyam-cli/src/commands/detect-universal-mocks.js.map +1 -1
  74. package/codeyam-cli/src/commands/init.js +49 -257
  75. package/codeyam-cli/src/commands/init.js.map +1 -1
  76. package/codeyam-cli/src/commands/memory.js +9 -9
  77. package/codeyam-cli/src/commands/memory.js.map +1 -1
  78. package/codeyam-cli/src/commands/recapture.js +2 -0
  79. package/codeyam-cli/src/commands/recapture.js.map +1 -1
  80. package/codeyam-cli/src/commands/setup-sandbox.js +2 -0
  81. package/codeyam-cli/src/commands/setup-sandbox.js.map +1 -1
  82. package/codeyam-cli/src/commands/setup-simulations.js +284 -0
  83. package/codeyam-cli/src/commands/setup-simulations.js.map +1 -0
  84. package/codeyam-cli/src/commands/test-startup.js +2 -0
  85. package/codeyam-cli/src/commands/test-startup.js.map +1 -1
  86. package/codeyam-cli/src/commands/verify.js +2 -0
  87. package/codeyam-cli/src/commands/verify.js.map +1 -1
  88. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +127 -85
  89. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
  90. package/codeyam-cli/src/utils/analyzer.js +7 -0
  91. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  92. package/codeyam-cli/src/utils/install-skills.js +41 -61
  93. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  94. package/codeyam-cli/src/utils/labsAutoCheck.js +48 -0
  95. package/codeyam-cli/src/utils/labsAutoCheck.js.map +1 -0
  96. package/codeyam-cli/src/utils/progress.js +7 -0
  97. package/codeyam-cli/src/utils/progress.js.map +1 -1
  98. package/codeyam-cli/src/utils/requireSimulations.js +10 -0
  99. package/codeyam-cli/src/utils/requireSimulations.js.map +1 -0
  100. package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js +4 -4
  101. package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js.map +1 -1
  102. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js +95 -2
  103. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js.map +1 -1
  104. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js +4 -4
  105. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js.map +1 -1
  106. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js +3 -3
  107. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js.map +1 -1
  108. package/codeyam-cli/src/utils/rules/__tests__/ruleState.test.js +23 -23
  109. package/codeyam-cli/src/utils/rules/__tests__/ruleState.test.js.map +1 -1
  110. package/codeyam-cli/src/utils/rules/parser.js +5 -0
  111. package/codeyam-cli/src/utils/rules/parser.js.map +1 -1
  112. package/codeyam-cli/src/utils/rules/ruleState.js +10 -10
  113. package/codeyam-cli/src/utils/rules/ruleState.js.map +1 -1
  114. package/codeyam-cli/src/utils/rules/staleness.js +6 -6
  115. package/codeyam-cli/src/utils/rules/staleness.js.map +1 -1
  116. package/codeyam-cli/src/utils/serverState.js +37 -10
  117. package/codeyam-cli/src/utils/serverState.js.map +1 -1
  118. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +19 -42
  119. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
  120. package/codeyam-cli/src/webserver/app/lib/database.js +14 -3
  121. package/codeyam-cli/src/webserver/app/lib/database.js.map +1 -1
  122. package/codeyam-cli/src/webserver/backgroundServer.js +31 -0
  123. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  124. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-CN61MOMa.js +11 -0
  125. package/codeyam-cli/src/webserver/build/client/assets/api.labs-survey-l0sNRNKZ.js +1 -0
  126. package/codeyam-cli/src/webserver/build/client/assets/api.labs-unlock-l0sNRNKZ.js +1 -0
  127. package/codeyam-cli/src/webserver/build/client/assets/globals-CuCsBc3b.css +1 -0
  128. package/codeyam-cli/src/webserver/build/client/assets/labs-CB3MGcys.js +1 -0
  129. package/codeyam-cli/src/webserver/build/client/assets/{manifest-87319d0f.js → manifest-de6ccaf4.js} +1 -1
  130. package/codeyam-cli/src/webserver/build/client/assets/memory-DCA-kLYt.js +81 -0
  131. package/codeyam-cli/src/webserver/build/client/assets/root-F7e6dvys.js +62 -0
  132. package/codeyam-cli/src/webserver/build/client/assets/settings-BejnUJ6R.js +1 -0
  133. package/codeyam-cli/src/webserver/build/server/assets/{index-9ox9LcrG.js → index-CFKHuovO.js} +1 -1
  134. package/codeyam-cli/src/webserver/build/server/assets/server-build-BQe9Dh4p.js +260 -0
  135. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  136. package/codeyam-cli/src/webserver/build-info.json +5 -5
  137. package/codeyam-cli/templates/codeyam-memory-hook.sh +14 -14
  138. package/codeyam-cli/templates/codeyam:diagnose.md +193 -515
  139. package/codeyam-cli/templates/codeyam:memory.md +9 -21
  140. package/codeyam-cli/templates/codeyam:setup.md +12 -0
  141. package/codeyam-cli/templates/rule-reflection-hook.py +64 -27
  142. package/codeyam-cli/templates/rules-instructions.md +50 -41
  143. package/package.json +1 -1
  144. package/packages/ai/src/lib/astScopes/processExpression.js +78 -1
  145. package/packages/ai/src/lib/astScopes/processExpression.js.map +1 -1
  146. package/packages/ai/src/lib/astScopes/sharedPatterns.js +25 -0
  147. package/packages/ai/src/lib/astScopes/sharedPatterns.js.map +1 -1
  148. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +128 -7
  149. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  150. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js +40 -6
  151. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js.map +1 -1
  152. package/packages/ai/src/lib/dataStructureChunking.js +26 -11
  153. package/packages/ai/src/lib/dataStructureChunking.js.map +1 -1
  154. package/packages/ai/src/lib/generateEntityScenarioData.js +22 -3
  155. package/packages/ai/src/lib/generateEntityScenarioData.js.map +1 -1
  156. package/packages/ai/src/lib/generateExecutionFlows.js +16 -2
  157. package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
  158. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js +242 -81
  159. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js.map +1 -1
  160. package/packages/ai/src/lib/mergeJsonTypeDefinitions.js +5 -0
  161. package/packages/ai/src/lib/mergeJsonTypeDefinitions.js.map +1 -1
  162. package/packages/ai/src/lib/promptGenerators/collapseNullableObjects.js +97 -0
  163. package/packages/ai/src/lib/promptGenerators/collapseNullableObjects.js.map +1 -0
  164. package/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.js +17 -2
  165. package/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.js.map +1 -1
  166. package/packages/database/src/lib/analysisBranchToDb.js +1 -1
  167. package/packages/database/src/lib/analysisBranchToDb.js.map +1 -1
  168. package/packages/database/src/lib/analysisToDb.js +1 -1
  169. package/packages/database/src/lib/analysisToDb.js.map +1 -1
  170. package/packages/database/src/lib/branchToDb.js +1 -1
  171. package/packages/database/src/lib/branchToDb.js.map +1 -1
  172. package/packages/database/src/lib/commitBranchToDb.js +1 -1
  173. package/packages/database/src/lib/commitBranchToDb.js.map +1 -1
  174. package/packages/database/src/lib/commitToDb.js +1 -1
  175. package/packages/database/src/lib/commitToDb.js.map +1 -1
  176. package/packages/database/src/lib/fileToDb.js +1 -1
  177. package/packages/database/src/lib/fileToDb.js.map +1 -1
  178. package/packages/database/src/lib/kysely/db.js +3 -0
  179. package/packages/database/src/lib/kysely/db.js.map +1 -1
  180. package/packages/database/src/lib/kysely/tables/labsRequestsTable.js +35 -0
  181. package/packages/database/src/lib/kysely/tables/labsRequestsTable.js.map +1 -0
  182. package/packages/database/src/lib/projectToDb.js +1 -1
  183. package/packages/database/src/lib/projectToDb.js.map +1 -1
  184. package/packages/database/src/lib/saveFiles.js +1 -1
  185. package/packages/database/src/lib/saveFiles.js.map +1 -1
  186. package/packages/database/src/lib/scenarioToDb.js +1 -1
  187. package/packages/database/src/lib/scenarioToDb.js.map +1 -1
  188. package/scripts/finalize-analyzer.cjs +8 -76
  189. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-DfKzxuoe.js +0 -11
  190. package/codeyam-cli/src/webserver/build/client/assets/globals-Bh6jH0cL.css +0 -1
  191. package/codeyam-cli/src/webserver/build/client/assets/labs-CdVUfvji.js +0 -1
  192. package/codeyam-cli/src/webserver/build/client/assets/memory-CPIDnDEj.js +0 -76
  193. package/codeyam-cli/src/webserver/build/client/assets/root-D6oziHts.js +0 -62
  194. package/codeyam-cli/src/webserver/build/client/assets/settings-eBI36Yv5.js +0 -1
  195. package/codeyam-cli/src/webserver/build/server/assets/server-build-Cq5Vqcob.js +0 -260
@@ -1,10 +1,10 @@
1
1
  {
2
- "buildTimestamp": "2026-02-09T23:40:32.703Z",
3
- "buildTime": 1770680432703,
4
- "gitCommit": "79ef713db8aedbd08bf0e42c7aa8b6f99117bbdb",
2
+ "buildTimestamp": "2026-02-12T15:28:10.920Z",
3
+ "buildTime": 1770910090920,
4
+ "gitCommit": "7c30edc8b3c3c3d584502dd7762988219e5dabbb",
5
5
  "nodeVersion": "v20.20.0",
6
- "contentHash": "aaa0ef29735f956b90bfc57d268b877db2ba758456e586a28cc3af7e44ea35f1",
7
- "buildNumber": 598,
8
- "semanticVersion": "0.1.598",
9
- "version": "0.1.598 (2026-02-09T23:40+aaa0ef2)"
6
+ "contentHash": "1614bacb94f772727a12f702e776ac1fe8e99792dd6cf286ea389f7c8026f245",
7
+ "buildNumber": 617,
8
+ "semanticVersion": "0.1.617",
9
+ "version": "0.1.617 (2026-02-12T15:28+1614bac)"
10
10
  }
@@ -1,7 +1,7 @@
1
1
 
2
- [2/9/2026, 11:40:32 PM] > codeyam-combo@1.0.0 mergeDependencies
3
- [2/9/2026, 11:40:32 PM] > node ./scripts/mergePackageJsonFiles.cjs
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
4
4
 
5
5
 
6
- [2/9/2026, 11:40:32 PM] Merged dependencies into root package.json
6
+ [2/12/2026, 3:28:10 PM] Merged dependencies into root package.json
7
7
 
@@ -9,6 +9,7 @@ import { StructuredPath } from './paths';
9
9
  import { nodeToSource } from './nodeToSource';
10
10
  import { methodRegistry, ArrayPushSemantics } from './methodSemantics';
11
11
  import {
12
+ getComparisonOperatorString,
12
13
  isArithmeticOperator,
13
14
  isAssignmentOperator,
14
15
  isBitwiseCompoundOperator,
@@ -25,6 +26,74 @@ import {
25
26
  } from './conditionalEffectsExtractor';
26
27
  import { detectArrayDerivedPattern } from './arrayDerivationDetector';
27
28
 
29
+ /**
30
+ * Recursively extracts root variable names from an expression AST node.
31
+ * Used to identify which variables flow into JSX expression children,
32
+ * so we can link them to the return value schema.
33
+ *
34
+ * Examples:
35
+ * - `filteredTopPaths.map(...)` → ['filteredTopPaths']
36
+ * - `a && b` → ['a', 'b']
37
+ * - `condition ? x : y` → ['condition', 'x', 'y']
38
+ */
39
+ function extractRootVariableNames(node: ts.Expression): string[] {
40
+ const ignoredIdentifiers = new Set([
41
+ 'undefined',
42
+ 'null',
43
+ 'true',
44
+ 'false',
45
+ 'NaN',
46
+ 'Infinity',
47
+ ]);
48
+
49
+ if (ts.isIdentifier(node)) {
50
+ const name = node.text;
51
+ return ignoredIdentifiers.has(name) ? [] : [name];
52
+ }
53
+
54
+ if (ts.isPropertyAccessExpression(node)) {
55
+ return extractRootVariableNames(node.expression);
56
+ }
57
+
58
+ if (ts.isCallExpression(node)) {
59
+ return extractRootVariableNames(node.expression);
60
+ }
61
+
62
+ if (ts.isBinaryExpression(node)) {
63
+ return [
64
+ ...extractRootVariableNames(node.left),
65
+ ...extractRootVariableNames(node.right),
66
+ ];
67
+ }
68
+
69
+ if (ts.isPrefixUnaryExpression(node)) {
70
+ return extractRootVariableNames(node.operand);
71
+ }
72
+
73
+ if (ts.isConditionalExpression(node)) {
74
+ return [
75
+ ...extractRootVariableNames(node.condition),
76
+ ...extractRootVariableNames(node.whenTrue),
77
+ ...extractRootVariableNames(node.whenFalse),
78
+ ];
79
+ }
80
+
81
+ if (ts.isParenthesizedExpression(node)) {
82
+ return extractRootVariableNames(node.expression);
83
+ }
84
+
85
+ // Stop recursion at JSX elements and other terminal nodes
86
+ if (
87
+ ts.isJsxElement(node) ||
88
+ ts.isJsxFragment(node) ||
89
+ ts.isJsxSelfClosingElement(node)
90
+ ) {
91
+ return [];
92
+ }
93
+
94
+ return [];
95
+ }
96
+
28
97
  /**
29
98
  * Checks if a JSX element has props that reference variables from the parent scope.
30
99
  * This is used to detect unconditionally-rendered children that should have their
@@ -1294,6 +1363,11 @@ export function extractConditionalUsage(
1294
1363
  return literalValue;
1295
1364
  };
1296
1365
 
1366
+ // Get the comparison operator string for the compound condition
1367
+ const comparisonOperator = getComparisonOperatorString(
1368
+ unwrapped.operatorToken.kind,
1369
+ );
1370
+
1297
1371
  // Helper to add a condition
1298
1372
  const addCondition = (
1299
1373
  path: string,
@@ -1338,6 +1412,7 @@ export function extractConditionalUsage(
1338
1412
  comparedValues,
1339
1413
  isNegated,
1340
1414
  requiredValue,
1415
+ ...(comparisonOperator && { comparisonOperator }),
1341
1416
  ...(chainInfo.currentOrGroupId && {
1342
1417
  orGroupId: chainInfo.currentOrGroupId,
1343
1418
  }),
@@ -3334,6 +3409,19 @@ export function processExpression({
3334
3409
  for (const child of unwrappedNode.children) {
3335
3410
  // Process expressions in JSX children: <div>{expr}</div>
3336
3411
  if (ts.isJsxExpression(child) && child.expression) {
3412
+ // When processing return value JSX, link root variables to return value schema
3413
+ if (targetPath && targetPath.base !== '') {
3414
+ const varNames = [
3415
+ ...new Set(extractRootVariableNames(child.expression)),
3416
+ ];
3417
+ for (const varName of varNames) {
3418
+ context.addEquivalence(
3419
+ targetPath.withProperty(varName),
3420
+ StructuredPath.fromBase(varName),
3421
+ );
3422
+ }
3423
+ }
3424
+
3337
3425
  // Process the expression with StructuredPath.empty() as targetPath
3338
3426
  // to trigger type registration without imposing prefix
3339
3427
  processExpression({
@@ -3368,6 +3456,19 @@ export function processExpression({
3368
3456
  for (const child of unwrappedNode.children) {
3369
3457
  // Process expressions in JSX children: <>{expr}</>
3370
3458
  if (ts.isJsxExpression(child) && child.expression) {
3459
+ // When processing return value JSX, link root variables to return value schema
3460
+ if (targetPath && targetPath.base !== '') {
3461
+ const varNames = [
3462
+ ...new Set(extractRootVariableNames(child.expression)),
3463
+ ];
3464
+ for (const varName of varNames) {
3465
+ context.addEquivalence(
3466
+ targetPath.withProperty(varName),
3467
+ StructuredPath.fromBase(varName),
3468
+ );
3469
+ }
3470
+ }
3471
+
3371
3472
  // Process the expression to extract structure
3372
3473
  processExpression({
3373
3474
  node: child.expression,
@@ -71,6 +71,34 @@ export function isComparisonOperator(kind: ts.SyntaxKind): boolean {
71
71
  );
72
72
  }
73
73
 
74
+ /**
75
+ * Returns the string representation of a comparison operator token.
76
+ */
77
+ export function getComparisonOperatorString(
78
+ kind: ts.SyntaxKind,
79
+ ): string | undefined {
80
+ switch (kind) {
81
+ case ts.SyntaxKind.EqualsEqualsToken:
82
+ return '==';
83
+ case ts.SyntaxKind.EqualsEqualsEqualsToken:
84
+ return '===';
85
+ case ts.SyntaxKind.ExclamationEqualsToken:
86
+ return '!=';
87
+ case ts.SyntaxKind.ExclamationEqualsEqualsToken:
88
+ return '!==';
89
+ case ts.SyntaxKind.LessThanToken:
90
+ return '<';
91
+ case ts.SyntaxKind.LessThanEqualsToken:
92
+ return '<=';
93
+ case ts.SyntaxKind.GreaterThanToken:
94
+ return '>';
95
+ case ts.SyntaxKind.GreaterThanEqualsToken:
96
+ return '>=';
97
+ default:
98
+ return undefined;
99
+ }
100
+ }
101
+
74
102
  /**
75
103
  * Checks if an operator is an arithmetic operator
76
104
  */
@@ -433,6 +433,12 @@ export interface CompoundConditional {
433
433
  isNegated: boolean;
434
434
  /** Required value for this condition to be true */
435
435
  requiredValue?: string | boolean;
436
+ /**
437
+ * The comparison operator used (e.g., '>', '<', '>=', '<=', '===', '!==').
438
+ * Preserves the original operator so flow generation can distinguish
439
+ * `length > 0` from `length === 0`.
440
+ */
441
+ comparisonOperator?: string;
436
442
  /**
437
443
  * When conditions are part of an OR expression within an && chain,
438
444
  * they share the same orGroupId. Conditions with the same orGroupId
@@ -397,6 +397,7 @@ const SILENTLY_IGNORED_EQUIVALENCY_REASONS = new Set([
397
397
  'transformed non-object function equivalency - implicit parent equivalency - rerouted via useCallback',
398
398
  'transformed non-object function equivalency - Array.from() equivalency',
399
399
  'Spread operator equivalency key update: Explicit array deconstruction equivalency value',
400
+ // 'transformed non-object function equivalency - Explicit array deconstruction equivalency value',
400
401
  ]);
401
402
 
402
403
  export class ScopeDataStructure {
@@ -2797,6 +2798,8 @@ export class ScopeDataStructure {
2797
2798
  usageEquivalency.scopeNodeName,
2798
2799
  ) as ScopeNode;
2799
2800
 
2801
+ if (!usageScopeNode) continue;
2802
+
2800
2803
  // Guard against infinite recursion by tracking which paths we've already
2801
2804
  // added from addComplexSourcePathVariables
2802
2805
  if (
@@ -2876,6 +2879,8 @@ export class ScopeDataStructure {
2876
2879
  usageEquivalency.scopeNodeName,
2877
2880
  ) as ScopeNode;
2878
2881
 
2882
+ if (!usageScopeNode) continue;
2883
+
2879
2884
  // This is put in place to avoid propagating array functions like 'filter' through complex equivalencies
2880
2885
  // but may cause problems if the funtion call is not on a known object (e.g. string or array)
2881
2886
  if (
@@ -3898,25 +3903,116 @@ export class ScopeDataStructure {
3898
3903
  return [source];
3899
3904
  };
3900
3905
 
3901
- return entries.reduce(
3902
- (acc, entry) => {
3903
- if (entry.sourceCandidates.length === 0) return acc;
3906
+ const acc = entries.reduce(
3907
+ (result, entry) => {
3908
+ if (entry.sourceCandidates.length === 0) return result;
3904
3909
  const usages = entry.usages.filter(usageMatchesScope);
3905
3910
  for (const usage of usages) {
3906
- acc[usage.schemaPath] ||= [];
3911
+ result[usage.schemaPath] ||= [];
3907
3912
  // Resolve each source candidate through the equivalency chain
3908
3913
  for (const source of entry.sourceCandidates) {
3909
3914
  const resolvedSources = resolveToSignature(source, new Set());
3910
- acc[usage.schemaPath].push(...resolvedSources);
3915
+ result[usage.schemaPath].push(...resolvedSources);
3911
3916
  }
3912
3917
  }
3913
- return acc;
3918
+ return result;
3914
3919
  },
3915
3920
  {} as Record<
3916
3921
  string,
3917
3922
  Pick<ScopeVariable, 'scopeNodeName' | 'schemaPath'>[]
3918
3923
  >,
3919
3924
  );
3925
+
3926
+ // Post-processing: enrich useState-backed sources with co-located external
3927
+ // function calls. When a useState value resolves to a setter variable that
3928
+ // lives in the same scope as a fetch/API call, that fetch is a data source.
3929
+ this.enrichUseStateSourcesWithCoLocatedCalls(acc);
3930
+
3931
+ return acc;
3932
+ }
3933
+
3934
+ /**
3935
+ * For each source that ends at a useState path, check if the setter was called
3936
+ * from a scope that also contains external function calls (like fetch).
3937
+ * If so, add those external calls as additional source candidates.
3938
+ */
3939
+ private enrichUseStateSourcesWithCoLocatedCalls(
3940
+ acc: Record<string, Pick<ScopeVariable, 'scopeNodeName' | 'schemaPath'>[]>,
3941
+ ) {
3942
+ const rootScopeName = this.scopeTreeManager.getRootName();
3943
+ const rootScope = this.scopeNodes[rootScopeName];
3944
+ if (!rootScope) return;
3945
+
3946
+ // Collect all descendants for each scope node
3947
+ const getAllDescendants = (
3948
+ node: import('./helpers/ScopeTreeManager').ScopeTreeNode,
3949
+ ): Set<string> => {
3950
+ const names = new Set<string>([node.name]);
3951
+ for (const child of node.children) {
3952
+ for (const name of getAllDescendants(child)) {
3953
+ names.add(name);
3954
+ }
3955
+ }
3956
+ return names;
3957
+ };
3958
+
3959
+ for (const [usagePath, sources] of Object.entries(acc)) {
3960
+ const additionalSources: Pick<
3961
+ ScopeVariable,
3962
+ 'scopeNodeName' | 'schemaPath'
3963
+ >[] = [];
3964
+
3965
+ for (const source of sources) {
3966
+ // Check if this source is a useState-related terminal path
3967
+ // (e.g., useState(X).functionCallReturnValue[1] or useState(X).signature[0])
3968
+ if (!source.schemaPath.match(/^useState\([^)]*\)\./)) continue;
3969
+
3970
+ // Find the useState call from the source path
3971
+ const useStateCallMatch = source.schemaPath.match(
3972
+ /^(useState\([^)]*\))\./,
3973
+ );
3974
+ if (!useStateCallMatch) continue;
3975
+ const useStateCall = useStateCallMatch[1];
3976
+
3977
+ // Look in the root scope for the useState value equivalency
3978
+ // which tells us where the setter was called from
3979
+ const valuePath = `${useStateCall}.functionCallReturnValue[0]`;
3980
+ const valueEquivs = rootScope.equivalencies[valuePath];
3981
+ if (!valueEquivs) continue;
3982
+
3983
+ for (const equiv of valueEquivs) {
3984
+ // Find the scope where the setter was called
3985
+ const setterScopeName = equiv.scopeNodeName;
3986
+ const setterScopeTree =
3987
+ this.scopeTreeManager.findNode(setterScopeName);
3988
+ if (!setterScopeTree) continue;
3989
+
3990
+ // Get all descendant scope names from the setter scope
3991
+ const relatedScopes = getAllDescendants(setterScopeTree);
3992
+
3993
+ // Find external function calls in those scopes whose return values
3994
+ // are actually consumed (assigned to a variable). This excludes
3995
+ // fire-and-forget calls like analytics.track() or console.log().
3996
+ const coLocatedCalls = this.externalFunctionCalls.filter(
3997
+ (efc) =>
3998
+ relatedScopes.has(efc.callScope) &&
3999
+ efc.receivingVariableNames &&
4000
+ efc.receivingVariableNames.length > 0,
4001
+ );
4002
+
4003
+ for (const call of coLocatedCalls) {
4004
+ additionalSources.push({
4005
+ scopeNodeName: call.callScope,
4006
+ schemaPath: `${call.callSignature}.functionCallReturnValue`,
4007
+ });
4008
+ }
4009
+ }
4010
+ }
4011
+
4012
+ if (additionalSources.length > 0) {
4013
+ acc[usagePath].push(...additionalSources);
4014
+ }
4015
+ }
3920
4016
  }
3921
4017
 
3922
4018
  getUsageEquivalencies(functionName?: string) {
@@ -4423,6 +4519,15 @@ export class ScopeDataStructure {
4423
4519
  !equivalentValue.schemaPath.startsWith('signature[') && // not a signature path
4424
4520
  !equivalentValue.schemaPath.endsWith('.functionCallReturnValue') // not already handled above
4425
4521
  ) {
4522
+ // Skip bare "returnValue" from child scopes — this is the child's return value,
4523
+ // not a meaningful data source path in the parent scope
4524
+ if (
4525
+ equivalentValue.schemaPath === 'returnValue' &&
4526
+ equivalentValue.scopeNodeName !==
4527
+ this.scopeTreeManager.getRootName()
4528
+ ) {
4529
+ continue;
4530
+ }
4426
4531
  // Add equivalency (will accumulate if multiple values for OR expressions)
4427
4532
  addEquivalency(path, equivalentValue.schemaPath);
4428
4533
  }
@@ -5119,6 +5224,10 @@ export class ScopeDataStructure {
5119
5224
  getEnrichedConditionalUsages(): Record<string, EnrichedConditionalUsage[]> {
5120
5225
  const enriched: Record<string, EnrichedConditionalUsage[]> = {};
5121
5226
 
5227
+ console.log(
5228
+ `[getEnrichedConditionalUsages] Processing ${Object.keys(this.rawConditionalUsages).length} conditional paths: [${Object.keys(this.rawConditionalUsages).join(', ')}]`,
5229
+ );
5230
+
5122
5231
  for (const [path, usages] of Object.entries(this.rawConditionalUsages)) {
5123
5232
  // Try to trace this path back to a data source
5124
5233
  // First, try the root scope
@@ -5127,10 +5236,69 @@ export class ScopeDataStructure {
5127
5236
 
5128
5237
  let sourceDataPath: string | undefined;
5129
5238
  if (explanation.source) {
5130
- // Build the full data path: scopeName.path
5131
- sourceDataPath = `${explanation.source.scope}.${explanation.source.path}`;
5239
+ const { scope, path: sourcePath } = explanation.source;
5240
+
5241
+ // Build initial path — avoid redundant prefix when path already contains the scope call
5242
+ let fullPath: string;
5243
+ if (sourcePath.startsWith(`${scope}(`)) {
5244
+ fullPath = sourcePath;
5245
+ } else {
5246
+ fullPath = `${scope}.${sourcePath}`;
5247
+ }
5248
+
5249
+ sourceDataPath = fullPath;
5250
+ console.log(
5251
+ `[getEnrichedConditionalUsages] "${path}" explainPath → scope="${scope}", sourcePath="${sourcePath}" → sourceDataPath="${sourceDataPath}"`,
5252
+ );
5253
+ } else {
5254
+ console.log(
5255
+ `[getEnrichedConditionalUsages] "${path}" explainPath → no source found`,
5256
+ );
5132
5257
  }
5133
5258
 
5259
+ // If explainPath didn't find a useful external source (e.g., it traced to
5260
+ // useState or just to the component scope itself), check sourceEquivalencies
5261
+ // for an external function call source like a fetch call
5262
+ const hasExternalSource = sourceDataPath?.includes(
5263
+ '.functionCallReturnValue',
5264
+ );
5265
+ if (!hasExternalSource) {
5266
+ console.log(
5267
+ `[getEnrichedConditionalUsages] "${path}" no external source (sourceDataPath="${sourceDataPath}"), checking sourceEquivalencies fallback...`,
5268
+ );
5269
+ const sourceEquiv = this.getSourceEquivalencies();
5270
+ const returnValueKey = `returnValue.${path}`;
5271
+ const sources = sourceEquiv[returnValueKey];
5272
+ if (sources) {
5273
+ console.log(
5274
+ `[getEnrichedConditionalUsages] "${path}" sourceEquivalencies["${returnValueKey}"] has ${sources.length} sources: [${sources.map((s: { schemaPath: string }) => s.schemaPath).join(', ')}]`,
5275
+ );
5276
+ const externalSource = sources.find(
5277
+ (s: { schemaPath: string }) =>
5278
+ s.schemaPath.includes('.functionCallReturnValue') &&
5279
+ !s.schemaPath.startsWith('useState('),
5280
+ );
5281
+ if (externalSource) {
5282
+ console.log(
5283
+ `[getEnrichedConditionalUsages] "${path}" sourceEquivalencies fallback found external source: "${externalSource.schemaPath}"`,
5284
+ );
5285
+ sourceDataPath = externalSource.schemaPath;
5286
+ } else {
5287
+ console.log(
5288
+ `[getEnrichedConditionalUsages] "${path}" sourceEquivalencies fallback found no external function call source`,
5289
+ );
5290
+ }
5291
+ } else {
5292
+ console.log(
5293
+ `[getEnrichedConditionalUsages] "${path}" sourceEquivalencies["${returnValueKey}"] not found`,
5294
+ );
5295
+ }
5296
+ }
5297
+
5298
+ console.log(
5299
+ `[getEnrichedConditionalUsages] "${path}" FINAL sourceDataPath="${sourceDataPath ?? '(none)'}" (${usages.length} usages)`,
5300
+ );
5301
+
5134
5302
  enriched[path] = usages.map((usage) => ({
5135
5303
  ...usage,
5136
5304
  sourceDataPath,
@@ -31,6 +31,20 @@ function isSkippableLeafType(t: string) {
31
31
  );
32
32
  }
33
33
 
34
+ // Extract the base structural type from a potentially nullable type string.
35
+ // e.g., 'object | undefined' → 'object', 'array | null' → 'array'
36
+ 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];
40
+ }
41
+
42
+ // Check if a type string has nullable annotations (| undefined or | null)
43
+ function isNullableType(t: string): boolean {
44
+ const parts = t.split('|').map((s) => s.trim());
45
+ return parts.includes('undefined') || parts.includes('null');
46
+ }
47
+
34
48
  // Matches paths containing [][] — e.g., "items[][]" or "items[][].text"
35
49
  const DOUBLE_ARRAY_RE = /\[\]\[\]/;
36
50
 
@@ -317,15 +331,41 @@ export default function convertDotNotation(
317
331
  cursor[key] = typ;
318
332
  } else {
319
333
  // Structural/placeholder terminal
320
- if (typ === 'array') {
334
+ const nullable = isNullableType(typ);
335
+ const baseType = getBaseSkippableType(typ);
336
+
337
+ if (baseType === 'array') {
321
338
  if (!Array.isArray(cursor[key])) cursor[key] = [];
339
+ if (nullable) {
340
+ (cursor[key] as any)._nullable = true;
341
+ }
322
342
  } else if (
323
- typ === 'object' ||
324
- typ === 'function' ||
325
- typ.trim() === 'unknown'
343
+ baseType === 'object' ||
344
+ baseType === 'function' ||
345
+ baseType === 'unknown'
326
346
  ) {
327
- if (cursor[key] === undefined) {
328
- cursor[key] = typ;
347
+ if (nullable) {
348
+ // Nullable object: ensure it's an actual object (not a string
349
+ // placeholder) so _nullable can be set and child paths can
350
+ // populate properties on it.
351
+ if (
352
+ cursor[key] === undefined ||
353
+ typeof cursor[key] === 'string'
354
+ ) {
355
+ cursor[key] = {};
356
+ }
357
+ if (
358
+ typeof cursor[key] === 'object' &&
359
+ cursor[key] !== null &&
360
+ !Array.isArray(cursor[key])
361
+ ) {
362
+ (cursor[key] as any)._nullable = true;
363
+ }
364
+ } else {
365
+ // Non-nullable: preserve existing behavior (string placeholder)
366
+ if (cursor[key] === undefined) {
367
+ cursor[key] = typ;
368
+ }
329
369
  }
330
370
  }
331
371
  }
@@ -3,9 +3,11 @@ import type { ExecutionFlow, ScenariosDataStructure } from '~codeyam/types';
3
3
  // Type for a single required value from ExecutionFlow
4
4
  type RequiredValue = NonNullable<ExecutionFlow['requiredValues']>[number];
5
5
 
6
- const DEFAULT_MAX_KEYS_PER_CHUNK = 8;
6
+ const DEFAULT_MAX_CHUNK_SIZE = 10_000; // ~10K chars per chunk
7
7
 
8
8
  export interface ChunkOptions {
9
+ maxChunkSize?: number;
10
+ /** @deprecated Use maxChunkSize instead. Kept for backward compatibility in tests. */
9
11
  maxKeysPerChunk?: number;
10
12
  }
11
13
 
@@ -15,8 +17,12 @@ export interface ChunkOptions {
15
17
  * Large schemas overwhelm LLMs, causing them to make mistakes on some keys.
16
18
  * By processing smaller chunks, each key gets more focused attention.
17
19
  *
20
+ * Uses cumulative JSON size to decide chunk boundaries rather than a fixed
21
+ * key count, so a single oversized key gets its own chunk while many small
22
+ * keys are grouped together efficiently.
23
+ *
18
24
  * @param dataForMocks - The full data structure schema
19
- * @param options - Chunking options (maxKeysPerChunk defaults to 8)
25
+ * @param options - Chunking options (maxChunkSize defaults to 10_000)
20
26
  * @returns Array of smaller data structure chunks
21
27
  */
22
28
  export function chunkDataStructure(
@@ -27,27 +33,39 @@ export function chunkDataStructure(
27
33
  return [dataForMocks];
28
34
  }
29
35
 
30
- const { maxKeysPerChunk = DEFAULT_MAX_KEYS_PER_CHUNK } = options;
36
+ const { maxChunkSize = DEFAULT_MAX_CHUNK_SIZE } = options;
31
37
  const keys = Object.keys(dataForMocks);
32
38
 
33
- // Don't chunk if already small enough
34
- if (keys.length <= maxKeysPerChunk) {
39
+ // Calculate total size to see if chunking is needed
40
+ const totalSize = JSON.stringify(dataForMocks).length;
41
+ if (totalSize <= maxChunkSize) {
35
42
  return [dataForMocks];
36
43
  }
37
44
 
45
+ // Greedily pack keys into chunks by cumulative size
38
46
  const chunks: Array<ScenariosDataStructure['dataForMocks']> = [];
39
-
40
- for (let i = 0; i < keys.length; i += maxKeysPerChunk) {
41
- const chunkKeys = keys.slice(i, i + maxKeysPerChunk);
42
- const chunk: ScenariosDataStructure['dataForMocks'] = {};
43
-
44
- for (const key of chunkKeys) {
45
- (chunk as Record<string, unknown>)[key] = (
46
- dataForMocks as Record<string, unknown>
47
- )[key];
47
+ let currentChunk: Record<string, unknown> = {};
48
+ let currentSize = 0;
49
+
50
+ for (const key of keys) {
51
+ const keySize = JSON.stringify(
52
+ (dataForMocks as Record<string, unknown>)[key],
53
+ ).length;
54
+
55
+ // If adding this key would exceed the limit AND chunk isn't empty, start a new chunk
56
+ if (currentSize > 0 && currentSize + keySize > maxChunkSize) {
57
+ chunks.push(currentChunk as ScenariosDataStructure['dataForMocks']);
58
+ currentChunk = {};
59
+ currentSize = 0;
48
60
  }
49
61
 
50
- chunks.push(chunk);
62
+ currentChunk[key] = (dataForMocks as Record<string, unknown>)[key];
63
+ currentSize += keySize;
64
+ }
65
+
66
+ // Push the last chunk
67
+ if (Object.keys(currentChunk).length > 0) {
68
+ chunks.push(currentChunk as ScenariosDataStructure['dataForMocks']);
51
69
  }
52
70
 
53
71
  return chunks;
@@ -294,11 +294,13 @@ function generateDefaultForSchemaType(schemaType: unknown): unknown {
294
294
  if (schemaType.includes('| null')) return null;
295
295
  return schemaType; // Return the type as a string placeholder
296
296
  }
297
- if (
298
- typeof schemaType === 'object' &&
299
- schemaType !== null &&
300
- !Array.isArray(schemaType)
301
- ) {
297
+ if (Array.isArray(schemaType)) {
298
+ if (schemaType.length === 0) return [];
299
+ // Generate a single default element based on the first element's schema
300
+ const elementDefault = generateDefaultForSchemaType(schemaType[0]);
301
+ return elementDefault !== undefined ? [elementDefault] : [];
302
+ }
303
+ if (typeof schemaType === 'object' && schemaType !== null) {
302
304
  // Recursively generate defaults for nested objects
303
305
  const result: Record<string, unknown> = {};
304
306
  for (const [key, value] of Object.entries(schemaType)) {
@@ -426,6 +428,8 @@ function fillMissingMockDataKeysWithDefaults(
426
428
  const missingKeys: string[] = [];
427
429
 
428
430
  for (const key of Object.keys(dataForMocks)) {
431
+ if (key === '_nullable') continue; // Internal marker, not a data key
432
+
429
433
  const fullPath = pathPrefix ? `${pathPrefix}.${key}` : key;
430
434
 
431
435
  if (mockData[key] === undefined) {
@@ -841,6 +845,29 @@ export async function generateDataForScenario({
841
845
  }
842
846
  }
843
847
 
848
+ // Detect keys that were lost from failed or partial chunk responses
849
+ // and fill them with schema-based defaults so they aren't permanently lost.
850
+ const allChunkedKeys = chunks.flatMap((c) => Object.keys(c || {}));
851
+ const returnedKeys = new Set(Object.keys(chunkedMockData));
852
+ const missingChunkKeys = allChunkedKeys.filter(
853
+ (k) => !returnedKeys.has(k),
854
+ );
855
+
856
+ if (missingChunkKeys.length > 0) {
857
+ awsLog(
858
+ `Chunked processing: ${missingChunkKeys.length} key(s) missing from chunk results, filling with defaults: ${missingChunkKeys.join(', ')}`,
859
+ );
860
+ const dataForMocksRecord = structure.dataForMocks as Record<
861
+ string,
862
+ unknown
863
+ >;
864
+ for (const key of missingChunkKeys) {
865
+ chunkedMockData[key] = generateDefaultForSchemaType(
866
+ dataForMocksRecord[key],
867
+ );
868
+ }
869
+ }
870
+
844
871
  awsLog(
845
872
  `Chunked processing complete. Generated ${Object.keys(chunkedMockData).length} keys total`,
846
873
  );