@codeyam/codeyam-cli 0.1.0-staging.323686 → 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.
- package/analyzer-template/.build-info.json +7 -7
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/package.json +5 -5
- package/analyzer-template/packages/ai/index.ts +7 -1
- package/analyzer-template/packages/ai/package.json +2 -2
- package/analyzer-template/packages/ai/src/lib/analyzeScope.ts +62 -18
- package/analyzer-template/packages/ai/src/lib/astScopes/astScopeAnalyzer.ts +67 -9
- package/analyzer-template/packages/ai/src/lib/astScopes/patterns/forInStatementHandler.ts +10 -17
- package/analyzer-template/packages/ai/src/lib/astScopes/processExpression.ts +409 -50
- package/analyzer-template/packages/ai/src/lib/astScopes/sharedPatterns.ts +28 -0
- package/analyzer-template/packages/ai/src/lib/astScopes/types.ts +21 -6
- package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +992 -249
- package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/JavascriptFrameworkManager.ts +5 -1
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.ts +16 -3
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.ts +6 -4
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +31 -3
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.ts +37 -15
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/coerceObjectsToPrimitivesBySchema.ts +70 -0
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.ts +126 -11
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.ts +179 -0
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.ts +40 -30
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.ts +367 -96
- package/analyzer-template/packages/ai/src/lib/dataStructureChunking.ts +33 -15
- package/analyzer-template/packages/ai/src/lib/generateEntityDataStructure.ts +58 -3
- package/analyzer-template/packages/ai/src/lib/generateEntityScenarioData.ts +315 -6
- package/analyzer-template/packages/ai/src/lib/generateEntityScenarios.ts +9 -5
- package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +49 -5
- package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionalEffects.ts +1 -1
- package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionals.ts +649 -142
- package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromJsxUsages.ts +1 -1
- package/analyzer-template/packages/ai/src/lib/isolateScopes.ts +51 -3
- package/analyzer-template/packages/ai/src/lib/mergeJsonTypeDefinitions.ts +5 -0
- package/analyzer-template/packages/ai/src/lib/mergeStatements.ts +90 -96
- package/analyzer-template/packages/ai/src/lib/promptGenerators/collapseNullableObjects.ts +118 -0
- package/analyzer-template/packages/ai/src/lib/promptGenerators/gatherAttributesMap.ts +10 -7
- package/analyzer-template/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.ts +24 -4
- package/analyzer-template/packages/ai/src/lib/resolvePathToControllable.ts +25 -13
- package/analyzer-template/packages/ai/src/lib/worker/SerializableDataStructure.ts +4 -3
- package/analyzer-template/packages/analyze/index.ts +2 -0
- package/analyzer-template/packages/analyze/src/lib/FileAnalyzer.ts +65 -59
- package/analyzer-template/packages/analyze/src/lib/ProjectAnalyzer.ts +113 -26
- package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.ts +19 -0
- package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.ts +19 -0
- package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllExports.ts +11 -0
- package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.ts +8 -0
- package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.ts +49 -1
- package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.ts +2 -1
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +89 -9
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +19 -4
- package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +4 -2
- package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +0 -3
- package/analyzer-template/packages/analyze/src/lib/files/analyzeRemixRoute.ts +4 -5
- package/analyzer-template/packages/analyze/src/lib/files/getImportedExports.ts +14 -12
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/TransformationTracer.ts +1315 -0
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.ts +61 -13
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.ts +37 -0
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +229 -19
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.ts +117 -9
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +459 -39
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/propagateArrayItemSchemas.ts +474 -0
- package/analyzer-template/packages/analyze/src/lib/files/setImportedExports.ts +2 -1
- package/analyzer-template/packages/analyze/src/lib/index.ts +1 -0
- package/analyzer-template/packages/analyze/src/lib/utils/getFileByPath.ts +19 -0
- package/analyzer-template/packages/aws/package.json +1 -1
- package/analyzer-template/packages/database/package.json +1 -1
- package/analyzer-template/packages/database/src/lib/analysisBranchToDb.ts +1 -1
- package/analyzer-template/packages/database/src/lib/analysisToDb.ts +1 -1
- package/analyzer-template/packages/database/src/lib/branchToDb.ts +1 -1
- package/analyzer-template/packages/database/src/lib/commitBranchToDb.ts +1 -1
- package/analyzer-template/packages/database/src/lib/commitToDb.ts +1 -1
- package/analyzer-template/packages/database/src/lib/fileToDb.ts +1 -1
- package/analyzer-template/packages/database/src/lib/kysely/db.ts +6 -0
- package/analyzer-template/packages/database/src/lib/kysely/tables/debugReportsTable.ts +1 -1
- package/analyzer-template/packages/database/src/lib/kysely/tables/labsRequestsTable.ts +52 -0
- package/analyzer-template/packages/database/src/lib/projectToDb.ts +1 -1
- package/analyzer-template/packages/database/src/lib/saveFiles.ts +1 -1
- package/analyzer-template/packages/database/src/lib/scenarioToDb.ts +1 -1
- package/analyzer-template/packages/database/src/lib/userScenarioToDb.ts +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/analysisBranchToDb.js +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/analysisBranchToDb.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/analysisToDb.js +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/analysisToDb.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/branchToDb.js +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/branchToDb.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/commitBranchToDb.js +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/commitBranchToDb.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/commitToDb.js +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/commitToDb.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/fileToDb.js +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/fileToDb.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.d.ts +2 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.js +3 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/debugReportsTable.d.ts +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/labsRequestsTable.d.ts +23 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/labsRequestsTable.d.ts.map +1 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/labsRequestsTable.js +35 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/labsRequestsTable.js.map +1 -0
- package/analyzer-template/packages/github/dist/database/src/lib/projectToDb.js +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/projectToDb.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/saveFiles.js +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/saveFiles.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/scenarioToDb.js +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/scenarioToDb.js.map +1 -1
- package/analyzer-template/packages/github/dist/types/src/types/ProjectMetadata.d.ts +7 -0
- package/analyzer-template/packages/github/dist/types/src/types/ProjectMetadata.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/types/src/types/ScenariosDataStructure.d.ts +5 -5
- package/analyzer-template/packages/github/dist/types/src/types/ScenariosDataStructure.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/types/src/types/ScopeAnalysis.d.ts +6 -1
- package/analyzer-template/packages/github/dist/types/src/types/ScopeAnalysis.d.ts.map +1 -1
- package/analyzer-template/packages/github/package.json +1 -1
- package/analyzer-template/packages/types/src/types/ProjectMetadata.ts +7 -0
- package/analyzer-template/packages/types/src/types/ScenariosDataStructure.ts +6 -5
- package/analyzer-template/packages/types/src/types/ScopeAnalysis.ts +6 -1
- package/analyzer-template/packages/utils/dist/types/src/types/ProjectMetadata.d.ts +7 -0
- package/analyzer-template/packages/utils/dist/types/src/types/ProjectMetadata.d.ts.map +1 -1
- package/analyzer-template/packages/utils/dist/types/src/types/ScenariosDataStructure.d.ts +5 -5
- package/analyzer-template/packages/utils/dist/types/src/types/ScenariosDataStructure.d.ts.map +1 -1
- package/analyzer-template/packages/utils/dist/types/src/types/ScopeAnalysis.d.ts +6 -1
- package/analyzer-template/packages/utils/dist/types/src/types/ScopeAnalysis.d.ts.map +1 -1
- package/analyzer-template/project/constructMockCode.ts +90 -10
- package/analyzer-template/project/writeMockDataTsx.ts +181 -8
- package/analyzer-template/project/writeScenarioComponents.ts +60 -12
- package/analyzer-template/project/writeSimpleRoot.ts +21 -11
- package/background/src/lib/local/createLocalAnalyzer.js +1 -1
- package/background/src/lib/local/createLocalAnalyzer.js.map +1 -1
- package/background/src/lib/virtualized/project/constructMockCode.js +75 -4
- package/background/src/lib/virtualized/project/constructMockCode.js.map +1 -1
- package/background/src/lib/virtualized/project/writeMockDataTsx.js +162 -4
- package/background/src/lib/virtualized/project/writeMockDataTsx.js.map +1 -1
- package/background/src/lib/virtualized/project/writeScenarioComponents.js +60 -15
- package/background/src/lib/virtualized/project/writeScenarioComponents.js.map +1 -1
- package/background/src/lib/virtualized/project/writeSimpleRoot.js +21 -11
- package/background/src/lib/virtualized/project/writeSimpleRoot.js.map +1 -1
- package/codeyam-cli/scripts/apply-setup.js +180 -0
- package/codeyam-cli/scripts/apply-setup.js.map +1 -1
- package/codeyam-cli/src/cli.js +2 -0
- package/codeyam-cli/src/cli.js.map +1 -1
- package/codeyam-cli/src/codeyam-cli.js +18 -2
- package/codeyam-cli/src/codeyam-cli.js.map +1 -1
- package/codeyam-cli/src/commands/analyze.js +4 -2
- package/codeyam-cli/src/commands/analyze.js.map +1 -1
- package/codeyam-cli/src/commands/baseline.js +2 -0
- package/codeyam-cli/src/commands/baseline.js.map +1 -1
- package/codeyam-cli/src/commands/debug.js +9 -5
- package/codeyam-cli/src/commands/debug.js.map +1 -1
- package/codeyam-cli/src/commands/default.js +31 -20
- package/codeyam-cli/src/commands/default.js.map +1 -1
- package/codeyam-cli/src/commands/detect-universal-mocks.js +2 -0
- package/codeyam-cli/src/commands/detect-universal-mocks.js.map +1 -1
- package/codeyam-cli/src/commands/init.js +49 -257
- package/codeyam-cli/src/commands/init.js.map +1 -1
- package/codeyam-cli/src/commands/memory.js +17 -26
- package/codeyam-cli/src/commands/memory.js.map +1 -1
- package/codeyam-cli/src/commands/recapture.js +2 -0
- package/codeyam-cli/src/commands/recapture.js.map +1 -1
- package/codeyam-cli/src/commands/setup-sandbox.js +2 -0
- package/codeyam-cli/src/commands/setup-sandbox.js.map +1 -1
- package/codeyam-cli/src/commands/setup-simulations.js +284 -0
- package/codeyam-cli/src/commands/setup-simulations.js.map +1 -0
- package/codeyam-cli/src/commands/test-startup.js +2 -0
- package/codeyam-cli/src/commands/test-startup.js.map +1 -1
- package/codeyam-cli/src/commands/verify.js +14 -2
- package/codeyam-cli/src/commands/verify.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +128 -86
- package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
- package/codeyam-cli/src/utils/analyzer.js +7 -0
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/backgroundServer.js +5 -0
- package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/utils/generateReport.js +2 -2
- package/codeyam-cli/src/utils/install-skills.js +70 -45
- package/codeyam-cli/src/utils/install-skills.js.map +1 -1
- package/codeyam-cli/src/utils/labsAutoCheck.js +19 -0
- package/codeyam-cli/src/utils/labsAutoCheck.js.map +1 -0
- package/codeyam-cli/src/utils/progress.js +7 -0
- package/codeyam-cli/src/utils/progress.js.map +1 -1
- package/codeyam-cli/src/utils/queue/job.js +4 -0
- package/codeyam-cli/src/utils/queue/job.js.map +1 -1
- package/codeyam-cli/src/utils/requireSimulations.js +10 -0
- package/codeyam-cli/src/utils/requireSimulations.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/confusionDetector.test.js +82 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/confusionDetector.test.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js +230 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/assertRules.js +67 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/assertRules.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/captureFixture.js +105 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/captureFixture.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/loadCapturedFixture.js +34 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/loadCapturedFixture.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/runClaude.js +162 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/runClaude.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/setupTempProject.js +75 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/setupTempProject.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js +378 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js +115 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/transcriptParser.test.js +127 -0
- package/codeyam-cli/src/utils/ruleReflection/__tests__/transcriptParser.test.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/confusionDetector.js +50 -0
- package/codeyam-cli/src/utils/ruleReflection/confusionDetector.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js +116 -0
- package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/index.js +5 -0
- package/codeyam-cli/src/utils/ruleReflection/index.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/promptBuilder.js +44 -0
- package/codeyam-cli/src/utils/ruleReflection/promptBuilder.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/transcriptParser.js +85 -0
- package/codeyam-cli/src/utils/ruleReflection/transcriptParser.js.map +1 -0
- package/codeyam-cli/src/utils/ruleReflection/types.js +5 -0
- package/codeyam-cli/src/utils/ruleReflection/types.js.map +1 -0
- package/codeyam-cli/src/utils/rules/__tests__/ruleState.test.js +293 -0
- package/codeyam-cli/src/utils/rules/__tests__/ruleState.test.js.map +1 -0
- package/codeyam-cli/src/utils/rules/index.js +1 -0
- package/codeyam-cli/src/utils/rules/index.js.map +1 -1
- package/codeyam-cli/src/utils/rules/parser.js +2 -25
- package/codeyam-cli/src/utils/rules/parser.js.map +1 -1
- package/codeyam-cli/src/utils/rules/ruleState.js +150 -0
- package/codeyam-cli/src/utils/rules/ruleState.js.map +1 -0
- package/codeyam-cli/src/utils/rules/staleness.js +16 -11
- package/codeyam-cli/src/utils/rules/staleness.js.map +1 -1
- package/codeyam-cli/src/utils/serverState.js +37 -10
- package/codeyam-cli/src/utils/serverState.js.map +1 -1
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +21 -44
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
- package/codeyam-cli/src/webserver/app/lib/database.js +15 -3
- package/codeyam-cli/src/webserver/app/lib/database.js.map +1 -1
- package/codeyam-cli/src/webserver/backgroundServer.js +24 -0
- package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/CopyButton-CA3JxPb7.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-DsN1wKrm.js → EntityItem-B86KKU7e.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeBadge-DLqD3qNt.js → EntityTypeBadge-B5ctlSYt.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-Ba2JVPzP.js → EntityTypeIcon-BqY8gDAW.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-C8lyxW9k.js → InlineSpinner-ClaLpuOo.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-aht4aafF.js → InteractivePreview-BDhPilK7.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-CVtiBnY5.js → LibraryFunctionPreview-VeqEBv9v.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-B0GLXMsr.js → LoadingDots-Bs7Nn1Jr.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-xgeCVgSM.js → LogViewer-Bm3PmcCz.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-OApQuNyq.js → ReportIssueModal-CgMEzchJ.js} +3 -8
- package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-DuDvi0jm.js → SafeScreenshot-Gq3Ocjo6.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-DzccYyI8.js → ScenarioViewer-CBui0id_.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{TruncatedFilePath-DyFZkK0l.js → TruncatedFilePath-CiwXDxLh.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{_index-BwqWJOgH.js → _index-B3TDXxnk.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BwavGCpm.js → activity.(_tab)-BtBFH820.js} +6 -11
- package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-CN61MOMa.js +11 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.agent-transcripts-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.labs-unlock-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.save-fixture-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/book-open-PttOB2SF.js +6 -0
- package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-Cx24_aWc.js → chevron-down-TJp6ofnp.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{chunk-EPOLDU6W-CXRTFQ3F.js → chunk-JZWAC4HX-JE9ZIoBl.js} +12 -12
- package/codeyam-cli/src/webserver/build/client/assets/{circle-check-BOARzkeR.js → circle-check-CXhHQYrI.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/copy-6y9ALfGT.js +11 -0
- package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-BdhJEx6B.js → createLucideIcon-Ca9fAY46.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-BBnGWYga.js → dev.empty-C0epRiVn.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BJUiQqZF.js → entity._sha._-BVnB8a9L.js} +10 -10
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-DavjRmOY.js → entity._sha.scenarios._scenarioId.fullscreen-CBoafmVs.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-D1T4TGjf.js → entity._sha_.create-scenario-DGgZjdFg.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-CTBG2mmz.js → entity._sha_.edit._scenarioId-38yPijoD.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entry.client-CS2cb_eZ.js → entry.client-BSHEfydn.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{fileTableUtils-DMJ7zii9.js → fileTableUtils-DCPhhSMo.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{files-CJ6lTdTA.js → files-0N0YJQv7.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{git-CPTZZ-JZ.js → git-DXnyr8uP.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-CKT08Djd.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{index-lzqtyFU8.js → index-CcsFv748.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-B1h680n5.js → index-ChN9-fAY.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/labs-BLJ7HxOC.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-B7B9V-bu.js → loader-circle-CTqLEAGU.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-b171b9d3.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/memory-CCQd4aZA.js +78 -0
- package/codeyam-cli/src/webserver/build/client/assets/pause-D6vreykR.js +11 -0
- package/codeyam-cli/src/webserver/build/client/assets/root-CHhiHoo_.js +62 -0
- package/codeyam-cli/src/webserver/build/client/assets/{search-CxXUmBSd.js → search-B8VUL8nl.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/settings-BejnUJ6R.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{simulations-DwFIBT09.js → simulations-CPoAg7Zo.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/terminal-BrCP7uQo.js +11 -0
- package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-B6LgvRJg.js → triangle-alert-BZz2NjYa.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-C1v1PQzo.js → useCustomSizes-DNwUduNu.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-aSv48UbS.js → useLastLogLine-COky1GVF.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-DYxHZQuP.js → useReportContext-CpZgwliL.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useToast-mBRpZPiu.js → useToast-Bv9JFvUO.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{index-DVzYx8PN.js → index-8Fv-lH1-.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-Akn3iYFP.js +257 -0
- package/codeyam-cli/src/webserver/build/server/index.js +1 -1
- package/codeyam-cli/src/webserver/build-info.json +5 -5
- package/codeyam-cli/templates/{codeyam:debug.md → codeyam-debug.md} +1 -1
- package/codeyam-cli/templates/codeyam-diagnose.md +481 -0
- package/codeyam-cli/templates/codeyam-memory-hook.sh +19 -20
- package/codeyam-cli/templates/codeyam-memory.md +392 -0
- package/codeyam-cli/templates/codeyam-new-rule.md +13 -0
- package/codeyam-cli/templates/{codeyam:setup.md → codeyam-setup.md} +13 -1
- package/codeyam-cli/templates/{codeyam:sim.md → codeyam-sim.md} +1 -1
- package/codeyam-cli/templates/{codeyam:test.md → codeyam-test.md} +1 -1
- package/codeyam-cli/templates/{codeyam:verify.md → codeyam-verify.md} +1 -1
- package/codeyam-cli/templates/rule-notification-hook.py +56 -0
- package/codeyam-cli/templates/rule-reflection-hook.py +627 -0
- package/codeyam-cli/templates/rules-instructions.md +132 -0
- package/package.json +2 -2
- package/packages/ai/index.js +3 -2
- package/packages/ai/index.js.map +1 -1
- package/packages/ai/src/lib/analyzeScope.js +50 -13
- package/packages/ai/src/lib/analyzeScope.js.map +1 -1
- package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js +54 -8
- package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js.map +1 -1
- package/packages/ai/src/lib/astScopes/patterns/forInStatementHandler.js +10 -14
- package/packages/ai/src/lib/astScopes/patterns/forInStatementHandler.js.map +1 -1
- package/packages/ai/src/lib/astScopes/processExpression.js +317 -44
- package/packages/ai/src/lib/astScopes/processExpression.js.map +1 -1
- package/packages/ai/src/lib/astScopes/sharedPatterns.js +25 -0
- package/packages/ai/src/lib/astScopes/sharedPatterns.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +763 -171
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/JavascriptFrameworkManager.js +5 -1
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/JavascriptFrameworkManager.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.js +13 -3
- package/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.js +6 -4
- package/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +33 -3
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js +36 -11
- package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/coerceObjectsToPrimitivesBySchema.js +63 -0
- package/packages/ai/src/lib/dataStructure/helpers/coerceObjectsToPrimitivesBySchema.js.map +1 -0
- package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js +113 -11
- package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js +173 -0
- package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js.map +1 -0
- package/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.js +37 -20
- package/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js +309 -84
- package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js.map +1 -1
- package/packages/ai/src/lib/dataStructureChunking.js +26 -11
- package/packages/ai/src/lib/dataStructureChunking.js.map +1 -1
- package/packages/ai/src/lib/generateEntityDataStructure.js +46 -2
- package/packages/ai/src/lib/generateEntityDataStructure.js.map +1 -1
- package/packages/ai/src/lib/generateEntityScenarioData.js +227 -4
- package/packages/ai/src/lib/generateEntityScenarioData.js.map +1 -1
- package/packages/ai/src/lib/generateEntityScenarios.js +7 -1
- package/packages/ai/src/lib/generateEntityScenarios.js.map +1 -1
- package/packages/ai/src/lib/generateExecutionFlows.js +26 -4
- package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
- package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js +447 -80
- package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js.map +1 -1
- package/packages/ai/src/lib/isolateScopes.js +39 -3
- package/packages/ai/src/lib/isolateScopes.js.map +1 -1
- package/packages/ai/src/lib/mergeJsonTypeDefinitions.js +5 -0
- package/packages/ai/src/lib/mergeJsonTypeDefinitions.js.map +1 -1
- package/packages/ai/src/lib/mergeStatements.js +70 -51
- package/packages/ai/src/lib/mergeStatements.js.map +1 -1
- package/packages/ai/src/lib/promptGenerators/collapseNullableObjects.js +97 -0
- package/packages/ai/src/lib/promptGenerators/collapseNullableObjects.js.map +1 -0
- package/packages/ai/src/lib/promptGenerators/gatherAttributesMap.js +10 -4
- package/packages/ai/src/lib/promptGenerators/gatherAttributesMap.js.map +1 -1
- package/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.js +17 -2
- package/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.js.map +1 -1
- package/packages/ai/src/lib/resolvePathToControllable.js +24 -14
- package/packages/ai/src/lib/resolvePathToControllable.js.map +1 -1
- package/packages/ai/src/lib/worker/SerializableDataStructure.js.map +1 -1
- package/packages/analyze/index.js +1 -0
- package/packages/analyze/index.js.map +1 -1
- package/packages/analyze/src/lib/FileAnalyzer.js +60 -36
- package/packages/analyze/src/lib/FileAnalyzer.js.map +1 -1
- package/packages/analyze/src/lib/ProjectAnalyzer.js +96 -26
- package/packages/analyze/src/lib/ProjectAnalyzer.js.map +1 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.js +14 -0
- package/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.js.map +1 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.js +14 -0
- package/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.js.map +1 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getAllExports.js +6 -0
- package/packages/analyze/src/lib/asts/sourceFiles/getAllExports.js.map +1 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.js +6 -0
- package/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.js.map +1 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.js +39 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.js.map +1 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.js +2 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +65 -7
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +17 -4
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +2 -1
- package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +0 -3
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeRemixRoute.js +3 -2
- package/packages/analyze/src/lib/files/analyzeRemixRoute.js.map +1 -1
- package/packages/analyze/src/lib/files/getImportedExports.js +11 -7
- package/packages/analyze/src/lib/files/getImportedExports.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/TransformationTracer.js +880 -0
- package/packages/analyze/src/lib/files/scenarios/TransformationTracer.js.map +1 -0
- package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js +56 -10
- package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js +33 -8
- package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +150 -17
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js +56 -8
- package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +399 -31
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/files/setImportedExports.js +2 -1
- package/packages/analyze/src/lib/files/setImportedExports.js.map +1 -1
- package/packages/analyze/src/lib/index.js +1 -0
- package/packages/analyze/src/lib/index.js.map +1 -1
- package/packages/analyze/src/lib/utils/getFileByPath.js +12 -0
- package/packages/analyze/src/lib/utils/getFileByPath.js.map +1 -0
- package/packages/database/src/lib/analysisBranchToDb.js +1 -1
- package/packages/database/src/lib/analysisBranchToDb.js.map +1 -1
- package/packages/database/src/lib/analysisToDb.js +1 -1
- package/packages/database/src/lib/analysisToDb.js.map +1 -1
- package/packages/database/src/lib/branchToDb.js +1 -1
- package/packages/database/src/lib/branchToDb.js.map +1 -1
- package/packages/database/src/lib/commitBranchToDb.js +1 -1
- package/packages/database/src/lib/commitBranchToDb.js.map +1 -1
- package/packages/database/src/lib/commitToDb.js +1 -1
- package/packages/database/src/lib/commitToDb.js.map +1 -1
- package/packages/database/src/lib/fileToDb.js +1 -1
- package/packages/database/src/lib/fileToDb.js.map +1 -1
- package/packages/database/src/lib/kysely/db.js +3 -0
- package/packages/database/src/lib/kysely/db.js.map +1 -1
- package/packages/database/src/lib/kysely/tables/labsRequestsTable.js +35 -0
- package/packages/database/src/lib/kysely/tables/labsRequestsTable.js.map +1 -0
- package/packages/database/src/lib/projectToDb.js +1 -1
- package/packages/database/src/lib/projectToDb.js.map +1 -1
- package/packages/database/src/lib/saveFiles.js +1 -1
- package/packages/database/src/lib/saveFiles.js.map +1 -1
- package/packages/database/src/lib/scenarioToDb.js +1 -1
- package/packages/database/src/lib/scenarioToDb.js.map +1 -1
- package/scripts/finalize-analyzer.cjs +8 -76
- package/codeyam-cli/src/webserver/build/client/assets/copy-Bb-80kDT.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/file-code-Dhef1kWN.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/globals-D3yhhV8x.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-7522edd4.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/memory-yxFcrxBX.js +0 -92
- package/codeyam-cli/src/webserver/build/client/assets/root-eVAaavTS.js +0 -62
- package/codeyam-cli/src/webserver/build/client/assets/settings-CS5f3WzT.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-4Cr0uToj.js +0 -257
- package/codeyam-cli/templates/codeyam:diagnose.md +0 -803
- package/codeyam-cli/templates/codeyam:memory.md +0 -462
- package/codeyam-cli/templates/codeyam:new-rule.md +0 -13
|
@@ -0,0 +1,627 @@
|
|
|
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 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'])
|
|
196
|
+
|
|
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
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
with open(transcript_path, 'r') as f:
|
|
215
|
+
all_lines = f.readlines()
|
|
216
|
+
except IOError:
|
|
217
|
+
return 0, [], set(), 0
|
|
218
|
+
|
|
219
|
+
current_line_count = len(all_lines)
|
|
220
|
+
new_lines = all_lines[last_line:]
|
|
221
|
+
|
|
222
|
+
if not new_lines:
|
|
223
|
+
return 0, [], set(), current_line_count
|
|
224
|
+
|
|
225
|
+
user_turn_count = 0
|
|
226
|
+
conversation_snippets = []
|
|
227
|
+
modified_files = set()
|
|
228
|
+
|
|
229
|
+
for line in new_lines:
|
|
230
|
+
try:
|
|
231
|
+
obj = json.loads(line)
|
|
232
|
+
msg_type = obj.get('type')
|
|
233
|
+
|
|
234
|
+
if msg_type not in ('user', 'assistant'):
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
message = obj.get('message', {})
|
|
238
|
+
content = message.get('content', '')
|
|
239
|
+
is_external_user = msg_type == 'user' and obj.get('userType') == 'external'
|
|
240
|
+
|
|
241
|
+
# Handle string content
|
|
242
|
+
if isinstance(content, str):
|
|
243
|
+
if obj.get('isMeta') or len(content) < 20:
|
|
244
|
+
continue
|
|
245
|
+
if content.startswith('[{') or '<tool_result' in content:
|
|
246
|
+
continue
|
|
247
|
+
|
|
248
|
+
if is_external_user and not content.startswith('<'):
|
|
249
|
+
user_turn_count += 1
|
|
250
|
+
|
|
251
|
+
conversation_snippets.append({
|
|
252
|
+
'role': message.get('role', msg_type),
|
|
253
|
+
'content': content
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
elif isinstance(content, list):
|
|
257
|
+
for item in content:
|
|
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
|
+
|
|
277
|
+
except (json.JSONDecodeError, KeyError):
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
return user_turn_count, conversation_snippets, modified_files, current_line_count
|
|
281
|
+
|
|
282
|
+
|
|
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
|
|
301
|
+
|
|
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")
|
|
359
|
+
summary_lines = []
|
|
360
|
+
for snippet in conversation_snippets:
|
|
361
|
+
role = snippet['role']
|
|
362
|
+
content = snippet['content'].replace('\n', ' ')
|
|
363
|
+
summary_lines.append(f"[{role}]: {content}")
|
|
364
|
+
parts.append('\n'.join(summary_lines))
|
|
365
|
+
|
|
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)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
if __name__ == '__main__':
|
|
627
|
+
main()
|