@codeyam/codeyam-cli 0.1.0-staging.6e699e5 → 0.1.0-staging.79ef713

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 (437) 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 +7 -7
  4. package/analyzer-template/packages/ai/index.ts +10 -2
  5. package/analyzer-template/packages/ai/package.json +2 -2
  6. package/analyzer-template/packages/ai/src/lib/analyzeScope.ts +86 -18
  7. package/analyzer-template/packages/ai/src/lib/astScopes/astScopeAnalyzer.ts +67 -9
  8. package/analyzer-template/packages/ai/src/lib/astScopes/methodSemantics.ts +41 -17
  9. package/analyzer-template/packages/ai/src/lib/astScopes/patterns/forInStatementHandler.ts +10 -17
  10. package/analyzer-template/packages/ai/src/lib/astScopes/processExpression.ts +308 -50
  11. package/analyzer-template/packages/ai/src/lib/astScopes/types.ts +15 -6
  12. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +833 -243
  13. package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/JavascriptFrameworkManager.ts +5 -1
  14. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.ts +16 -3
  15. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.ts +6 -4
  16. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +54 -3
  17. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.ts +60 -15
  18. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/coerceObjectsToPrimitivesBySchema.ts +70 -0
  19. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.ts +80 -5
  20. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.ts +179 -0
  21. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.ts +40 -30
  22. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.ts +393 -97
  23. package/analyzer-template/packages/ai/src/lib/generateEntityDataStructure.ts +58 -3
  24. package/analyzer-template/packages/ai/src/lib/generateEntityScenarioData.ts +283 -1
  25. package/analyzer-template/packages/ai/src/lib/generateEntityScenarios.ts +9 -5
  26. package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +11 -3
  27. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionalEffects.ts +1 -1
  28. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionals.ts +297 -7
  29. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromJsxUsages.ts +1 -1
  30. package/analyzer-template/packages/ai/src/lib/isolateScopes.ts +51 -3
  31. package/analyzer-template/packages/ai/src/lib/mergeStatements.ts +90 -96
  32. package/analyzer-template/packages/ai/src/lib/promptGenerators/gatherAttributesMap.ts +10 -7
  33. package/analyzer-template/packages/ai/src/lib/resolvePathToControllable.ts +25 -13
  34. package/analyzer-template/packages/ai/src/lib/worker/SerializableDataStructure.ts +4 -3
  35. package/analyzer-template/packages/ai/src/lib/worker/analyzeScopeWorker.ts +114 -2
  36. package/analyzer-template/packages/analyze/index.ts +2 -0
  37. package/analyzer-template/packages/analyze/src/lib/FileAnalyzer.ts +65 -59
  38. package/analyzer-template/packages/analyze/src/lib/ProjectAnalyzer.ts +113 -26
  39. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.ts +19 -0
  40. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.ts +19 -0
  41. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllExports.ts +11 -0
  42. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.ts +8 -0
  43. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.ts +49 -1
  44. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.ts +2 -1
  45. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +71 -9
  46. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +19 -4
  47. package/analyzer-template/packages/analyze/src/lib/files/analyze/dependencyResolver.ts +6 -0
  48. package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +4 -2
  49. package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +0 -3
  50. package/analyzer-template/packages/analyze/src/lib/files/analyzeRemixRoute.ts +4 -5
  51. package/analyzer-template/packages/analyze/src/lib/files/getImportedExports.ts +14 -12
  52. package/analyzer-template/packages/analyze/src/lib/files/scenarios/TransformationTracer.ts +1315 -0
  53. package/analyzer-template/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.ts +61 -13
  54. package/analyzer-template/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.ts +37 -0
  55. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +229 -19
  56. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.ts +117 -9
  57. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +459 -39
  58. package/analyzer-template/packages/analyze/src/lib/files/scenarios/propagateArrayItemSchemas.ts +474 -0
  59. package/analyzer-template/packages/analyze/src/lib/files/setImportedExports.ts +2 -1
  60. package/analyzer-template/packages/analyze/src/lib/index.ts +1 -0
  61. package/analyzer-template/packages/analyze/src/lib/utils/getFileByPath.ts +19 -0
  62. package/analyzer-template/packages/aws/package.json +1 -1
  63. package/analyzer-template/packages/database/package.json +1 -1
  64. package/analyzer-template/packages/database/src/lib/kysely/db.ts +8 -1
  65. package/analyzer-template/packages/database/src/lib/kysely/tables/commitsTable.ts +6 -0
  66. package/analyzer-template/packages/database/src/lib/loadAnalyses.ts +58 -1
  67. package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +13 -0
  68. package/analyzer-template/packages/database/src/lib/loadBranch.ts +16 -1
  69. package/analyzer-template/packages/database/src/lib/loadCommit.ts +11 -0
  70. package/analyzer-template/packages/database/src/lib/loadCommits.ts +28 -0
  71. package/analyzer-template/packages/database/src/lib/loadEntities.ts +26 -3
  72. package/analyzer-template/packages/database/src/lib/loadEntityBranches.ts +12 -0
  73. package/analyzer-template/packages/database/src/lib/updateCommitMetadata.ts +7 -14
  74. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.d.ts.map +1 -1
  75. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.js +8 -1
  76. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.js.map +1 -1
  77. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/commitsTable.d.ts +1 -0
  78. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/commitsTable.d.ts.map +1 -1
  79. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/commitsTable.js +3 -0
  80. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/commitsTable.js.map +1 -1
  81. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalyses.d.ts +2 -0
  82. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalyses.d.ts.map +1 -1
  83. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalyses.js +45 -2
  84. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalyses.js.map +1 -1
  85. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.d.ts.map +1 -1
  86. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js +8 -0
  87. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js.map +1 -1
  88. package/analyzer-template/packages/github/dist/database/src/lib/loadBranch.js +11 -1
  89. package/analyzer-template/packages/github/dist/database/src/lib/loadBranch.js.map +1 -1
  90. package/analyzer-template/packages/github/dist/database/src/lib/loadCommit.d.ts.map +1 -1
  91. package/analyzer-template/packages/github/dist/database/src/lib/loadCommit.js +7 -0
  92. package/analyzer-template/packages/github/dist/database/src/lib/loadCommit.js.map +1 -1
  93. package/analyzer-template/packages/github/dist/database/src/lib/loadCommits.d.ts +3 -1
  94. package/analyzer-template/packages/github/dist/database/src/lib/loadCommits.d.ts.map +1 -1
  95. package/analyzer-template/packages/github/dist/database/src/lib/loadCommits.js +22 -1
  96. package/analyzer-template/packages/github/dist/database/src/lib/loadCommits.js.map +1 -1
  97. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts +3 -1
  98. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts.map +1 -1
  99. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js +23 -4
  100. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js.map +1 -1
  101. package/analyzer-template/packages/github/dist/database/src/lib/loadEntityBranches.d.ts.map +1 -1
  102. package/analyzer-template/packages/github/dist/database/src/lib/loadEntityBranches.js +9 -0
  103. package/analyzer-template/packages/github/dist/database/src/lib/loadEntityBranches.js.map +1 -1
  104. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts +2 -2
  105. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts.map +1 -1
  106. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js +5 -4
  107. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js.map +1 -1
  108. package/analyzer-template/packages/github/dist/types/src/types/Commit.d.ts +2 -0
  109. package/analyzer-template/packages/github/dist/types/src/types/Commit.d.ts.map +1 -1
  110. package/analyzer-template/packages/github/dist/types/src/types/ProjectMetadata.d.ts +3 -0
  111. package/analyzer-template/packages/github/dist/types/src/types/ProjectMetadata.d.ts.map +1 -1
  112. package/analyzer-template/packages/github/dist/types/src/types/ScenariosDataStructure.d.ts +5 -5
  113. package/analyzer-template/packages/github/dist/types/src/types/ScenariosDataStructure.d.ts.map +1 -1
  114. package/analyzer-template/packages/github/dist/types/src/types/ScopeAnalysis.d.ts +6 -1
  115. package/analyzer-template/packages/github/dist/types/src/types/ScopeAnalysis.d.ts.map +1 -1
  116. package/analyzer-template/packages/github/package.json +1 -1
  117. package/analyzer-template/packages/types/src/types/Commit.ts +2 -0
  118. package/analyzer-template/packages/types/src/types/ProjectMetadata.ts +1 -0
  119. package/analyzer-template/packages/types/src/types/ScenariosDataStructure.ts +6 -5
  120. package/analyzer-template/packages/types/src/types/ScopeAnalysis.ts +6 -1
  121. package/analyzer-template/packages/utils/dist/types/src/types/Commit.d.ts +2 -0
  122. package/analyzer-template/packages/utils/dist/types/src/types/Commit.d.ts.map +1 -1
  123. package/analyzer-template/packages/utils/dist/types/src/types/ProjectMetadata.d.ts +3 -0
  124. package/analyzer-template/packages/utils/dist/types/src/types/ProjectMetadata.d.ts.map +1 -1
  125. package/analyzer-template/packages/utils/dist/types/src/types/ScenariosDataStructure.d.ts +5 -5
  126. package/analyzer-template/packages/utils/dist/types/src/types/ScenariosDataStructure.d.ts.map +1 -1
  127. package/analyzer-template/packages/utils/dist/types/src/types/ScopeAnalysis.d.ts +6 -1
  128. package/analyzer-template/packages/utils/dist/types/src/types/ScopeAnalysis.d.ts.map +1 -1
  129. package/analyzer-template/playwright/capture.ts +20 -8
  130. package/analyzer-template/playwright/captureStatic.ts +1 -1
  131. package/analyzer-template/project/analyzeBaselineCommit.ts +5 -0
  132. package/analyzer-template/project/analyzeRegularCommit.ts +5 -0
  133. package/analyzer-template/project/captureLibraryFunctionDirect.ts +29 -26
  134. package/analyzer-template/project/constructMockCode.ts +90 -10
  135. package/analyzer-template/project/createEntitiesAndSortFiles.ts +83 -0
  136. package/analyzer-template/project/loadReadyToBeCaptured.ts +65 -41
  137. package/analyzer-template/project/orchestrateCapture/AwsCaptureTaskRunner.ts +12 -4
  138. package/analyzer-template/project/orchestrateCapture/SequentialCaptureTaskRunner.ts +11 -6
  139. package/analyzer-template/project/orchestrateCapture/taskRunner.ts +4 -2
  140. package/analyzer-template/project/orchestrateCapture.ts +45 -6
  141. package/analyzer-template/project/start.ts +35 -11
  142. package/analyzer-template/project/writeMockDataTsx.ts +181 -8
  143. package/analyzer-template/project/writeScenarioComponents.ts +103 -12
  144. package/analyzer-template/project/writeSimpleRoot.ts +21 -11
  145. package/analyzer-template/scripts/comboWorkerLoop.cjs +98 -50
  146. package/background/src/lib/virtualized/project/analyzeBaselineCommit.js +5 -0
  147. package/background/src/lib/virtualized/project/analyzeBaselineCommit.js.map +1 -1
  148. package/background/src/lib/virtualized/project/analyzeRegularCommit.js +5 -0
  149. package/background/src/lib/virtualized/project/analyzeRegularCommit.js.map +1 -1
  150. package/background/src/lib/virtualized/project/captureLibraryFunctionDirect.js +3 -3
  151. package/background/src/lib/virtualized/project/captureLibraryFunctionDirect.js.map +1 -1
  152. package/background/src/lib/virtualized/project/constructMockCode.js +75 -4
  153. package/background/src/lib/virtualized/project/constructMockCode.js.map +1 -1
  154. package/background/src/lib/virtualized/project/createEntitiesAndSortFiles.js +73 -1
  155. package/background/src/lib/virtualized/project/createEntitiesAndSortFiles.js.map +1 -1
  156. package/background/src/lib/virtualized/project/loadReadyToBeCaptured.js +19 -8
  157. package/background/src/lib/virtualized/project/loadReadyToBeCaptured.js.map +1 -1
  158. package/background/src/lib/virtualized/project/orchestrateCapture/AwsCaptureTaskRunner.js +2 -2
  159. package/background/src/lib/virtualized/project/orchestrateCapture/AwsCaptureTaskRunner.js.map +1 -1
  160. package/background/src/lib/virtualized/project/orchestrateCapture/SequentialCaptureTaskRunner.js +4 -4
  161. package/background/src/lib/virtualized/project/orchestrateCapture/SequentialCaptureTaskRunner.js.map +1 -1
  162. package/background/src/lib/virtualized/project/orchestrateCapture.js +38 -6
  163. package/background/src/lib/virtualized/project/orchestrateCapture.js.map +1 -1
  164. package/background/src/lib/virtualized/project/start.js +32 -11
  165. package/background/src/lib/virtualized/project/start.js.map +1 -1
  166. package/background/src/lib/virtualized/project/writeMockDataTsx.js +162 -4
  167. package/background/src/lib/virtualized/project/writeMockDataTsx.js.map +1 -1
  168. package/background/src/lib/virtualized/project/writeScenarioComponents.js +85 -15
  169. package/background/src/lib/virtualized/project/writeScenarioComponents.js.map +1 -1
  170. package/background/src/lib/virtualized/project/writeSimpleRoot.js +21 -11
  171. package/background/src/lib/virtualized/project/writeSimpleRoot.js.map +1 -1
  172. package/codeyam-cli/scripts/apply-setup.js +180 -0
  173. package/codeyam-cli/scripts/apply-setup.js.map +1 -1
  174. package/codeyam-cli/src/cli.js +2 -0
  175. package/codeyam-cli/src/cli.js.map +1 -1
  176. package/codeyam-cli/src/commands/debug.js +7 -5
  177. package/codeyam-cli/src/commands/debug.js.map +1 -1
  178. package/codeyam-cli/src/commands/memory.js +264 -0
  179. package/codeyam-cli/src/commands/memory.js.map +1 -0
  180. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +2 -2
  181. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
  182. package/codeyam-cli/src/utils/analysisRunner.js +21 -2
  183. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  184. package/codeyam-cli/src/utils/backgroundServer.js +4 -0
  185. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  186. package/codeyam-cli/src/utils/install-skills.js +55 -10
  187. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  188. package/codeyam-cli/src/utils/queue/job.js +4 -0
  189. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  190. package/codeyam-cli/src/utils/ruleReflection/__tests__/confusionDetector.test.js +82 -0
  191. package/codeyam-cli/src/utils/ruleReflection/__tests__/confusionDetector.test.js.map +1 -0
  192. package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js +230 -0
  193. package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js.map +1 -0
  194. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/assertRules.js +67 -0
  195. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/assertRules.js.map +1 -0
  196. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/captureFixture.js +105 -0
  197. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/captureFixture.js.map +1 -0
  198. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/loadCapturedFixture.js +34 -0
  199. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/loadCapturedFixture.js.map +1 -0
  200. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/runClaude.js +162 -0
  201. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/runClaude.js.map +1 -0
  202. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/setupTempProject.js +75 -0
  203. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/setupTempProject.js.map +1 -0
  204. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js +285 -0
  205. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js.map +1 -0
  206. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js +115 -0
  207. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js.map +1 -0
  208. package/codeyam-cli/src/utils/ruleReflection/__tests__/transcriptParser.test.js +127 -0
  209. package/codeyam-cli/src/utils/ruleReflection/__tests__/transcriptParser.test.js.map +1 -0
  210. package/codeyam-cli/src/utils/ruleReflection/confusionDetector.js +50 -0
  211. package/codeyam-cli/src/utils/ruleReflection/confusionDetector.js.map +1 -0
  212. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js +116 -0
  213. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js.map +1 -0
  214. package/codeyam-cli/src/utils/ruleReflection/index.js +5 -0
  215. package/codeyam-cli/src/utils/ruleReflection/index.js.map +1 -0
  216. package/codeyam-cli/src/utils/ruleReflection/promptBuilder.js +44 -0
  217. package/codeyam-cli/src/utils/ruleReflection/promptBuilder.js.map +1 -0
  218. package/codeyam-cli/src/utils/ruleReflection/transcriptParser.js +85 -0
  219. package/codeyam-cli/src/utils/ruleReflection/transcriptParser.js.map +1 -0
  220. package/codeyam-cli/src/utils/ruleReflection/types.js +5 -0
  221. package/codeyam-cli/src/utils/ruleReflection/types.js.map +1 -0
  222. package/codeyam-cli/src/utils/rules/__tests__/ruleState.test.js +293 -0
  223. package/codeyam-cli/src/utils/rules/__tests__/ruleState.test.js.map +1 -0
  224. package/codeyam-cli/src/utils/rules/index.js +6 -0
  225. package/codeyam-cli/src/utils/rules/index.js.map +1 -0
  226. package/codeyam-cli/src/utils/rules/parser.js +78 -0
  227. package/codeyam-cli/src/utils/rules/parser.js.map +1 -0
  228. package/codeyam-cli/src/utils/rules/pathMatcher.js +18 -0
  229. package/codeyam-cli/src/utils/rules/pathMatcher.js.map +1 -0
  230. package/codeyam-cli/src/utils/rules/ruleState.js +150 -0
  231. package/codeyam-cli/src/utils/rules/ruleState.js.map +1 -0
  232. package/codeyam-cli/src/utils/rules/staleness.js +137 -0
  233. package/codeyam-cli/src/utils/rules/staleness.js.map +1 -0
  234. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +1 -1
  235. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
  236. package/codeyam-cli/src/webserver/app/lib/database.js +7 -3
  237. package/codeyam-cli/src/webserver/app/lib/database.js.map +1 -1
  238. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-CA3JxPb7.js +1 -0
  239. package/codeyam-cli/src/webserver/build/client/assets/EntityItem-B86KKU7e.js +11 -0
  240. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeBadge-DLqD3qNt.js → EntityTypeBadge-B5ctlSYt.js} +1 -1
  241. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-Ba2JVPzP.js → EntityTypeIcon-BqY8gDAW.js} +1 -1
  242. package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-C8lyxW9k.js → InlineSpinner-ClaLpuOo.js} +1 -1
  243. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-aht4aafF.js → InteractivePreview-BDhPilK7.js} +2 -2
  244. package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-CVtiBnY5.js → LibraryFunctionPreview-VeqEBv9v.js} +1 -1
  245. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-B0GLXMsr.js → LoadingDots-Bs7Nn1Jr.js} +1 -1
  246. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-xgeCVgSM.js → LogViewer-Bm3PmcCz.js} +1 -1
  247. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-D4TZhLuw.js → ReportIssueModal-C6PKeMYR.js} +3 -13
  248. package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-DuDvi0jm.js → SafeScreenshot-Gq3Ocjo6.js} +1 -1
  249. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-DEx02QDa.js → ScenarioViewer-BNLaXBHR.js} +3 -3
  250. package/codeyam-cli/src/webserver/build/client/assets/{TruncatedFilePath-DyFZkK0l.js → TruncatedFilePath-CiwXDxLh.js} +1 -1
  251. package/codeyam-cli/src/webserver/build/client/assets/{_index-BwqWJOgH.js → _index-B3TDXxnk.js} +1 -1
  252. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-DoLIqZX2.js → activity.(_tab)-BtBFH820.js} +6 -16
  253. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-DfKzxuoe.js +11 -0
  254. package/codeyam-cli/src/webserver/build/client/assets/api.memory-profile-l0sNRNKZ.js +1 -0
  255. package/codeyam-cli/src/webserver/build/client/assets/api.save-fixture-l0sNRNKZ.js +1 -0
  256. package/codeyam-cli/src/webserver/build/client/assets/book-open-PttOB2SF.js +6 -0
  257. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-Cx24_aWc.js → chevron-down-TJp6ofnp.js} +1 -1
  258. package/codeyam-cli/src/webserver/build/client/assets/{chunk-EPOLDU6W-CXRTFQ3F.js → chunk-JZWAC4HX-JE9ZIoBl.js} +12 -12
  259. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-BOARzkeR.js → circle-check-CXhHQYrI.js} +1 -1
  260. package/codeyam-cli/src/webserver/build/client/assets/copy-6y9ALfGT.js +11 -0
  261. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-BdhJEx6B.js → createLucideIcon-Ca9fAY46.js} +1 -1
  262. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-C5lqplTC.js +1 -0
  263. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-C2N4Op8e.js → entity._sha._-n38keI1k.js} +10 -10
  264. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-DavjRmOY.js → entity._sha.scenarios._scenarioId.fullscreen-CBoafmVs.js} +1 -1
  265. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-D1T4TGjf.js → entity._sha_.create-scenario-DGgZjdFg.js} +1 -1
  266. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-CTBG2mmz.js → entity._sha_.edit._scenarioId-38yPijoD.js} +1 -1
  267. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-CS2cb_eZ.js → entry.client-BSHEfydn.js} +1 -1
  268. package/codeyam-cli/src/webserver/build/client/assets/{fileTableUtils-DMJ7zii9.js → fileTableUtils-DCPhhSMo.js} +1 -1
  269. package/codeyam-cli/src/webserver/build/client/assets/files-0N0YJQv7.js +1 -0
  270. package/codeyam-cli/src/webserver/build/client/assets/{git-B4RJRvYB.js → git-DXnyr8uP.js} +8 -8
  271. package/codeyam-cli/src/webserver/build/client/assets/globals-Bh6jH0cL.css +1 -0
  272. package/codeyam-cli/src/webserver/build/client/assets/{index-lzqtyFU8.js → index-CcsFv748.js} +1 -1
  273. package/codeyam-cli/src/webserver/build/client/assets/{index-B1h680n5.js → index-ChN9-fAY.js} +1 -1
  274. package/codeyam-cli/src/webserver/build/client/assets/labs-CdVUfvji.js +1 -0
  275. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-B7B9V-bu.js → loader-circle-CTqLEAGU.js} +1 -1
  276. package/codeyam-cli/src/webserver/build/client/assets/manifest-87319d0f.js +1 -0
  277. package/codeyam-cli/src/webserver/build/client/assets/memory-CPIDnDEj.js +76 -0
  278. package/codeyam-cli/src/webserver/build/client/assets/pause-D6vreykR.js +11 -0
  279. package/codeyam-cli/src/webserver/build/client/assets/root-D6oziHts.js +62 -0
  280. package/codeyam-cli/src/webserver/build/client/assets/{search-CxXUmBSd.js → search-B8VUL8nl.js} +1 -1
  281. package/codeyam-cli/src/webserver/build/client/assets/{settings-CS5f3WzT.js → settings-eBI36Yv5.js} +1 -1
  282. package/codeyam-cli/src/webserver/build/client/assets/{simulations-DwFIBT09.js → simulations-CPoAg7Zo.js} +1 -1
  283. package/codeyam-cli/src/webserver/build/client/assets/terminal-BrCP7uQo.js +11 -0
  284. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-B6LgvRJg.js → triangle-alert-BZz2NjYa.js} +1 -1
  285. package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-C1v1PQzo.js → useCustomSizes-DNwUduNu.js} +1 -1
  286. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-aSv48UbS.js → useLastLogLine-COky1GVF.js} +1 -1
  287. package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-DYxHZQuP.js → useReportContext-CpZgwliL.js} +1 -1
  288. package/codeyam-cli/src/webserver/build/client/assets/{useToast-mBRpZPiu.js → useToast-Bv9JFvUO.js} +1 -1
  289. package/codeyam-cli/src/webserver/build/server/assets/index-9ox9LcrG.js +1 -0
  290. package/codeyam-cli/src/webserver/build/server/assets/server-build-Cq5Vqcob.js +260 -0
  291. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  292. package/codeyam-cli/src/webserver/build-info.json +5 -5
  293. package/codeyam-cli/templates/{codeyam-power-rules-hook.sh → codeyam-memory-hook.sh} +12 -13
  294. package/codeyam-cli/templates/codeyam:diagnose.md +178 -25
  295. package/codeyam-cli/templates/codeyam:memory.md +404 -0
  296. package/codeyam-cli/templates/codeyam:new-rule.md +2 -2
  297. package/codeyam-cli/templates/rule-notification-hook.py +56 -0
  298. package/codeyam-cli/templates/rule-reflection-hook.py +590 -0
  299. package/codeyam-cli/templates/rules-instructions.md +123 -0
  300. package/package.json +8 -6
  301. package/packages/ai/index.js +3 -2
  302. package/packages/ai/index.js.map +1 -1
  303. package/packages/ai/src/lib/analyzeScope.js +68 -13
  304. package/packages/ai/src/lib/analyzeScope.js.map +1 -1
  305. package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js +54 -8
  306. package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js.map +1 -1
  307. package/packages/ai/src/lib/astScopes/methodSemantics.js +41 -17
  308. package/packages/ai/src/lib/astScopes/methodSemantics.js.map +1 -1
  309. package/packages/ai/src/lib/astScopes/patterns/forInStatementHandler.js +10 -14
  310. package/packages/ai/src/lib/astScopes/patterns/forInStatementHandler.js.map +1 -1
  311. package/packages/ai/src/lib/astScopes/processExpression.js +239 -43
  312. package/packages/ai/src/lib/astScopes/processExpression.js.map +1 -1
  313. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +650 -166
  314. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  315. package/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/JavascriptFrameworkManager.js +5 -1
  316. package/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/JavascriptFrameworkManager.js.map +1 -1
  317. package/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.js +13 -3
  318. package/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.js.map +1 -1
  319. package/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.js +6 -4
  320. package/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.js.map +1 -1
  321. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +52 -3
  322. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  323. package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js +55 -11
  324. package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js.map +1 -1
  325. package/packages/ai/src/lib/dataStructure/helpers/coerceObjectsToPrimitivesBySchema.js +63 -0
  326. package/packages/ai/src/lib/dataStructure/helpers/coerceObjectsToPrimitivesBySchema.js.map +1 -0
  327. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js +73 -5
  328. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js.map +1 -1
  329. package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js +173 -0
  330. package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js.map +1 -0
  331. package/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.js +37 -20
  332. package/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.js.map +1 -1
  333. package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js +333 -86
  334. package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js.map +1 -1
  335. package/packages/ai/src/lib/generateEntityDataStructure.js +46 -2
  336. package/packages/ai/src/lib/generateEntityDataStructure.js.map +1 -1
  337. package/packages/ai/src/lib/generateEntityScenarioData.js +205 -1
  338. package/packages/ai/src/lib/generateEntityScenarioData.js.map +1 -1
  339. package/packages/ai/src/lib/generateEntityScenarios.js +7 -1
  340. package/packages/ai/src/lib/generateEntityScenarios.js.map +1 -1
  341. package/packages/ai/src/lib/generateExecutionFlows.js +10 -2
  342. package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
  343. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js +209 -3
  344. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js.map +1 -1
  345. package/packages/ai/src/lib/isolateScopes.js +39 -3
  346. package/packages/ai/src/lib/isolateScopes.js.map +1 -1
  347. package/packages/ai/src/lib/mergeStatements.js +70 -51
  348. package/packages/ai/src/lib/mergeStatements.js.map +1 -1
  349. package/packages/ai/src/lib/promptGenerators/gatherAttributesMap.js +10 -4
  350. package/packages/ai/src/lib/promptGenerators/gatherAttributesMap.js.map +1 -1
  351. package/packages/ai/src/lib/resolvePathToControllable.js +24 -14
  352. package/packages/ai/src/lib/resolvePathToControllable.js.map +1 -1
  353. package/packages/ai/src/lib/worker/SerializableDataStructure.js.map +1 -1
  354. package/packages/ai/src/lib/worker/analyzeScopeWorker.js +94 -1
  355. package/packages/ai/src/lib/worker/analyzeScopeWorker.js.map +1 -1
  356. package/packages/analyze/index.js +1 -0
  357. package/packages/analyze/index.js.map +1 -1
  358. package/packages/analyze/src/lib/FileAnalyzer.js +60 -36
  359. package/packages/analyze/src/lib/FileAnalyzer.js.map +1 -1
  360. package/packages/analyze/src/lib/ProjectAnalyzer.js +96 -26
  361. package/packages/analyze/src/lib/ProjectAnalyzer.js.map +1 -1
  362. package/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.js +14 -0
  363. package/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.js.map +1 -1
  364. package/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.js +14 -0
  365. package/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.js.map +1 -1
  366. package/packages/analyze/src/lib/asts/sourceFiles/getAllExports.js +6 -0
  367. package/packages/analyze/src/lib/asts/sourceFiles/getAllExports.js.map +1 -1
  368. package/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.js +6 -0
  369. package/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.js.map +1 -1
  370. package/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.js +39 -1
  371. package/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.js.map +1 -1
  372. package/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.js +2 -1
  373. package/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.js.map +1 -1
  374. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +54 -6
  375. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  376. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +17 -4
  377. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  378. package/packages/analyze/src/lib/files/analyze/dependencyResolver.js +5 -0
  379. package/packages/analyze/src/lib/files/analyze/dependencyResolver.js.map +1 -1
  380. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +2 -1
  381. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
  382. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +0 -3
  383. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
  384. package/packages/analyze/src/lib/files/analyzeRemixRoute.js +3 -2
  385. package/packages/analyze/src/lib/files/analyzeRemixRoute.js.map +1 -1
  386. package/packages/analyze/src/lib/files/getImportedExports.js +11 -7
  387. package/packages/analyze/src/lib/files/getImportedExports.js.map +1 -1
  388. package/packages/analyze/src/lib/files/scenarios/TransformationTracer.js +880 -0
  389. package/packages/analyze/src/lib/files/scenarios/TransformationTracer.js.map +1 -0
  390. package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js +56 -10
  391. package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js.map +1 -1
  392. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js +33 -8
  393. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js.map +1 -1
  394. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +150 -17
  395. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  396. package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js +56 -8
  397. package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js.map +1 -1
  398. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +399 -31
  399. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  400. package/packages/analyze/src/lib/files/setImportedExports.js +2 -1
  401. package/packages/analyze/src/lib/files/setImportedExports.js.map +1 -1
  402. package/packages/analyze/src/lib/index.js +1 -0
  403. package/packages/analyze/src/lib/index.js.map +1 -1
  404. package/packages/analyze/src/lib/utils/getFileByPath.js +12 -0
  405. package/packages/analyze/src/lib/utils/getFileByPath.js.map +1 -0
  406. package/packages/database/src/lib/kysely/db.js +8 -1
  407. package/packages/database/src/lib/kysely/db.js.map +1 -1
  408. package/packages/database/src/lib/kysely/tables/commitsTable.js +3 -0
  409. package/packages/database/src/lib/kysely/tables/commitsTable.js.map +1 -1
  410. package/packages/database/src/lib/loadAnalyses.js +45 -2
  411. package/packages/database/src/lib/loadAnalyses.js.map +1 -1
  412. package/packages/database/src/lib/loadAnalysis.js +8 -0
  413. package/packages/database/src/lib/loadAnalysis.js.map +1 -1
  414. package/packages/database/src/lib/loadBranch.js +11 -1
  415. package/packages/database/src/lib/loadBranch.js.map +1 -1
  416. package/packages/database/src/lib/loadCommit.js +7 -0
  417. package/packages/database/src/lib/loadCommit.js.map +1 -1
  418. package/packages/database/src/lib/loadCommits.js +22 -1
  419. package/packages/database/src/lib/loadCommits.js.map +1 -1
  420. package/packages/database/src/lib/loadEntities.js +23 -4
  421. package/packages/database/src/lib/loadEntities.js.map +1 -1
  422. package/packages/database/src/lib/loadEntityBranches.js +9 -0
  423. package/packages/database/src/lib/loadEntityBranches.js.map +1 -1
  424. package/packages/database/src/lib/updateCommitMetadata.js +5 -4
  425. package/packages/database/src/lib/updateCommitMetadata.js.map +1 -1
  426. package/codeyam-cli/src/webserver/build/client/assets/EntityItem-BXhEawa3.js +0 -1
  427. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-BRb-0kQl.js +0 -1
  428. package/codeyam-cli/src/webserver/build/client/assets/files-Cs4MdYtv.js +0 -1
  429. package/codeyam-cli/src/webserver/build/client/assets/git-commit-horizontal-CysbcZxi.js +0 -6
  430. package/codeyam-cli/src/webserver/build/client/assets/globals-DMUaGAqV.css +0 -1
  431. package/codeyam-cli/src/webserver/build/client/assets/manifest-f874c610.js +0 -1
  432. package/codeyam-cli/src/webserver/build/client/assets/root-Bz5TunQg.js +0 -57
  433. package/codeyam-cli/src/webserver/build/client/assets/rules-hEkvVw2-.js +0 -97
  434. package/codeyam-cli/src/webserver/build/server/assets/index-967OuJoF.js +0 -1
  435. package/codeyam-cli/src/webserver/build/server/assets/server-build-DRTmerg9.js +0 -257
  436. package/codeyam-cli/templates/codeyam:power-rules.md +0 -447
  437. /package/codeyam-cli/src/webserver/build/client/assets/{api.rules-l0sNRNKZ.js → api.agent-transcripts-l0sNRNKZ.js} +0 -0
