@codeyam/codeyam-cli 0.1.0-staging.2a88920 → 0.1.0-staging.4813bf3

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 (357) 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 +3 -3
  4. package/analyzer-template/packages/ai/index.ts +1 -0
  5. package/analyzer-template/packages/ai/package.json +2 -2
  6. package/analyzer-template/packages/ai/src/lib/analyzeScope.ts +23 -1
  7. package/analyzer-template/packages/ai/src/lib/astScopes/patterns/forInStatementHandler.ts +10 -17
  8. package/analyzer-template/packages/ai/src/lib/astScopes/processExpression.ts +101 -0
  9. package/analyzer-template/packages/ai/src/lib/astScopes/sharedPatterns.ts +28 -0
  10. package/analyzer-template/packages/ai/src/lib/astScopes/types.ts +6 -0
  11. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +330 -9
  12. package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/JavascriptFrameworkManager.ts +5 -1
  13. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +11 -2
  14. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.ts +2 -2
  15. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/coerceObjectsToPrimitivesBySchema.ts +70 -0
  16. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.ts +126 -11
  17. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.ts +20 -1
  18. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.ts +84 -19
  19. package/analyzer-template/packages/ai/src/lib/dataStructureChunking.ts +33 -15
  20. package/analyzer-template/packages/ai/src/lib/generateEntityDataStructure.ts +58 -3
  21. package/analyzer-template/packages/ai/src/lib/generateEntityScenarioData.ts +299 -5
  22. package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +38 -2
  23. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionals.ts +359 -142
  24. package/analyzer-template/packages/ai/src/lib/isolateScopes.ts +51 -3
  25. package/analyzer-template/packages/ai/src/lib/mergeJsonTypeDefinitions.ts +5 -0
  26. package/analyzer-template/packages/ai/src/lib/promptGenerators/collapseNullableObjects.ts +118 -0
  27. package/analyzer-template/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.ts +24 -4
  28. package/analyzer-template/packages/analyze/index.ts +2 -0
  29. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +69 -3
  30. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +5 -0
  31. package/analyzer-template/packages/analyze/src/lib/files/scenarios/TransformationTracer.ts +1315 -0
  32. package/analyzer-template/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.ts +4 -0
  33. package/analyzer-template/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.ts +9 -1
  34. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +194 -15
  35. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +260 -22
  36. package/analyzer-template/packages/analyze/src/lib/index.ts +1 -0
  37. package/analyzer-template/packages/database/package.json +1 -1
  38. package/analyzer-template/packages/database/src/lib/analysisBranchToDb.ts +1 -1
  39. package/analyzer-template/packages/database/src/lib/analysisToDb.ts +1 -1
  40. package/analyzer-template/packages/database/src/lib/branchToDb.ts +1 -1
  41. package/analyzer-template/packages/database/src/lib/commitBranchToDb.ts +1 -1
  42. package/analyzer-template/packages/database/src/lib/commitToDb.ts +1 -1
  43. package/analyzer-template/packages/database/src/lib/fileToDb.ts +1 -1
  44. package/analyzer-template/packages/database/src/lib/kysely/db.ts +6 -0
  45. package/analyzer-template/packages/database/src/lib/kysely/tables/debugReportsTable.ts +1 -1
  46. package/analyzer-template/packages/database/src/lib/kysely/tables/labsRequestsTable.ts +52 -0
  47. package/analyzer-template/packages/database/src/lib/projectToDb.ts +1 -1
  48. package/analyzer-template/packages/database/src/lib/saveFiles.ts +1 -1
  49. package/analyzer-template/packages/database/src/lib/scenarioToDb.ts +1 -1
  50. package/analyzer-template/packages/database/src/lib/userScenarioToDb.ts +1 -1
  51. package/analyzer-template/packages/github/dist/database/src/lib/analysisBranchToDb.js +1 -1
  52. package/analyzer-template/packages/github/dist/database/src/lib/analysisBranchToDb.js.map +1 -1
  53. package/analyzer-template/packages/github/dist/database/src/lib/analysisToDb.js +1 -1
  54. package/analyzer-template/packages/github/dist/database/src/lib/analysisToDb.js.map +1 -1
  55. package/analyzer-template/packages/github/dist/database/src/lib/branchToDb.js +1 -1
  56. package/analyzer-template/packages/github/dist/database/src/lib/branchToDb.js.map +1 -1
  57. package/analyzer-template/packages/github/dist/database/src/lib/commitBranchToDb.js +1 -1
  58. package/analyzer-template/packages/github/dist/database/src/lib/commitBranchToDb.js.map +1 -1
  59. package/analyzer-template/packages/github/dist/database/src/lib/commitToDb.js +1 -1
  60. package/analyzer-template/packages/github/dist/database/src/lib/commitToDb.js.map +1 -1
  61. package/analyzer-template/packages/github/dist/database/src/lib/fileToDb.js +1 -1
  62. package/analyzer-template/packages/github/dist/database/src/lib/fileToDb.js.map +1 -1
  63. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.d.ts +2 -0
  64. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.d.ts.map +1 -1
  65. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.js +3 -0
  66. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.js.map +1 -1
  67. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/debugReportsTable.d.ts +1 -1
  68. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/labsRequestsTable.d.ts +23 -0
  69. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/labsRequestsTable.d.ts.map +1 -0
  70. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/labsRequestsTable.js +35 -0
  71. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/labsRequestsTable.js.map +1 -0
  72. package/analyzer-template/packages/github/dist/database/src/lib/projectToDb.js +1 -1
  73. package/analyzer-template/packages/github/dist/database/src/lib/projectToDb.js.map +1 -1
  74. package/analyzer-template/packages/github/dist/database/src/lib/saveFiles.js +1 -1
  75. package/analyzer-template/packages/github/dist/database/src/lib/saveFiles.js.map +1 -1
  76. package/analyzer-template/packages/github/dist/database/src/lib/scenarioToDb.js +1 -1
  77. package/analyzer-template/packages/github/dist/database/src/lib/scenarioToDb.js.map +1 -1
  78. package/analyzer-template/packages/github/dist/types/src/types/ProjectMetadata.d.ts +7 -0
  79. package/analyzer-template/packages/github/dist/types/src/types/ProjectMetadata.d.ts.map +1 -1
  80. package/analyzer-template/packages/types/src/types/ProjectMetadata.ts +7 -0
  81. package/analyzer-template/packages/utils/dist/types/src/types/ProjectMetadata.d.ts +7 -0
  82. package/analyzer-template/packages/utils/dist/types/src/types/ProjectMetadata.d.ts.map +1 -1
  83. package/analyzer-template/project/constructMockCode.ts +36 -1
  84. package/analyzer-template/project/writeMockDataTsx.ts +174 -12
  85. package/analyzer-template/project/writeScenarioComponents.ts +60 -12
  86. package/analyzer-template/project/writeSimpleRoot.ts +21 -11
  87. package/background/src/lib/local/createLocalAnalyzer.js +1 -1
  88. package/background/src/lib/local/createLocalAnalyzer.js.map +1 -1
  89. package/background/src/lib/virtualized/project/constructMockCode.js +30 -1
  90. package/background/src/lib/virtualized/project/constructMockCode.js.map +1 -1
  91. package/background/src/lib/virtualized/project/writeMockDataTsx.js +156 -8
  92. package/background/src/lib/virtualized/project/writeMockDataTsx.js.map +1 -1
  93. package/background/src/lib/virtualized/project/writeScenarioComponents.js +60 -15
  94. package/background/src/lib/virtualized/project/writeScenarioComponents.js.map +1 -1
  95. package/background/src/lib/virtualized/project/writeSimpleRoot.js +21 -11
  96. package/background/src/lib/virtualized/project/writeSimpleRoot.js.map +1 -1
  97. package/codeyam-cli/scripts/apply-setup.js +180 -0
  98. package/codeyam-cli/scripts/apply-setup.js.map +1 -1
  99. package/codeyam-cli/src/cli.js +2 -0
  100. package/codeyam-cli/src/cli.js.map +1 -1
  101. package/codeyam-cli/src/codeyam-cli.js +18 -2
  102. package/codeyam-cli/src/codeyam-cli.js.map +1 -1
  103. package/codeyam-cli/src/commands/analyze.js +4 -2
  104. package/codeyam-cli/src/commands/analyze.js.map +1 -1
  105. package/codeyam-cli/src/commands/baseline.js +2 -0
  106. package/codeyam-cli/src/commands/baseline.js.map +1 -1
  107. package/codeyam-cli/src/commands/debug.js +2 -0
  108. package/codeyam-cli/src/commands/debug.js.map +1 -1
  109. package/codeyam-cli/src/commands/default.js +31 -20
  110. package/codeyam-cli/src/commands/default.js.map +1 -1
  111. package/codeyam-cli/src/commands/detect-universal-mocks.js +2 -0
  112. package/codeyam-cli/src/commands/detect-universal-mocks.js.map +1 -1
  113. package/codeyam-cli/src/commands/init.js +49 -257
  114. package/codeyam-cli/src/commands/init.js.map +1 -1
  115. package/codeyam-cli/src/commands/memory.js +17 -26
  116. package/codeyam-cli/src/commands/memory.js.map +1 -1
  117. package/codeyam-cli/src/commands/recapture.js +2 -0
  118. package/codeyam-cli/src/commands/recapture.js.map +1 -1
  119. package/codeyam-cli/src/commands/setup-sandbox.js +2 -0
  120. package/codeyam-cli/src/commands/setup-sandbox.js.map +1 -1
  121. package/codeyam-cli/src/commands/setup-simulations.js +284 -0
  122. package/codeyam-cli/src/commands/setup-simulations.js.map +1 -0
  123. package/codeyam-cli/src/commands/test-startup.js +2 -0
  124. package/codeyam-cli/src/commands/test-startup.js.map +1 -1
  125. package/codeyam-cli/src/commands/verify.js +14 -2
  126. package/codeyam-cli/src/commands/verify.js.map +1 -1
  127. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +128 -86
  128. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
  129. package/codeyam-cli/src/utils/analyzer.js +7 -0
  130. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  131. package/codeyam-cli/src/utils/backgroundServer.js +5 -0
  132. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  133. package/codeyam-cli/src/utils/generateReport.js +2 -2
  134. package/codeyam-cli/src/utils/install-skills.js +57 -54
  135. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  136. package/codeyam-cli/src/utils/labsAutoCheck.js +19 -0
  137. package/codeyam-cli/src/utils/labsAutoCheck.js.map +1 -0
  138. package/codeyam-cli/src/utils/progress.js +7 -0
  139. package/codeyam-cli/src/utils/progress.js.map +1 -1
  140. package/codeyam-cli/src/utils/queue/job.js +4 -0
  141. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  142. package/codeyam-cli/src/utils/requireSimulations.js +10 -0
  143. package/codeyam-cli/src/utils/requireSimulations.js.map +1 -0
  144. package/codeyam-cli/src/utils/ruleReflection/__tests__/confusionDetector.test.js +82 -0
  145. package/codeyam-cli/src/utils/ruleReflection/__tests__/confusionDetector.test.js.map +1 -0
  146. package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js +230 -0
  147. package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js.map +1 -0
  148. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/assertRules.js +67 -0
  149. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/assertRules.js.map +1 -0
  150. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/captureFixture.js +105 -0
  151. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/captureFixture.js.map +1 -0
  152. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/loadCapturedFixture.js +34 -0
  153. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/loadCapturedFixture.js.map +1 -0
  154. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/runClaude.js +162 -0
  155. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/runClaude.js.map +1 -0
  156. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/setupTempProject.js +75 -0
  157. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/setupTempProject.js.map +1 -0
  158. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js +378 -0
  159. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js.map +1 -0
  160. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js +115 -0
  161. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js.map +1 -0
  162. package/codeyam-cli/src/utils/ruleReflection/__tests__/transcriptParser.test.js +127 -0
  163. package/codeyam-cli/src/utils/ruleReflection/__tests__/transcriptParser.test.js.map +1 -0
  164. package/codeyam-cli/src/utils/ruleReflection/confusionDetector.js +50 -0
  165. package/codeyam-cli/src/utils/ruleReflection/confusionDetector.js.map +1 -0
  166. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js +116 -0
  167. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js.map +1 -0
  168. package/codeyam-cli/src/utils/ruleReflection/index.js +5 -0
  169. package/codeyam-cli/src/utils/ruleReflection/index.js.map +1 -0
  170. package/codeyam-cli/src/utils/ruleReflection/promptBuilder.js +44 -0
  171. package/codeyam-cli/src/utils/ruleReflection/promptBuilder.js.map +1 -0
  172. package/codeyam-cli/src/utils/ruleReflection/transcriptParser.js +85 -0
  173. package/codeyam-cli/src/utils/ruleReflection/transcriptParser.js.map +1 -0
  174. package/codeyam-cli/src/utils/ruleReflection/types.js +5 -0
  175. package/codeyam-cli/src/utils/ruleReflection/types.js.map +1 -0
  176. package/codeyam-cli/src/utils/rules/__tests__/ruleState.test.js +293 -0
  177. package/codeyam-cli/src/utils/rules/__tests__/ruleState.test.js.map +1 -0
  178. package/codeyam-cli/src/utils/rules/index.js +1 -0
  179. package/codeyam-cli/src/utils/rules/index.js.map +1 -1
  180. package/codeyam-cli/src/utils/rules/parser.js +2 -25
  181. package/codeyam-cli/src/utils/rules/parser.js.map +1 -1
  182. package/codeyam-cli/src/utils/rules/ruleState.js +150 -0
  183. package/codeyam-cli/src/utils/rules/ruleState.js.map +1 -0
  184. package/codeyam-cli/src/utils/rules/staleness.js +16 -11
  185. package/codeyam-cli/src/utils/rules/staleness.js.map +1 -1
  186. package/codeyam-cli/src/utils/serverState.js +37 -10
  187. package/codeyam-cli/src/utils/serverState.js.map +1 -1
  188. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +21 -44
  189. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
  190. package/codeyam-cli/src/webserver/app/lib/database.js +15 -3
  191. package/codeyam-cli/src/webserver/app/lib/database.js.map +1 -1
  192. package/codeyam-cli/src/webserver/backgroundServer.js +24 -0
  193. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  194. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-CA3JxPb7.js +1 -0
  195. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-DsN1wKrm.js → EntityItem-B86KKU7e.js} +1 -1
  196. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeBadge-DLqD3qNt.js → EntityTypeBadge-B5ctlSYt.js} +1 -1
  197. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-Ba2JVPzP.js → EntityTypeIcon-BqY8gDAW.js} +1 -1
  198. package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-C8lyxW9k.js → InlineSpinner-ClaLpuOo.js} +1 -1
  199. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-aht4aafF.js → InteractivePreview-BDhPilK7.js} +2 -2
  200. package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-CVtiBnY5.js → LibraryFunctionPreview-VeqEBv9v.js} +1 -1
  201. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-B0GLXMsr.js → LoadingDots-Bs7Nn1Jr.js} +1 -1
  202. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-xgeCVgSM.js → LogViewer-Bm3PmcCz.js} +1 -1
  203. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-OApQuNyq.js → ReportIssueModal-CgMEzchJ.js} +3 -8
  204. package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-DuDvi0jm.js → SafeScreenshot-Gq3Ocjo6.js} +1 -1
  205. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-DzccYyI8.js → ScenarioViewer-CBui0id_.js} +2 -2
  206. package/codeyam-cli/src/webserver/build/client/assets/{TruncatedFilePath-DyFZkK0l.js → TruncatedFilePath-CiwXDxLh.js} +1 -1
  207. package/codeyam-cli/src/webserver/build/client/assets/{_index-BwqWJOgH.js → _index-B3TDXxnk.js} +1 -1
  208. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BwavGCpm.js → activity.(_tab)-BtBFH820.js} +6 -11
  209. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-CN61MOMa.js +11 -0
  210. package/codeyam-cli/src/webserver/build/client/assets/api.agent-transcripts-l0sNRNKZ.js +1 -0
  211. package/codeyam-cli/src/webserver/build/client/assets/api.labs-unlock-l0sNRNKZ.js +1 -0
  212. package/codeyam-cli/src/webserver/build/client/assets/api.save-fixture-l0sNRNKZ.js +1 -0
  213. package/codeyam-cli/src/webserver/build/client/assets/book-open-PttOB2SF.js +6 -0
  214. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-Cx24_aWc.js → chevron-down-TJp6ofnp.js} +1 -1
  215. package/codeyam-cli/src/webserver/build/client/assets/{chunk-EPOLDU6W-CXRTFQ3F.js → chunk-JZWAC4HX-JE9ZIoBl.js} +12 -12
  216. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-BOARzkeR.js → circle-check-CXhHQYrI.js} +1 -1
  217. package/codeyam-cli/src/webserver/build/client/assets/copy-6y9ALfGT.js +11 -0
  218. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-BdhJEx6B.js → createLucideIcon-Ca9fAY46.js} +1 -1
  219. package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-BBnGWYga.js → dev.empty-C0epRiVn.js} +1 -1
  220. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BJUiQqZF.js → entity._sha._-BVnB8a9L.js} +10 -10
  221. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-DavjRmOY.js → entity._sha.scenarios._scenarioId.fullscreen-CBoafmVs.js} +1 -1
  222. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-D1T4TGjf.js → entity._sha_.create-scenario-DGgZjdFg.js} +1 -1
  223. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-CTBG2mmz.js → entity._sha_.edit._scenarioId-38yPijoD.js} +1 -1
  224. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-CS2cb_eZ.js → entry.client-BSHEfydn.js} +1 -1
  225. package/codeyam-cli/src/webserver/build/client/assets/{fileTableUtils-DMJ7zii9.js → fileTableUtils-DCPhhSMo.js} +1 -1
  226. package/codeyam-cli/src/webserver/build/client/assets/{files-CJ6lTdTA.js → files-0N0YJQv7.js} +1 -1
  227. package/codeyam-cli/src/webserver/build/client/assets/{git-CPTZZ-JZ.js → git-DXnyr8uP.js} +1 -1
  228. package/codeyam-cli/src/webserver/build/client/assets/globals-CKT08Djd.css +1 -0
  229. package/codeyam-cli/src/webserver/build/client/assets/{index-lzqtyFU8.js → index-CcsFv748.js} +1 -1
  230. package/codeyam-cli/src/webserver/build/client/assets/{index-B1h680n5.js → index-ChN9-fAY.js} +1 -1
  231. package/codeyam-cli/src/webserver/build/client/assets/labs-BLJ7HxOC.js +1 -0
  232. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-B7B9V-bu.js → loader-circle-CTqLEAGU.js} +1 -1
  233. package/codeyam-cli/src/webserver/build/client/assets/manifest-b171b9d3.js +1 -0
  234. package/codeyam-cli/src/webserver/build/client/assets/memory-CCQd4aZA.js +78 -0
  235. package/codeyam-cli/src/webserver/build/client/assets/pause-D6vreykR.js +11 -0
  236. package/codeyam-cli/src/webserver/build/client/assets/root-CHhiHoo_.js +62 -0
  237. package/codeyam-cli/src/webserver/build/client/assets/{search-CxXUmBSd.js → search-B8VUL8nl.js} +1 -1
  238. package/codeyam-cli/src/webserver/build/client/assets/settings-BejnUJ6R.js +1 -0
  239. package/codeyam-cli/src/webserver/build/client/assets/{simulations-DwFIBT09.js → simulations-CPoAg7Zo.js} +1 -1
  240. package/codeyam-cli/src/webserver/build/client/assets/terminal-BrCP7uQo.js +11 -0
  241. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-B6LgvRJg.js → triangle-alert-BZz2NjYa.js} +1 -1
  242. package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-C1v1PQzo.js → useCustomSizes-DNwUduNu.js} +1 -1
  243. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-aSv48UbS.js → useLastLogLine-COky1GVF.js} +1 -1
  244. package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-DYxHZQuP.js → useReportContext-CpZgwliL.js} +1 -1
  245. package/codeyam-cli/src/webserver/build/client/assets/{useToast-mBRpZPiu.js → useToast-Bv9JFvUO.js} +1 -1
  246. package/codeyam-cli/src/webserver/build/server/assets/{index-BM6TDT1Y.js → index-8Fv-lH1-.js} +1 -1
  247. package/codeyam-cli/src/webserver/build/server/assets/server-build-Akn3iYFP.js +257 -0
  248. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  249. package/codeyam-cli/src/webserver/build-info.json +5 -5
  250. package/codeyam-cli/templates/{codeyam:debug.md → codeyam-debug.md} +1 -1
  251. package/codeyam-cli/templates/codeyam-diagnose.md +481 -0
  252. package/codeyam-cli/templates/codeyam-memory-hook.sh +19 -20
  253. package/codeyam-cli/templates/codeyam-memory.md +392 -0
  254. package/codeyam-cli/templates/{codeyam:new-rule.md → codeyam-new-rule.md} +2 -2
  255. package/codeyam-cli/templates/{codeyam:setup.md → codeyam-setup.md} +13 -1
  256. package/codeyam-cli/templates/{codeyam:sim.md → codeyam-sim.md} +1 -1
  257. package/codeyam-cli/templates/{codeyam:test.md → codeyam-test.md} +1 -1
  258. package/codeyam-cli/templates/{codeyam:verify.md → codeyam-verify.md} +1 -1
  259. package/codeyam-cli/templates/rule-notification-hook.py +56 -0
  260. package/codeyam-cli/templates/rule-reflection-hook.py +554 -87
  261. package/codeyam-cli/templates/rules-instructions.md +63 -24
  262. package/package.json +2 -2
  263. package/packages/ai/index.js +1 -1
  264. package/packages/ai/index.js.map +1 -1
  265. package/packages/ai/src/lib/analyzeScope.js +21 -1
  266. package/packages/ai/src/lib/analyzeScope.js.map +1 -1
  267. package/packages/ai/src/lib/astScopes/patterns/forInStatementHandler.js +10 -14
  268. package/packages/ai/src/lib/astScopes/patterns/forInStatementHandler.js.map +1 -1
  269. package/packages/ai/src/lib/astScopes/processExpression.js +78 -1
  270. package/packages/ai/src/lib/astScopes/processExpression.js.map +1 -1
  271. package/packages/ai/src/lib/astScopes/sharedPatterns.js +25 -0
  272. package/packages/ai/src/lib/astScopes/sharedPatterns.js.map +1 -1
  273. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +262 -8
  274. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  275. package/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/JavascriptFrameworkManager.js +5 -1
  276. package/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/JavascriptFrameworkManager.js.map +1 -1
  277. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +11 -2
  278. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  279. package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js +2 -2
  280. package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js.map +1 -1
  281. package/packages/ai/src/lib/dataStructure/helpers/coerceObjectsToPrimitivesBySchema.js +63 -0
  282. package/packages/ai/src/lib/dataStructure/helpers/coerceObjectsToPrimitivesBySchema.js.map +1 -0
  283. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js +113 -11
  284. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js.map +1 -1
  285. package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js +15 -1
  286. package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js.map +1 -1
  287. package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js +78 -17
  288. package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js.map +1 -1
  289. package/packages/ai/src/lib/dataStructureChunking.js +26 -11
  290. package/packages/ai/src/lib/dataStructureChunking.js.map +1 -1
  291. package/packages/ai/src/lib/generateEntityDataStructure.js +46 -2
  292. package/packages/ai/src/lib/generateEntityDataStructure.js.map +1 -1
  293. package/packages/ai/src/lib/generateEntityScenarioData.js +212 -3
  294. package/packages/ai/src/lib/generateEntityScenarioData.js.map +1 -1
  295. package/packages/ai/src/lib/generateExecutionFlows.js +16 -2
  296. package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
  297. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js +242 -81
  298. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js.map +1 -1
  299. package/packages/ai/src/lib/isolateScopes.js +39 -3
  300. package/packages/ai/src/lib/isolateScopes.js.map +1 -1
  301. package/packages/ai/src/lib/mergeJsonTypeDefinitions.js +5 -0
  302. package/packages/ai/src/lib/mergeJsonTypeDefinitions.js.map +1 -1
  303. package/packages/ai/src/lib/promptGenerators/collapseNullableObjects.js +97 -0
  304. package/packages/ai/src/lib/promptGenerators/collapseNullableObjects.js.map +1 -0
  305. package/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.js +17 -2
  306. package/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.js.map +1 -1
  307. package/packages/analyze/index.js +1 -0
  308. package/packages/analyze/index.js.map +1 -1
  309. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +52 -2
  310. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  311. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +3 -0
  312. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  313. package/packages/analyze/src/lib/files/scenarios/TransformationTracer.js +880 -0
  314. package/packages/analyze/src/lib/files/scenarios/TransformationTracer.js.map +1 -0
  315. package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js +5 -1
  316. package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js.map +1 -1
  317. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js +9 -1
  318. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js.map +1 -1
  319. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +116 -13
  320. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  321. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +231 -22
  322. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  323. package/packages/analyze/src/lib/index.js +1 -0
  324. package/packages/analyze/src/lib/index.js.map +1 -1
  325. package/packages/database/src/lib/analysisBranchToDb.js +1 -1
  326. package/packages/database/src/lib/analysisBranchToDb.js.map +1 -1
  327. package/packages/database/src/lib/analysisToDb.js +1 -1
  328. package/packages/database/src/lib/analysisToDb.js.map +1 -1
  329. package/packages/database/src/lib/branchToDb.js +1 -1
  330. package/packages/database/src/lib/branchToDb.js.map +1 -1
  331. package/packages/database/src/lib/commitBranchToDb.js +1 -1
  332. package/packages/database/src/lib/commitBranchToDb.js.map +1 -1
  333. package/packages/database/src/lib/commitToDb.js +1 -1
  334. package/packages/database/src/lib/commitToDb.js.map +1 -1
  335. package/packages/database/src/lib/fileToDb.js +1 -1
  336. package/packages/database/src/lib/fileToDb.js.map +1 -1
  337. package/packages/database/src/lib/kysely/db.js +3 -0
  338. package/packages/database/src/lib/kysely/db.js.map +1 -1
  339. package/packages/database/src/lib/kysely/tables/labsRequestsTable.js +35 -0
  340. package/packages/database/src/lib/kysely/tables/labsRequestsTable.js.map +1 -0
  341. package/packages/database/src/lib/projectToDb.js +1 -1
  342. package/packages/database/src/lib/projectToDb.js.map +1 -1
  343. package/packages/database/src/lib/saveFiles.js +1 -1
  344. package/packages/database/src/lib/saveFiles.js.map +1 -1
  345. package/packages/database/src/lib/scenarioToDb.js +1 -1
  346. package/packages/database/src/lib/scenarioToDb.js.map +1 -1
  347. package/scripts/finalize-analyzer.cjs +8 -76
  348. package/codeyam-cli/src/webserver/build/client/assets/copy-Bb-80kDT.js +0 -6
  349. package/codeyam-cli/src/webserver/build/client/assets/file-code-Dhef1kWN.js +0 -6
  350. package/codeyam-cli/src/webserver/build/client/assets/globals-D3yhhV8x.css +0 -1
  351. package/codeyam-cli/src/webserver/build/client/assets/manifest-a78b90a2.js +0 -1
  352. package/codeyam-cli/src/webserver/build/client/assets/memory--GCbFsBE.js +0 -92
  353. package/codeyam-cli/src/webserver/build/client/assets/root-eVAaavTS.js +0 -62
  354. package/codeyam-cli/src/webserver/build/client/assets/settings-CS5f3WzT.js +0 -1
  355. package/codeyam-cli/src/webserver/build/server/assets/server-build-dYC34MHw.js +0 -257
  356. package/codeyam-cli/templates/codeyam:diagnose.md +0 -803
  357. package/codeyam-cli/templates/codeyam:memory.md +0 -341
@@ -2,64 +2,229 @@
2
2
  """
3
3
  Rule reflection hook for Claude Code.
4
4
 
5
- Reads the session transcript to find new conversation since last check,
6
- then prompts Claude to consider rule updates if there was substantive content.
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).
7
15
  """
8
16
 
9
17
  import json
18
+ import os
19
+ import subprocess
10
20
  import sys
21
+ from datetime import datetime
11
22
  from pathlib import Path
12
23
 
13
- MIN_USER_TURNS = 5 # Fire after N user turns
24
+ MIN_USER_TURNS = 3 # Minimum user turns before checking for confusion
14
25
 
15
- def main():
16
- # Read hook input from stdin
17
- try:
18
- hook_input = json.load(sys.stdin)
19
- except json.JSONDecodeError:
20
- # No valid input, exit silently
21
- return
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
+ ]
22
35
 
23
- session_id = hook_input.get('session_id', '')
24
- transcript_path = hook_input.get('transcript_path', '')
25
- stop_hook_active = hook_input.get('stop_hook_active', False)
36
+ # Prompt templates directory — installed alongside this hook in .codeyam/bin/prompts/
37
+ PROMPTS_DIR = Path(__file__).parent / 'prompts'
26
38
 
27
- # Prevent infinite loops - if we're already in a stop hook continuation, exit silently
28
- if stop_hook_active:
29
- return
30
39
 