@@ -0,0 +1,590 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Rule reflection hook for Claude Code.
4
+
5
+ Handles two hook events:
6
+ 1. Stop — Reviews completed turns for stale rules and conversation confusion signals
7
+ 2. UserPromptSubmit — Detects user interruptions (Escape/Ctrl+C) by checking if the
8
+ Stop hook's marker file is stale, then spawns a rule-reflection agent focused on
9
+ the interruption signal
10
+
11
+ Each review fires as a separate `claude -p` invocation so the LLM can focus on one task at a time.
12
+ Stays silent if there's nothing to review.
13
+
14
+ Prompt text lives in templates/prompts/*.txt (single source of truth shared with TypeScript tests).
15
+ """
16
+
17
+ import json
18
+ import os
19
+ import subprocess
20
+ import sys
21
+ from datetime import datetime
22
+ from pathlib import Path
23
+
24
+ MIN_USER_TURNS = 3 # Minimum user turns before checking for confusion
25
+
26
+ # Signals that indicate confusion, mistakes, or user corrections
27
+ CONFUSION_SIGNALS = [
28
+ 'no,', 'no ', "that's not", "thats not", "that is not",
29
+ 'wrong', 'incorrect', 'actually,', 'actually ',
30
+ "i meant", "i mean", "not what i", "stop", "wait",
31
+ "don't do", "dont do", "shouldn't", "should not",
32
+ "try again", "let me clarify", "to clarify",
33
+ "that broke", "that failed", "error", "bug",
34
+ ]
35
+
36
+ # Prompt templates directory — installed alongside this hook in .codeyam/bin/prompts/
37
+ PROMPTS_DIR = Path(__file__).parent / 'prompts'
38
+
39
+
40
+ def load_prompt_template(name, **kwargs):
41
+ """
42
+ Load a prompt template from the prompts/ directory and substitute placeholders.
43
+ Placeholders use {{KEY}} syntax (e.g., {{CONTEXT_FILE}}, {{PROJECT_DIR}}).
44
+ """
45
+ template_path = PROMPTS_DIR / name
46
+ text = template_path.read_text()
47
+ for key, value in kwargs.items():
48
+ text = text.replace(f'{{{{{key}}}}}', str(value))
49
+ return text
50
+
51
+
52
+ def has_confusion_signals(conversation_snippets):
53
+ """
54
+ Check if the conversation contains signals of confusion or user corrections.
55
+ Returns True if confusion signals are detected.
56
+ """
57
+ for snippet in conversation_snippets:
58
+ if snippet['role'] != 'user':
59
+ continue
60
+ content_lower = snippet['content'].lower()
61
+ for signal in CONFUSION_SIGNALS:
62
+ if signal in content_lower:
63
+ return True
64
+ return False
65
+
66
+
67
+ def get_file_diff(file_path, max_lines=50):
68
+ """
69
+ Get git diff for a file. Tries staged diff first, then unstaged.
70
+ Returns truncated diff string or empty string.
71
+ """
72
+ for diff_cmd in [
73
+ ['git', 'diff', 'HEAD', '--', file_path],
74
+ ['git', 'diff', '--', file_path],
75
+ ]:
76
+ try:
77
+ result = subprocess.run(
78
+ diff_cmd, capture_output=True, text=True, timeout=10
79
+ )
80
+ if result.stdout.strip():
81
+ lines = result.stdout.strip().split('\n')
82
+ # Skip the diff header (--- a/, +++ b/, @@ lines)
83
+ content_lines = [l for l in lines if not l.startswith('diff ') and
84
+ not l.startswith('index ') and not l.startswith('--- ') and
85
+ not l.startswith('+++ ')]
86
+ if len(content_lines) > max_lines:
87
+ content_lines = content_lines[:max_lines] + [f'... ({len(lines) - max_lines} more lines)']
88
+ return '\n'.join(content_lines)
89
+ except (subprocess.TimeoutExpired, FileNotFoundError):
90
+ continue
91
+ return ''
92
+
93
+
94
+ def read_rule_content(rule_name):
95
+ """
96
+ Read a rule file and return its content after YAML frontmatter.
97
+ Searches in .claude/rules/ directory.
98
+ """
99
+ # Search for the rule file
100
+ rules_dir = Path('.claude/rules')
101
+ if not rules_dir.exists():
102
+ return ''
103
+
104
+ # Find the file - could be at any depth
105
+ matches = list(rules_dir.rglob(rule_name))
106
+ if not matches:
107
+ return ''
108
+
109
+ try:
110
+ text = matches[0].read_text()
111
+ except IOError:
112
+ return ''
113
+
114
+ # Strip YAML frontmatter
115
+ if text.startswith('---'):
116
+ end = text.find('---', 3)
117
+ if end != -1:
118
+ text = text[end + 3:].strip()
119
+
120
+ return text
121
+
122
+
123
+ def get_stale_rules():
124
+ """
125
+ Run `codeyam memory status` and parse the output to find stale rules.
126
+ Returns a list of dicts with rule info, or empty list if none stale.
127
+ """
128
+ try:
129
+ # Use the local codeyam CLI via node to avoid PATH issues
130
+ project_dir = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
131
+ codeyam_js = os.path.join(project_dir, 'codeyam-cli', 'dist', 'codeyam-cli', 'src', 'codeyam-cli.js')
132
+ if os.path.exists(codeyam_js):
133
+ cmd = ['node', '--no-warnings', codeyam_js, 'memory', 'status']
134
+ else:
135
+ cmd = ['codeyam', 'memory', 'status']
136
+ result = subprocess.run(
137
+ cmd,
138
+ capture_output=True,
139
+ text=True,
140
+ timeout=30
141
+ )
142
+ output = result.stdout
143
+
144
+ # Check if there are stale rules
145
+ if 'Found' not in output or 'stale rule' not in output:
146
+ return []
147
+
148
+ # Parse the output to extract stale rule information
149
+ stale_rules = []
150
+ lines = output.split('\n')
151
+ i = 0
152
+ while i < len(lines):
153
+ line = lines[i].strip()
154
+ # Look for rule filenames (lines that end with .md and aren't indented much)
155
+ if line.endswith('.md') and not line.startswith('Rule') and not line.startswith('Newest'):
156
+ rule_info = {'name': line}
157
+ # Look for the next few lines for details
158
+ for j in range(i + 1, min(i + 4, len(lines))):
159
+ detail = lines[j].strip()
160
+ if detail.startswith('Rule timestamp:'):
161
+ rule_info['rule_timestamp'] = detail.replace('Rule timestamp:', '').strip()
162
+ elif detail.startswith('Newest file:'):
163
+ rule_info['newest_file'] = detail.replace('Newest file:', '').strip()
164
+ elif detail.startswith('File modified:'):
165
+ rule_info['file_modified'] = detail.replace('File modified:', '').strip()
166
+ # Get diff for the changed file
167
+ if rule_info.get('newest_file'):
168
+ rule_info['diff'] = get_file_diff(rule_info['newest_file'])
169
+
170
+ # Get inline rule content
171
+ rule_info['rule_content'] = read_rule_content(rule_info['name'])
172
+
173
+ stale_rules.append(rule_info)
174
+ i += 1
175
+
176
+ return stale_rules
177
+ except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
178
+ return []
179
+
180
+
181
+ def get_conversation_context(transcript_path, last_line):
182
+ """
183
+ Extract conversation snippets and modified files from transcript for rule review.
184
+ Returns (user_turn_count, conversation_snippets, modified_files, current_line_count)
185
+ """
186
+ try:
187
+ with open(transcript_path, 'r') as f:
188
+ all_lines = f.readlines()
189
+ except IOError:
190
+ return 0, [], set(), 0
191
+
192
+ current_line_count = len(all_lines)
193
+ new_lines = all_lines[last_line:]
194
+
195
+ if not new_lines:
196
+ return 0, [], set(), current_line_count
197
+
198
+ user_turn_count = 0
199
+ conversation_snippets = []
200
+ modified_files = set()
201
+
202
+ for line in new_lines:
203
+ try:
204
+ obj = json.loads(line)
205
+ msg_type = obj.get('type')
206
+
207
+ if msg_type not in ('user', 'assistant'):
208
+ continue
209
+
210
+ message = obj.get('message', {})
211
+ content = message.get('content', '')
212
+ is_external_user = msg_type == 'user' and obj.get('userType') == 'external'
213
+
214
+ # Handle string content
215
+ if isinstance(content, str):
216
+ if obj.get('isMeta') or len(content) < 20:
217
+ continue
218
+ if content.startswith('[{') or '<tool_result' in content:
219
+ continue
220
+
221
+ if is_external_user and not content.startswith('<'):
222
+ user_turn_count += 1
223
+
224
+ conversation_snippets.append({
225
+ 'role': message.get('role', msg_type),
226
+ 'content': content
227
+ })
228
+
229
+ elif isinstance(content, list):
230
+ for item in content:
231
+ if not isinstance(item, dict):
232
+ continue
233
+
234
+ # Extract text snippets
235
+ if item.get('type') == 'text':
236
+ text = item.get('text', '')
237
+ if len(text) > 20:
238
+ conversation_snippets.append({
239
+ 'role': message.get('role', msg_type),
240
+ 'content': text
241
+ })
242
+
243
+ # Track file modifications from tool_use entries
244
+ if item.get('type') == 'tool_use':
245
+ tool_name = item.get('name', '')
246
+ tool_input = item.get('input', {})
247
+ if tool_name in ('Edit', 'Write') and tool_input.get('file_path'):
248
+ modified_files.add((tool_input['file_path'], tool_name))
249
+
250
+ except (json.JSONDecodeError, KeyError):
251
+ continue
252
+
253
+ return user_turn_count, conversation_snippets, modified_files, current_line_count
254
+
255
+
256
+ def has_assistant_messages(transcript_path, start_line):
257
+ """
258
+ Check if there are any assistant messages in the transcript after start_line.
259
+ Used to confirm Claude actually started responding before treating a gap as an interruption.
260
+ """
261
+ try:
262
+ with open(transcript_path, 'r') as f:
263
+ all_lines = f.readlines()
264
+ except IOError:
265
+ return False
266
+
267
+ for line in all_lines[start_line:]:
268
+ try:
269
+ obj = json.loads(line)
270
+ if obj.get('type') == 'assistant':
271
+ return True
272
+ except (json.JSONDecodeError, KeyError):
273
+ continue
274
+
275
+ return False
276
+
277
+
278
+ def build_stale_rules_context(stale_rules):
279
+ """Build context content for stale rules review."""
280
+ parts = []
281
+ parts.append("# Reflection Step\n")
282
+ parts.append("Please review the stale rules below to determine if any need updating based on recent code changes.\n")
283
+ parts.append("Please show your thinking regarding the stale rules.\n")
284
+ parts.append("## Stale Rules to Review\n")
285
+ parts.append("The following rules have files that changed since the rule was last reviewed.")
286
+ parts.append("For each rule, review the rule content and the diff of changes, then:")
287
+ parts.append("1. Determine if the rule content needs updating based on the code changes")
288
+ parts.append("2. Update the rule if needed")
289
+ parts.append("3. ALWAYS update the timestamp (run `codeyam memory touch`)\n")
290
+
291
+ for rule in stale_rules:
292
+ parts.append(f"### {rule['name']}")
293
+ if rule.get('rule_content'):
294
+ parts.append(f" Rule content:")
295
+ for line in rule['rule_content'].split('\n'):
296
+ parts.append(f" {line}")
297
+ if rule.get('newest_file'):
298
+ parts.append(f" Changed file: {rule['newest_file']}")
299
+ if rule.get('file_modified'):
300
+ parts.append(f" File modified: {rule['file_modified']}")
301
+ if rule.get('rule_timestamp'):
302
+ parts.append(f" Rule timestamp: {rule['rule_timestamp']}")
303
+ if rule.get('diff'):
304
+ parts.append(f" Changes:")
305
+ for line in rule['diff'].split('\n'):
306
+ parts.append(f" {line}")
307
+ parts.append("")
308
+
309
+ return '\n'.join(parts)
310
+
311
+
312
+ def build_conversation_context(conversation_snippets, modified_files):
313
+ """Build context content for conversation review."""
314
+ parts = []
315
+ parts.append("## Conversation Review\n")
316
+ parts.append(
317
+ "Review this session for rule-worthy learnings: architectural decisions, tribal knowledge, "
318
+ "confusion, mistakes, or corrections that future sessions would benefit from knowing.\n"
319
+ )
320
+
321
+ # Load guidance from shared template file
322
+ guidance = (PROMPTS_DIR / 'conversation-guidance.txt').read_text()
323
+ parts.append(guidance)
324
+
325
+ if modified_files:
326
+ parts.append("Files modified this session:")
327
+ for file_path, tool_name in sorted(modified_files):
328
+ parts.append(f"- {file_path} ({tool_name})")
329
+ parts.append("")
330
+
331
+ parts.append("### Session transcript\n")
332
+ summary_lines = []
333
+ for snippet in conversation_snippets:
334
+ role = snippet['role']
335
+ content = snippet['content'].replace('\n', ' ')
336
+ summary_lines.append(f"[{role}]: {content}")
337
+ parts.append('\n'.join(summary_lines))
338
+
339
+ return '\n'.join(parts)
340
+
341
+
342
+ def build_interruption_context(conversation_snippets, follow_up_prompt, modified_files):
343
+ """Build context content for an interrupted session review."""
344
+ parts = []
345
+ parts.append("## Interruption Review\n")
346
+ parts.append(
347
+ "The user interrupted Claude mid-response — a strong signal of confusion or misunderstanding. "
348
+ "Review the interrupted conversation and the user's follow-up to identify rule-worthy learnings.\n"
349
+ )
350
+
351
+ # Load guidance from shared template file
352
+ guidance = (PROMPTS_DIR / 'conversation-guidance.txt').read_text()
353
+ parts.append(guidance)
354
+
355
+ if modified_files:
356
+ parts.append("Files modified this session:")
357
+ for file_path, tool_name in sorted(modified_files):
358
+ parts.append(f"- {file_path} ({tool_name})")
359
+ parts.append("")
360
+
361
+ parts.append("### Session transcript\n")
362
+ summary_lines = []
363
+ for snippet in conversation_snippets:
364
+ role = snippet['role']
365
+ content = snippet['content'].replace('\n', ' ')
366
+ summary_lines.append(f"[{role}]: {content}")
367
+ parts.append('\n'.join(summary_lines))
368
+
369
+ parts.append("")
370
+ parts.append("### User's follow-up after interruption\n")
371
+ parts.append(follow_up_prompt)
372
+
373
+ return '\n'.join(parts)
374
+
375
+
376
+ def spawn_claude_agent(prompt, log_file, project_dir):
377
+ """Spawn a detached claude -p agent as a background process."""
378
+ try:
379
+ log_fh = open(log_file, 'w')
380
+ env = os.environ.copy()
381
+ env['CODEYAM_RULE_AGENT'] = '1'
382
+ subprocess.Popen(
383
+ ['claude', '-p', prompt,
384
+ '--model', 'haiku',
385
+ '--output-format', 'stream-json', '--verbose',
386
+ '--allowedTools', 'Read,Edit,Write,Bash,Glob,Grep'],
387
+ cwd=project_dir,
388
+ stdout=log_fh,
389
+ stderr=log_fh,
390
+ env=env,
391
+ start_new_session=True,
392
+ )
393
+ except FileNotFoundError:
394
+ pass # claude CLI not available, skip silently
395
+
396
+
397
+ def read_marker(marker_file):
398
+ """
399
+ Read marker file. Returns (last_line, written_by_stop).
400
+ Format: "<line_count>" or "<line_count> stop" — the suffix distinguishes
401
+ whether the Stop hook wrote this marker (normal completion) vs the
402
+ UserPromptSubmit handler (interruption detection).
403
+ """
404
+ if marker_file.exists():
405
+ try:
406
+ text = marker_file.read_text().strip()
407
+ parts = text.split()
408
+ line_count = int(parts[0])
409
+ was_stop = len(parts) > 1 and parts[1] == 'stop'
410
+ return line_count, was_stop
411
+ except (ValueError, IOError):
412
+ pass
413
+ return 0, False
414
+
415
+
416
+ def write_marker(marker_file, line_count, source='submit'):
417
+ """Write marker with source tag. source is 'stop' or 'submit'."""
418
+ marker_file.write_text(f'{line_count} {source}')
419
+
420
+
421
+ def handle_stop(hook_input):
422
+ """
423
+ Handle the Stop hook event.
424
+ Reviews completed turns for stale rules and conversation confusion signals.
425
+
426
+ Important: the fast work (transcript parsing, marker update, conversation
427
+ agent spawn) runs first. The slow `get_stale_rules()` call (~10s) runs last
428
+ so the hook timeout doesn't kill us before the critical work is done.
429
+ """
430
+ session_id = hook_input.get('session_id', '')
431
+ transcript_path = hook_input.get('transcript_path', '')
432
+
433
+ if not session_id or not transcript_path:
434
+ return
435
+
436
+ marker_dir = Path('/tmp/claude-rule-markers')
437
+ marker_dir.mkdir(exist_ok=True)
438
+ marker_file = marker_dir / f'{session_id}.marker'
439
+
440
+ last_line, _ = read_marker(marker_file)
441
+
442
+ # Fast: parse transcript for conversation context
443
+ user_turn_count, conversation_snippets, modified_files, current_line_count = get_conversation_context(
444
+ transcript_path, last_line
445
+ )
446
+
447
+ # Update marker immediately so UserPromptSubmit knows Stop ran,
448
+ # even if we get killed during the slow stale rules check below
449
+ write_marker(marker_file, current_line_count, 'stop')
450
+
451
+ project_dir = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
452
+ invocation_ts = datetime.now().strftime('%Y%m%d-%H%M%S')
453
+ invocation_id = f'{session_id}-{invocation_ts}'
454
+
455
+ # Fast: spawn conversation review agent first
456
+ if len(conversation_snippets) > 0:
457
+ conv_context = build_conversation_context(conversation_snippets, modified_files)
458
+ conv_context_file = marker_dir / f'{invocation_id}-conversation.context'
459
+ conv_context_file.write_text(conv_context)
460
+
461
+ conv_log_file = marker_dir / f'{invocation_id}-conversation.log'
462
+ conv_notification_file = marker_dir / 'rule-notification-conversation.md'
463
+ conv_prompt = load_prompt_template(
464
+ 'conversation-prompt.txt',
465
+ CONTEXT_FILE=str(conv_context_file),
466
+ NOTIFICATION_FILE=str(conv_notification_file),
467
+ PROJECT_DIR=project_dir,
468
+ )
469
+ spawn_claude_agent(conv_prompt, conv_log_file, project_dir)
470
+
471
+ # Slow (~10s): check for stale rules last — if the hook timeout kills us
472
+ # here, the conversation agent and marker are already handled
473
+ stale_rules = get_stale_rules()
474
+
475
+ if len(stale_rules) > 0:
476
+ stale_context = build_stale_rules_context(stale_rules)
477
+ stale_context_file = marker_dir / f'{invocation_id}-stale.context'
478
+ stale_context_file.write_text(stale_context)
479
+
480
+ stale_log_file = marker_dir / f'{invocation_id}-stale.log'
481
+ stale_notification_file = marker_dir / 'rule-notification-stale.md'
482
+ stale_prompt = load_prompt_template(
483
+ 'stale-rules-prompt.txt',
484
+ CONTEXT_FILE=str(stale_context_file),
485
+ NOTIFICATION_FILE=str(stale_notification_file),
486
+ PROJECT_DIR=project_dir,
487
+ )
488
+ spawn_claude_agent(stale_prompt, stale_log_file, project_dir)
489
+
490
+
491
+ def handle_user_prompt_submit(hook_input):
492
+ """
493
+ Handle the UserPromptSubmit hook event.
494
+ Detects whether the previous turn was interrupted by checking if the Stop hook's
495
+ marker file is stale (transcript has lines beyond the marker). If so, spawns a
496
+ rule-reflection agent focused on the interruption.
497
+ """
498
+ session_id = hook_input.get('session_id', '')
499
+ transcript_path = hook_input.get('transcript_path', '')
500
+ follow_up_prompt = hook_input.get('prompt', '')
501
+
502
+ if not session_id or not transcript_path:
503
+ return
504
+
505
+ marker_dir = Path('/tmp/claude-rule-markers')
506
+ marker_dir.mkdir(exist_ok=True)
507
+ marker_file = marker_dir / f'{session_id}.marker'
508
+
509
+ last_line, was_stop = read_marker(marker_file)
510
+
511
+ # If the Stop hook already ran for the previous turn, this is a normal
512
+ # completion — not an interruption. The Stop hook tags the marker with
513
+ # 'stop' when it writes it.
514
+ if was_stop:
515
+ return
516
+
517
+ # Count current transcript lines
518
+ try:
519
+ with open(transcript_path, 'r') as f:
520
+ current_line_count = sum(1 for _ in f)
521
+ except IOError:
522
+ return
523
+
524
+ # Quick exit: if nothing new since the marker, definitely no interruption
525
+ if current_line_count <= last_line:
526
+ return
527
+
528
+ # Verify Claude actually started responding in the unprocessed lines.
529
+ # Guards against false positives on first prompt (no assistant yet) or
530
+ # if the user hit Escape before Claude produced any output.
531
+ if not has_assistant_messages(transcript_path, last_line):
532
+ return
533
+
534
+ # Interruption detected! Build context and spawn agent.
535
+ _, conversation_snippets, modified_files, _ = get_conversation_context(
536
+ transcript_path, last_line
537
+ )
538
+
539
+ if not conversation_snippets:
540
+ return
541
+
542
+ # Update marker so we don't re-process these lines
543
+ write_marker(marker_file, current_line_count, 'submit')
544
+
545
+ project_dir = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
546
+
547
+ invocation_ts = datetime.now().strftime('%Y%m%d-%H%M%S')
548
+ invocation_id = f'{session_id}-{invocation_ts}'
549
+
550
+ interruption_context = build_interruption_context(
551
+ conversation_snippets, follow_up_prompt, modified_files
552
+ )
553
+ context_file = marker_dir / f'{invocation_id}-interruption.context'
554
+ context_file.write_text(interruption_context)
555
+
556
+ log_file = marker_dir / f'{invocation_id}-interruption.log'
557
+ notification_file = marker_dir / 'rule-notification-interruption.md'
558
+ prompt = load_prompt_template(
559
+ 'interruption-prompt.txt',
560
+ CONTEXT_FILE=str(context_file),
561
+ NOTIFICATION_FILE=str(notification_file),
562
+ PROJECT_DIR=project_dir,
563
+ )
564
+ spawn_claude_agent(prompt, log_file, project_dir)
565
+
566
+
567
+ def main():
568
+ # Read hook input from stdin
569
+ try:
570
+ hook_input = json.load(sys.stdin)
571
+ except json.JSONDecodeError:
572
+ return
573
+
574
+ stop_hook_active = hook_input.get('stop_hook_active', False)
575
+
576
+ # Prevent infinite loops — stop_hook_active covers same-process recursion,
577
+ # env var covers spawned subagent sessions triggering the hook on exit
578
+ if stop_hook_active or os.environ.get('CODEYAM_RULE_AGENT'):
579
+ return
580
+
581
+ # Detect event type: UserPromptSubmit has a 'prompt' field, Stop does not
582
+ is_user_prompt = 'prompt' in hook_input
583
+ if is_user_prompt:
584
+ handle_user_prompt_submit(hook_input)
585
+ else:
586
+ handle_stop(hook_input)
587
+
588
+
589
+ if __name__ == '__main__':
590
+ main()