31
- if not session_id or not transcript_path:
32
- return
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
33
50
 
34
- # Marker file tracks last checked line per session
35
- marker_dir = Path('/tmp/claude-rule-markers')
36
- marker_dir.mkdir(exist_ok=True)
37
- marker_file = marker_dir / f'{session_id}.marker'
38
51
 
39
- # Read last checked line
40
- last_line = 0
41
- if marker_file.exists():
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
+ ]:
42
76
  try:
43
- last_line = int(marker_file.read_text().strip())
44
- except (ValueError, IOError):
45
- last_line = 0
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 load_memory_settings():
124
+ """
125
+ Load memory settings from .codeyam/config.json.
126
+ Returns dict with safe defaults when absent or malformed.
127
+ """
128
+ defaults = {
129
+ 'conversationReflection': True,
130
+ 'ruleMaintenance': True,
131
+ 'promptModel': 'haiku',
132
+ }
133
+ try:
134
+ project_dir = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
135
+ config_path = os.path.join(project_dir, '.codeyam', 'config.json')
136
+ with open(config_path, 'r') as f:
137
+ config = json.load(f)
138
+ memory = config.get('memory', {})
139
+ if not isinstance(memory, dict):
140
+ return defaults
141
+ return {
142
+ 'conversationReflection': memory.get('conversationReflection', True),
143
+ 'ruleMaintenance': memory.get('ruleMaintenance', True),
144
+ 'promptModel': memory.get('promptModel', 'haiku'),
145
+ }
146
+ except (IOError, json.JSONDecodeError, KeyError):
147
+ return defaults
148
+
149
+
150
+ def get_stale_rules():
151
+ """
152
+ Run `codeyam memory status` and parse the output to find stale rules.
153
+ Returns a list of dicts with rule info, or empty list if none stale.
154
+ """
155
+ try:
156
+ # Use the local codeyam CLI via node to avoid PATH issues
157
+ project_dir = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
158
+ codeyam_js = os.path.join(project_dir, 'codeyam-cli', 'dist', 'codeyam-cli', 'src', 'codeyam-cli.js')
159
+ if os.path.exists(codeyam_js):
160
+ cmd = ['node', '--no-warnings', codeyam_js, 'memory', 'status']
161
+ else:
162
+ cmd = ['codeyam', 'memory', 'status']
163
+ result = subprocess.run(
164
+ cmd,
165
+ capture_output=True,
166
+ text=True,
167
+ timeout=30
168
+ )
169
+ output = result.stdout
170
+
171
+ # Check if there are stale rules
172
+ if 'Found' not in output or 'stale rule' not in output:
173
+ return []
174
+
175
+ # Parse the output to extract stale rule information
176
+ stale_rules = []
177
+ lines = output.split('\n')
178
+ i = 0
179
+ while i < len(lines):
180
+ line = lines[i].strip()
181
+ # Look for rule filenames (lines that end with .md and aren't indented much)
182
+ if line.endswith('.md') and not line.startswith('Rule') and not line.startswith('Newest'):
183
+ rule_info = {'name': line}
184
+ # Look for the next few lines for details
185
+ for j in range(i + 1, min(i + 4, len(lines))):
186
+ detail = lines[j].strip()
187
+ if detail.startswith('Last audited:'):
188
+ rule_info['last_audited'] = detail.replace('Last audited:', '').strip()
189
+ elif detail.startswith('Newest file:'):
190
+ rule_info['newest_file'] = detail.replace('Newest file:', '').strip()
191
+ elif detail.startswith('File modified:'):
192
+ rule_info['file_modified'] = detail.replace('File modified:', '').strip()
193
+ # Get diff for the changed file
194
+ if rule_info.get('newest_file'):
195
+ rule_info['diff'] = get_file_diff(rule_info['newest_file'])
46
196
 
47
- # Read transcript and get new lines
197
+ # Get inline rule content
198
+ rule_info['rule_content'] = read_rule_content(rule_info['name'])
199
+
200
+ stale_rules.append(rule_info)
201
+ i += 1
202
+
203
+ return stale_rules
204
+ except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
205
+ return []
206
+
207
+
208
+ def get_conversation_context(transcript_path, last_line):
209
+ """
210
+ Extract conversation snippets and modified files from transcript for rule review.
211
+ Returns (user_turn_count, conversation_snippets, modified_files, current_line_count)
212
+ """
48
213
  try:
49
214
  with open(transcript_path, 'r') as f:
50
215
  all_lines = f.readlines()
51
216
  except IOError:
52
- return
217
+ return 0, [], set(), 0
53
218
 
54
219
  current_line_count = len(all_lines)
55
220
  new_lines = all_lines[last_line:]
56
221
 
57
222
  if not new_lines:
58
- return
223
+ return 0, [], set(), current_line_count
59
224
 
60
- # Count user turns and extract conversation content from new lines
61
225
  user_turn_count = 0
62
226
  conversation_snippets = []
227
+ modified_files = set()
63
228
 
64
229
  for line in new_lines:
65
230
  try:
@@ -75,86 +240,388 @@ def main():
75
240
 
76
241
  # Handle string content
77
242
  if isinstance(content, str):
78
- # Skip meta messages and very short content
79
243
  if obj.get('isMeta') or len(content) < 20:
80
244
  continue
81
- # Skip tool results (they show up as user messages)
82
245
  if content.startswith('[{') or '<tool_result' in content:
83
246
  continue
84
247
 
85
- # Count substantive user turns
86
248
  if is_external_user and not content.startswith('<'):
87
249
  user_turn_count += 1
88
250
 
89
251
  conversation_snippets.append({
90
252
  'role': message.get('role', msg_type),
91
- 'content': content[:500] # Truncate long messages
253
+ 'content': content
92
254
  })
93
255
 
94
- # Handle array content (tool calls, etc.)
95
256
  elif isinstance(content, list):
96
257
  for item in content:
97
- if isinstance(item, dict):
98
- if item.get('type') == 'text':
99
- text = item.get('text', '')
100
- if len(text) > 20:
101
- conversation_snippets.append({
102
- 'role': message.get('role', msg_type),
103
- 'content': text[:500]
104
- })
258
+ if not isinstance(item, dict):
259
+ continue
260
+
261
+ # Extract text snippets
262
+ if item.get('type') == 'text':
263
+ text = item.get('text', '')
264
+ if len(text) > 20:
265
+ conversation_snippets.append({
266
+ 'role': message.get('role', msg_type),
267
+ 'content': text
268
+ })
269
+
270
+ # Track file modifications from tool_use entries
271
+ if item.get('type') == 'tool_use':
272
+ tool_name = item.get('name', '')
273
+ tool_input = item.get('input', {})
274
+ if tool_name in ('Edit', 'Write') and tool_input.get('file_path'):
275
+ modified_files.add((tool_input['file_path'], tool_name))
276
+
105
277
  except (json.JSONDecodeError, KeyError):
106
278
  continue
107
279
 
108
- # Only fire after enough user turns
109
- if user_turn_count < MIN_USER_TURNS:
110
- return
280
+ return user_turn_count, conversation_snippets, modified_files, current_line_count
111
281
 
112
- # Update marker with current line count
113
- marker_file.write_text(str(current_line_count))
114
282
 
115
- # If not enough substantive conversation, exit silently
116
- if len(conversation_snippets) < 3:
117
- return
283
+ def has_assistant_messages(transcript_path, start_line):
284
+ """
285
+ Check if there are any assistant messages in the transcript after start_line.
286
+ Used to confirm Claude actually started responding before treating a gap as an interruption.
287
+ """
288
+ try:
289
+ with open(transcript_path, 'r') as f:
290
+ all_lines = f.readlines()
291
+ except IOError:
292
+ return False
293
+
294
+ for line in all_lines[start_line:]:
295
+ try:
296
+ obj = json.loads(line)
297
+ if obj.get('type') == 'assistant':
298
+ return True
299
+ except (json.JSONDecodeError, KeyError):
300
+ continue
118
301
 
119
- # Build a summary of the recent conversation for the rule check
302
+ return False
303
+
304
+
305
+ def build_stale_rules_context(stale_rules):
306
+ """Build context content for stale rules review."""
307
+ parts = []
308
+ parts.append("# Reflection Step\n")
309
+ parts.append("Please review the stale rules below to determine if any need updating based on recent code changes.\n")
310
+ parts.append("Please show your thinking regarding the stale rules.\n")
311
+ parts.append("## Stale Rules to Review\n")
312
+ parts.append("The following rules have files that changed since the rule was last reviewed.")
313
+ parts.append("For each rule, review the rule content and the diff of changes, then:")
314
+ parts.append("1. Determine if the rule content needs updating based on the code changes")
315
+ parts.append("2. Update the rule if needed")
316
+ parts.append("3. ALWAYS run `codeyam memory touch` to mark rules as audited\n")
317
+
318
+ for rule in stale_rules:
319
+ parts.append(f"### {rule['name']}")
320
+ if rule.get('rule_content'):
321
+ parts.append(f" Rule content:")
322
+ for line in rule['rule_content'].split('\n'):
323
+ parts.append(f" {line}")
324
+ if rule.get('newest_file'):
325
+ parts.append(f" Changed file: {rule['newest_file']}")
326
+ if rule.get('file_modified'):
327
+ parts.append(f" File modified: {rule['file_modified']}")
328
+ if rule.get('last_audited'):
329
+ parts.append(f" Last audited: {rule['last_audited']}")
330
+ if rule.get('diff'):
331
+ parts.append(f" Changes:")
332
+ for line in rule['diff'].split('\n'):
333
+ parts.append(f" {line}")
334
+ parts.append("")
335
+
336
+ return '\n'.join(parts)
337
+
338
+
339
+ def build_conversation_context(conversation_snippets, modified_files):
340
+ """Build context content for conversation review."""
341
+ parts = []
342
+ parts.append("## Conversation Review\n")
343
+ parts.append(
344
+ "Review this session for rule-worthy learnings: architectural decisions, tribal knowledge, "
345
+ "confusion, mistakes, or corrections that future sessions would benefit from knowing.\n"
346
+ )
347
+
348
+ # Load guidance from shared template file
349
+ guidance = (PROMPTS_DIR / 'conversation-guidance.txt').read_text()
350
+ parts.append(guidance)
351
+
352
+ if modified_files:
353
+ parts.append("Files modified this session:")
354
+ for file_path, tool_name in sorted(modified_files):
355
+ parts.append(f"- {file_path} ({tool_name})")
356
+ parts.append("")
357
+
358
+ parts.append("### Session transcript\n")
120
359
  summary_lines = []
121
- for snippet in conversation_snippets[-15:]: # Last 15 messages
360
+ for snippet in conversation_snippets:
122
361
  role = snippet['role']
123
- content = snippet['content'].replace('\n', ' ')[:300]
362
+ content = snippet['content'].replace('\n', ' ')
124
363
  summary_lines.append(f"[{role}]: {content}")
364
+ parts.append('\n'.join(summary_lines))
125
365
 
126
- summary = '\n'.join(summary_lines)
127
-
128
- # Write detailed context to a file (keeps displayed output short)
129
- context_file = marker_dir / f'{session_id}.context'
130
- context = f"""Recent conversation to review for rule-worthy learnings:
131
-
132
- {summary}
133
-
134
- ---
135
- Guidelines:
136
- - Did Claude get confused during this session in any way?
137
- - How exactly did it get confused and what information would have resolved that confusion. No additional detail is needed especially if it is something that Claude likely already knows.
138
- - Only update rules for patterns of confusion that will likely recur in future sessions.
139
- - Document knowledge not easily evident from code
140
- - Locations of tests
141
- - Commands that can be run that are relevant to the task at hand
142
- - Architectural design, where to look for, fix, or add certain things
143
- - Do not document bugs being fixed as they will likely be resolved.
144
- - Issues that are abandoned as known issues can be noted.
145
- - Keep rules concise (<50 lines), use bullets/tables where appropriate.
146
- - Clean up, summarize, and delete existing rules as needed.
147
- - If no changes are needed, just say "No rule updates needed."
148
- """
149
- context_file.write_text(context)
366
+ return '\n'.join(parts)
367
+
368
+
369
+ def build_interruption_context(conversation_snippets, follow_up_prompt, modified_files):
370
+ """Build context content for an interrupted session review."""
371
+ parts = []
372
+ parts.append("## Interruption Review\n")
373
+ parts.append(
374
+ "The user interrupted Claude mid-response — a strong signal of confusion or misunderstanding. "
375
+ "Review the interrupted conversation and the user's follow-up to identify rule-worthy learnings.\n"
376
+ )
377
+
378
+ # Load guidance from shared template file
379
+ guidance = (PROMPTS_DIR / 'conversation-guidance.txt').read_text()
380
+ parts.append(guidance)
381
+
382
+ if modified_files:
383
+ parts.append("Files modified this session:")
384
+ for file_path, tool_name in sorted(modified_files):
385
+ parts.append(f"- {file_path} ({tool_name})")
386
+ parts.append("")
387
+
388
+ parts.append("### Session transcript\n")
389
+ summary_lines = []
390
+ for snippet in conversation_snippets:
391
+ role = snippet['role']
392
+ content = snippet['content'].replace('\n', ' ')
393
+ summary_lines.append(f"[{role}]: {content}")
394
+ parts.append('\n'.join(summary_lines))
395
+
396
+ parts.append("")
397
+ parts.append("### User's follow-up after interruption\n")
398
+ parts.append(follow_up_prompt)
399
+
400
+ return '\n'.join(parts)
401
+
402
+
403
+ def spawn_claude_agent(prompt, log_file, project_dir, model='haiku'):
404
+ """Spawn a detached claude -p agent as a background process."""
405
+ try:
406
+ log_fh = open(log_file, 'w')
407
+ env = os.environ.copy()
408
+ env['CODEYAM_RULE_AGENT'] = '1'
409
+ subprocess.Popen(
410
+ ['claude', '-p', prompt,
411
+ '--model', model,
412
+ '--no-session-persistence',
413
+ '--output-format', 'stream-json', '--verbose',
414
+ '--allowedTools', 'Read,Edit,Write,Bash,Glob,Grep'],
415
+ cwd=project_dir,
416
+ stdout=log_fh,
417
+ stderr=log_fh,
418
+ env=env,
419
+ start_new_session=True,
420
+ )
421
+ except FileNotFoundError:
422
+ pass # claude CLI not available, skip silently
423
+
424
+
425
+ def read_marker(marker_file):
426
+ """
427
+ Read marker file. Returns (last_line, written_by_stop).
428
+ Format: "<line_count>" or "<line_count> stop" — the suffix distinguishes
429
+ whether the Stop hook wrote this marker (normal completion) vs the
430
+ UserPromptSubmit handler (interruption detection).
431
+ """
432
+ if marker_file.exists():
433
+ try:
434
+ text = marker_file.read_text().strip()
435
+ parts = text.split()
436
+ line_count = int(parts[0])
437
+ was_stop = len(parts) > 1 and parts[1] == 'stop'
438
+ return line_count, was_stop
439
+ except (ValueError, IOError):
440
+ pass
441
+ return 0, False
442
+
443
+
444
+ def write_marker(marker_file, line_count, source='submit'):
445
+ """Write marker with source tag. source is 'stop' or 'submit'."""
446
+ marker_file.write_text(f'{line_count} {source}')
447
+
448
+
449
+ def handle_stop(hook_input):
450
+ """
451
+ Handle the Stop hook event.
452
+ Reviews completed turns for stale rules and conversation confusion signals.
453
+
454
+ Important: the fast work (transcript parsing, marker update, conversation
455
+ agent spawn) runs first. The slow `get_stale_rules()` call (~10s) runs last
456
+ so the hook timeout doesn't kill us before the critical work is done.
457
+ """
458
+ settings = load_memory_settings()
459
+
460
+ session_id = hook_input.get('session_id', '')
461
+ transcript_path = hook_input.get('transcript_path', '')
462
+
463
+ if not session_id or not transcript_path:
464
+ return
465
+
466
+ marker_dir = Path('/tmp/claude-rule-markers')
467
+ marker_dir.mkdir(exist_ok=True)
468
+ marker_file = marker_dir / f'{session_id}.marker'
469
+
470
+ last_line, _ = read_marker(marker_file)
471
+
472
+ # Fast: parse transcript for conversation context
473
+ user_turn_count, conversation_snippets, modified_files, current_line_count = get_conversation_context(
474
+ transcript_path, last_line
475
+ )
476
+
477
+ # Update marker immediately so UserPromptSubmit knows Stop ran,
478
+ # even if we get killed during the slow stale rules check below
479
+ write_marker(marker_file, current_line_count, 'stop')
480
+
481
+ project_dir = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
482
+ invocation_ts = datetime.now().strftime('%Y%m%d-%H%M%S')
483
+ invocation_id = f'{session_id}-{invocation_ts}'
484
+
485
+ # Fast: spawn conversation review agent first (if enabled)
486
+ if settings['conversationReflection'] and len(conversation_snippets) > 0:
487
+ conv_context = build_conversation_context(conversation_snippets, modified_files)
488
+ conv_context_file = marker_dir / f'{invocation_id}-conversation.context'
489
+ conv_context_file.write_text(conv_context)
490
+
491
+ conv_log_file = marker_dir / f'{invocation_id}-conversation.log'
492
+ conv_notification_file = marker_dir / 'rule-notification-conversation.md'
493
+ conv_prompt = load_prompt_template(
494
+ 'conversation-prompt.txt',
495
+ CONTEXT_FILE=str(conv_context_file),
496
+ NOTIFICATION_FILE=str(conv_notification_file),
497
+ PROJECT_DIR=project_dir,
498
+ )
499
+ spawn_claude_agent(conv_prompt, conv_log_file, project_dir, model=settings['promptModel'])
500
+
501
+ # Slow (~10s): check for stale rules last — if the hook timeout kills us
502
+ # here, the conversation agent and marker are already handled
503
+ if settings['ruleMaintenance']:
504
+ stale_rules = get_stale_rules()
505
+
506
+ if len(stale_rules) > 0:
507
+ stale_context = build_stale_rules_context(stale_rules)
508
+ stale_context_file = marker_dir / f'{invocation_id}-stale.context'
509
+ stale_context_file.write_text(stale_context)
510
+
511
+ stale_log_file = marker_dir / f'{invocation_id}-stale.log'
512
+ stale_notification_file = marker_dir / 'rule-notification-stale.md'
513
+ stale_prompt = load_prompt_template(
514
+ 'stale-rules-prompt.txt',
515
+ CONTEXT_FILE=str(stale_context_file),
516
+ NOTIFICATION_FILE=str(stale_notification_file),
517
+ PROJECT_DIR=project_dir,
518
+ )
519
+ spawn_claude_agent(stale_prompt, stale_log_file, project_dir, model=settings['promptModel'])
520
+
521
+
522
+ def handle_user_prompt_submit(hook_input):
523
+ """
524
+ Handle the UserPromptSubmit hook event.
525
+ Detects whether the previous turn was interrupted by checking if the Stop hook's
526
+ marker file is stale (transcript has lines beyond the marker). If so, spawns a
527
+ rule-reflection agent focused on the interruption.
528
+ """
529
+ settings = load_memory_settings()
530
+
531
+ # Interruption detection is part of conversation reflection
532
+ if not settings['conversationReflection']:
533
+ return
534
+
535
+ session_id = hook_input.get('session_id', '')
536
+ transcript_path = hook_input.get('transcript_path', '')
537
+ follow_up_prompt = hook_input.get('prompt', '')
538
+
539
+ if not session_id or not transcript_path:
540
+ return
541
+
542
+ marker_dir = Path('/tmp/claude-rule-markers')
543
+ marker_dir.mkdir(exist_ok=True)
544
+ marker_file = marker_dir / f'{session_id}.marker'
545
+
546
+ last_line, was_stop = read_marker(marker_file)
547
+
548
+ # If the Stop hook already ran for the previous turn, this is a normal
549
+ # completion — not an interruption. The Stop hook tags the marker with
550
+ # 'stop' when it writes it.
551
+ if was_stop:
552
+ return
553
+
554
+ # Count current transcript lines
555
+ try:
556
+ with open(transcript_path, 'r') as f:
557
+ current_line_count = sum(1 for _ in f)
558
+ except IOError:
559
+ return
560
+
561
+ # Quick exit: if nothing new since the marker, definitely no interruption
562
+ if current_line_count <= last_line:
563
+ return
564
+
565
+ # Verify Claude actually started responding in the unprocessed lines.
566
+ # Guards against false positives on first prompt (no assistant yet) or
567
+ # if the user hit Escape before Claude produced any output.
568
+ if not has_assistant_messages(transcript_path, last_line):
569
+ return
570
+
571
+ # Interruption detected! Build context and spawn agent.
572
+ _, conversation_snippets, modified_files, _ = get_conversation_context(
573
+ transcript_path, last_line
574
+ )
575
+
576
+ if not conversation_snippets:
577
+ return
578
+
579
+ # Update marker so we don't re-process these lines
580
+ write_marker(marker_file, current_line_count, 'submit')
581
+
582
+ project_dir = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
583
+
584
+ invocation_ts = datetime.now().strftime('%Y%m%d-%H%M%S')
585
+ invocation_id = f'{session_id}-{invocation_ts}'
586
+
587
+ interruption_context = build_interruption_context(
588
+ conversation_snippets, follow_up_prompt, modified_files
589
+ )
590
+ context_file = marker_dir / f'{invocation_id}-interruption.context'
591
+ context_file.write_text(interruption_context)
592
+
593
+ log_file = marker_dir / f'{invocation_id}-interruption.log'
594
+ notification_file = marker_dir / 'rule-notification-interruption.md'
595
+ prompt = load_prompt_template(
596
+ 'interruption-prompt.txt',
597
+ CONTEXT_FILE=str(context_file),
598
+ NOTIFICATION_FILE=str(notification_file),
599
+ PROJECT_DIR=project_dir,
600
+ )
601
+ spawn_claude_agent(prompt, log_file, project_dir, model=settings['promptModel'])
602
+
603
+
604
+ def main():
605
+ # Read hook input from stdin
606
+ try:
607
+ hook_input = json.load(sys.stdin)
608
+ except json.JSONDecodeError:
609
+ return
610
+
611
+ stop_hook_active = hook_input.get('stop_hook_active', False)
612
+
613
+ # Prevent infinite loops — stop_hook_active covers same-process recursion,
614
+ # env var covers spawned subagent sessions triggering the hook on exit
615
+ if stop_hook_active or os.environ.get('CODEYAM_RULE_AGENT'):
616
+ return
617
+
618
+ # Detect event type: UserPromptSubmit has a 'prompt' field, Stop does not
619
+ is_user_prompt = 'prompt' in hook_input
620
+ if is_user_prompt:
621
+ handle_user_prompt_submit(hook_input)
622
+ else:
623
+ handle_stop(hook_input)
150
624
 
151
- # Output blocking decision - just trigger background agent
152
- # Keep reason minimal since main agent will spawn background worker
153
- output = {
154
- "decision": "block",
155
- "reason": f"RULE_CHECK:{context_file}"
156
- }
157
- print(json.dumps(output))
158
625
 
159
626
  if __name__ == '__main__':
160
627
  main()