@rosetears/aili-pi 0.1.0
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/LICENSE +21 -0
- package/README.md +109 -0
- package/THIRD_PARTY_NOTICES.md +64 -0
- package/extensions/index.ts +6 -0
- package/install.sh +5 -0
- package/manifests/adapter-evidence.json +45 -0
- package/manifests/capabilities.json +124 -0
- package/manifests/live-verification.json +26 -0
- package/manifests/provenance.json +71 -0
- package/manifests/roles.json +474 -0
- package/manifests/sbom.json +7797 -0
- package/manifests/skill-compatibility.json +8100 -0
- package/manifests/subagent-provenance.json +37 -0
- package/package.json +91 -0
- package/prompts/build.md +8 -0
- package/prompts/define.md +8 -0
- package/prompts/ideate.md +8 -0
- package/prompts/local-review.md +8 -0
- package/prompts/ship.md +8 -0
- package/roles/agent-evaluator.md +47 -0
- package/roles/ai-regression-scout.md +47 -0
- package/roles/browser-qa-runner.md +47 -0
- package/roles/code-reviewer.md +47 -0
- package/roles/code-scout.md +47 -0
- package/roles/convergence-reviewer.md +55 -0
- package/roles/doc-researcher.md +47 -0
- package/roles/e2e-artifact-runner.md +47 -0
- package/roles/implementer.md +47 -0
- package/roles/opensource-sanitizer.md +47 -0
- package/roles/plan-auditor.md +47 -0
- package/roles/pr-test-analyzer.md +47 -0
- package/roles/security-auditor.md +47 -0
- package/roles/silent-failure-reviewer.md +47 -0
- package/roles/spec-miner.md +47 -0
- package/roles/test-coverage-reviewer.md +47 -0
- package/roles/test-engineer.md +48 -0
- package/roles/web-performance-auditor.md +47 -0
- package/roles/web-researcher.md +47 -0
- package/scripts/apply-adapter-evidence.ts +73 -0
- package/scripts/bootstrap.sh +180 -0
- package/scripts/generate-provenance.ts +143 -0
- package/scripts/local-package-e2e.ts +51 -0
- package/scripts/sync-roles.ts +213 -0
- package/scripts/sync-skills.ts +356 -0
- package/scripts/validate-runtime.ts +11 -0
- package/skills/academic-paper-review/SKILL.md +81 -0
- package/skills/agents-md-initialization/SKILL.md +121 -0
- package/skills/agents-md-initialization/references/agents-template.md +83 -0
- package/skills/agents-md-initialization/references/agents_md.py +215 -0
- package/skills/ai-regression-scout/SKILL.md +24 -0
- package/skills/aili-delivery-flow/SKILL.md +125 -0
- package/skills/aili-delivery-flow/references/artifact-contracts.md +138 -0
- package/skills/aili-delivery-flow/references/backend-routing.md +40 -0
- package/skills/aili-delivery-flow/references/build-execution-loop.md +113 -0
- package/skills/aili-delivery-flow/references/build-goal-mode.md +3 -0
- package/skills/aili-delivery-flow/references/direct-vs-delegated-work.md +38 -0
- package/skills/aili-delivery-flow/references/implementation-packages.md +54 -0
- package/skills/aili-delivery-flow/references/lifecycle.md +127 -0
- package/skills/aili-delivery-flow/references/protocols/acceptance-test-plan.md +18 -0
- package/skills/aili-delivery-flow/references/protocols/alignment-questionnaire.md +10 -0
- package/skills/aili-delivery-flow/references/protocols/closeout-report.md +101 -0
- package/skills/aili-delivery-flow/references/protocols/compact-evidence-pack.md +46 -0
- package/skills/aili-delivery-flow/references/protocols/idea-brief.md +10 -0
- package/skills/aili-delivery-flow/references/protocols/implementation-package.md +53 -0
- package/skills/aili-delivery-flow/references/protocols/research-evidence-pack.md +19 -0
- package/skills/aili-delivery-flow/references/protocols/review-report.md +7 -0
- package/skills/aili-delivery-flow/references/protocols/spec-draft.md +12 -0
- package/skills/aili-delivery-flow/references/protocols/subagent-result.md +60 -0
- package/skills/aili-delivery-flow/references/protocols/subagent-task-packet.md +29 -0
- package/skills/aili-delivery-flow/references/protocols/worktree-context.md +109 -0
- package/skills/aili-delivery-flow/references/questionnaire-policy.md +62 -0
- package/skills/aili-delivery-flow/references/review-repair-loop.md +16 -0
- package/skills/aili-delivery-flow/references/test-document-policy.md +35 -0
- package/skills/android-native-dev/SKILL.md +722 -0
- package/skills/android-native-dev/references/accessibility.md +209 -0
- package/skills/android-native-dev/references/adaptive-screens.md +231 -0
- package/skills/android-native-dev/references/design-style-guide.md +365 -0
- package/skills/android-native-dev/references/functional-requirements.md +229 -0
- package/skills/android-native-dev/references/motion-system.md +203 -0
- package/skills/android-native-dev/references/performance-stability.md +223 -0
- package/skills/android-native-dev/references/privacy-security.md +244 -0
- package/skills/android-native-dev/references/testing.md +554 -0
- package/skills/android-native-dev/references/visual-design.md +246 -0
- package/skills/api-and-interface-design/SKILL.md +318 -0
- package/skills/browser-qa/SKILL.md +32 -0
- package/skills/browser-testing-with-devtools/SKILL.md +323 -0
- package/skills/build-failure-repair/SKILL.md +49 -0
- package/skills/chart-visualization/SKILL.md +73 -0
- package/skills/ci-cd-and-automation/SKILL.md +353 -0
- package/skills/code-review-and-quality/SKILL.md +375 -0
- package/skills/code-review-quality-gates/SKILL.md +132 -0
- package/skills/code-simplification/SKILL.md +371 -0
- package/skills/comment-accuracy-review/SKILL.md +37 -0
- package/skills/consulting-analysis/SKILL.md +69 -0
- package/skills/context-engineering/SKILL.md +375 -0
- package/skills/coverage-review/SKILL.md +24 -0
- package/skills/data-analysis/SKILL.md +74 -0
- package/skills/deprecation-and-migration/SKILL.md +222 -0
- package/skills/documentation-and-adrs/SKILL.md +330 -0
- package/skills/e2e-artifact-handling/SKILL.md +30 -0
- package/skills/evidence-scoped-retrospective/SKILL.md +156 -0
- package/skills/explain-by-allegory/SKILL.md +99 -0
- package/skills/flutter-dev/SKILL.md +162 -0
- package/skills/flutter-dev/references/animations.md +497 -0
- package/skills/flutter-dev/references/bloc-state.md +281 -0
- package/skills/flutter-dev/references/forms.md +656 -0
- package/skills/flutter-dev/references/gorouter-navigation.md +257 -0
- package/skills/flutter-dev/references/localization.md +510 -0
- package/skills/flutter-dev/references/networking.md +566 -0
- package/skills/flutter-dev/references/performance.md +305 -0
- package/skills/flutter-dev/references/platform-specific.md +417 -0
- package/skills/flutter-dev/references/project-structure.md +273 -0
- package/skills/flutter-dev/references/riverpod-state.md +232 -0
- package/skills/flutter-dev/references/testing.md +364 -0
- package/skills/flutter-dev/references/widget-patterns.md +233 -0
- package/skills/frontend-dev/SKILL.md +468 -0
- package/skills/frontend-dev/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/DMMono-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/skills/frontend-dev/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Gloock-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Italiana-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Jura-Light.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Jura-Medium.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Jura-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Lora-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Lora-Italic.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Lora-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/Lora-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Outfit-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/Tektur-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/skills/frontend-dev/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/skills/frontend-dev/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/skills/frontend-dev/references/asset-prompt-guide.md +43 -0
- package/skills/frontend-dev/references/env-setup.md +33 -0
- package/skills/frontend-dev/references/minimax-cli-reference.md +133 -0
- package/skills/frontend-dev/references/minimax-image-guide.md +65 -0
- package/skills/frontend-dev/references/minimax-music-guide.md +216 -0
- package/skills/frontend-dev/references/minimax-tts-guide.md +78 -0
- package/skills/frontend-dev/references/minimax-video-guide.md +82 -0
- package/skills/frontend-dev/references/minimax-voice-catalog.md +685 -0
- package/skills/frontend-dev/references/motion-recipes.md +407 -0
- package/skills/frontend-dev/references/troubleshooting.md +85 -0
- package/skills/frontend-dev/scripts/minimax_image.py +137 -0
- package/skills/frontend-dev/scripts/minimax_music.py +157 -0
- package/skills/frontend-dev/scripts/minimax_tts.py +127 -0
- package/skills/frontend-dev/scripts/minimax_video.py +187 -0
- package/skills/frontend-dev/templates/generator_template.js +223 -0
- package/skills/frontend-dev/templates/viewer.html +599 -0
- package/skills/frontend-ui-engineering/SKILL.md +367 -0
- package/skills/fullstack-dev/SKILL.md +819 -0
- package/skills/fullstack-dev/references/api-design.md +444 -0
- package/skills/fullstack-dev/references/auth-flow.md +165 -0
- package/skills/fullstack-dev/references/db-schema.md +706 -0
- package/skills/fullstack-dev/references/django-best-practices.md +466 -0
- package/skills/fullstack-dev/references/environment-management.md +78 -0
- package/skills/fullstack-dev/references/release-checklist.md +278 -0
- package/skills/fullstack-dev/references/technology-selection.md +254 -0
- package/skills/fullstack-dev/references/testing-strategy.md +404 -0
- package/skills/git-workflow-and-versioning/SKILL.md +446 -0
- package/skills/github-evidence-triage/SKILL.md +115 -0
- package/skills/harness-evolution/SKILL.md +59 -0
- package/skills/harness-evolution/references/activation-matrix.md +13 -0
- package/skills/harness-evolution/references/approval-policy.md +20 -0
- package/skills/harness-evolution/references/change-report-template.md +33 -0
- package/skills/harness-evolution/references/component-taxonomy.md +19 -0
- package/skills/harness-evolution/references/verdict-policy.md +13 -0
- package/skills/harness-issue-triage/SKILL.md +72 -0
- package/skills/harness-issue-triage/references/component-diagnosis.md +25 -0
- package/skills/harness-issue-triage/references/triage-report-template.md +34 -0
- package/skills/harness-optimization-audit/SKILL.md +125 -0
- package/skills/idea-refine/SKILL.md +191 -0
- package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/LICENSE +21 -0
- package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/NOTICE.md +9 -0
- package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/idea-refine/SKILL.upstream.md +178 -0
- package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/idea-refine/examples.md +238 -0
- package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/idea-refine/frameworks.md +99 -0
- package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/idea-refine/refinement-criteria.md +113 -0
- package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/idea-refine/scripts/idea-refine.upstream.sh +15 -0
- package/skills/incremental-implementation/SKILL.md +252 -0
- package/skills/ios-application-dev/SKILL.md +212 -0
- package/skills/ios-application-dev/references/accessibility.md +259 -0
- package/skills/ios-application-dev/references/graphics-animation.md +350 -0
- package/skills/ios-application-dev/references/layout-system.md +199 -0
- package/skills/ios-application-dev/references/metal-shader.md +178 -0
- package/skills/ios-application-dev/references/navigation-patterns.md +175 -0
- package/skills/ios-application-dev/references/swift-coding-standards.md +757 -0
- package/skills/ios-application-dev/references/swiftui-design-guidelines.md +1167 -0
- package/skills/ios-application-dev/references/system-integration.md +401 -0
- package/skills/ios-application-dev/references/uikit-components.md +297 -0
- package/skills/local-review-gate/SKILL.md +207 -0
- package/skills/local-review-gate/references/addyosmani-code-review-rubric.md +33 -0
- package/skills/local-review-gate/references/codex-github-compatibility.md +26 -0
- package/skills/local-review-gate/references/ecc-code-review-adaptation.md +40 -0
- package/skills/local-review-gate/references/graphify-local-review.md +33 -0
- package/skills/local-review-gate/references/orchestration-adaptation.md +51 -0
- package/skills/local-review-gate/references/review-repair-lane-adaptation.md +46 -0
- package/skills/local-review-gate/references/upstream-provenance.md +61 -0
- package/skills/mature-project-pattern-research/SKILL.md +155 -0
- package/skills/mature-project-pattern-research/references/research-rubric.md +45 -0
- package/skills/minimax-docx/LICENSE +21 -0
- package/skills/minimax-docx/SKILL.md +273 -0
- package/skills/minimax-docx/assets/styles/academic_styles.xml +250 -0
- package/skills/minimax-docx/assets/styles/corporate_styles.xml +284 -0
- package/skills/minimax-docx/assets/styles/default_styles.xml +449 -0
- package/skills/minimax-docx/assets/xsd/aesthetic-rules.xsd +470 -0
- package/skills/minimax-docx/assets/xsd/business-rules.xsd +130 -0
- package/skills/minimax-docx/assets/xsd/common-types.xsd +159 -0
- package/skills/minimax-docx/assets/xsd/wml-subset.xsd +589 -0
- package/skills/minimax-docx/references/cjk_typography.md +357 -0
- package/skills/minimax-docx/references/cjk_university_template_guide.md +184 -0
- package/skills/minimax-docx/references/comments_guide.md +191 -0
- package/skills/minimax-docx/references/design_good_bad_examples.md +829 -0
- package/skills/minimax-docx/references/design_principles.md +819 -0
- package/skills/minimax-docx/references/openxml_element_order.md +308 -0
- package/skills/minimax-docx/references/openxml_encyclopedia_part1.md +4061 -0
- package/skills/minimax-docx/references/openxml_encyclopedia_part2.md +2820 -0
- package/skills/minimax-docx/references/openxml_encyclopedia_part3.md +3381 -0
- package/skills/minimax-docx/references/openxml_namespaces.md +82 -0
- package/skills/minimax-docx/references/openxml_units.md +72 -0
- package/skills/minimax-docx/references/scenario_a_create.md +284 -0
- package/skills/minimax-docx/references/scenario_b_edit_content.md +295 -0
- package/skills/minimax-docx/references/scenario_c_apply_template.md +456 -0
- package/skills/minimax-docx/references/track_changes_guide.md +200 -0
- package/skills/minimax-docx/references/troubleshooting.md +506 -0
- package/skills/minimax-docx/references/typography_guide.md +294 -0
- package/skills/minimax-docx/references/xsd_validation_guide.md +158 -0
- package/skills/minimax-docx/scripts/doc_to_docx.sh +40 -0
- package/skills/minimax-docx/scripts/docx_preview.sh +37 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Cli/MiniMaxAIDocx.Cli.csproj +19 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Cli/Program.cs +18 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/AnalyzeCommand.cs +147 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/ApplyTemplateCommand.cs +322 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/CreateCommand.cs +324 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/DiffCommand.cs +155 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/EditContentCommand.cs +487 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/FixOrderCommand.cs +108 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/MergeRunsCommand.cs +122 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/ValidateCommand.cs +107 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/MiniMaxAIDocx.Core.csproj +15 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/CommentSynchronizer.cs +169 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/ElementOrder.cs +80 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/NamespaceConstants.cs +42 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/RunMerger.cs +81 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/StyleAnalyzer.cs +81 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/TrackChangesHelper.cs +99 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/UnitConverter.cs +23 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples.cs +1832 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch1.cs +910 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch2.cs +999 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch3.cs +1048 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch4.cs +1038 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/CharacterFormattingSamples.cs +1020 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/DocumentCreationSamples.cs +1121 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/FieldAndTocSamples.cs +624 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/FootnoteAndCommentSamples.cs +675 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/HeaderFooterSamples.cs +838 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ImageSamples.cs +917 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ListAndNumberingSamples.cs +826 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ParagraphFormattingSamples.cs +1199 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/StyleSystemSamples.cs +1487 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/TableSamples.cs +1163 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/TrackChangesSamples.cs +595 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/CjkHelper.cs +39 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/FontDefaults.cs +24 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/PageSizes.cs +20 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/BusinessRuleValidator.cs +224 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/GateCheckValidator.cs +148 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/ValidationResult.cs +23 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/XsdValidator.cs +69 -0
- package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.slnx +4 -0
- package/skills/minimax-docx/scripts/env_check.sh +196 -0
- package/skills/minimax-docx/scripts/setup.ps1 +274 -0
- package/skills/minimax-docx/scripts/setup.sh +504 -0
- package/skills/minimax-pdf/README.md +222 -0
- package/skills/minimax-pdf/SKILL.md +218 -0
- package/skills/minimax-pdf/design/design.md +381 -0
- package/skills/minimax-pdf/scripts/cover.py +1579 -0
- package/skills/minimax-pdf/scripts/fill_inspect.py +200 -0
- package/skills/minimax-pdf/scripts/fill_write.py +242 -0
- package/skills/minimax-pdf/scripts/make.sh +491 -0
- package/skills/minimax-pdf/scripts/merge.py +112 -0
- package/skills/minimax-pdf/scripts/palette.py +521 -0
- package/skills/minimax-pdf/scripts/reformat_parse.py +374 -0
- package/skills/minimax-pdf/scripts/render_body.py +1052 -0
- package/skills/minimax-pdf/scripts/render_cover.js +111 -0
- package/skills/minimax-xlsx/SKILL.md +156 -0
- package/skills/minimax-xlsx/references/create.md +691 -0
- package/skills/minimax-xlsx/references/edit.md +684 -0
- package/skills/minimax-xlsx/references/fix.md +37 -0
- package/skills/minimax-xlsx/references/format.md +768 -0
- package/skills/minimax-xlsx/references/ooxml-cheatsheet.md +231 -0
- package/skills/minimax-xlsx/references/read-analyze.md +97 -0
- package/skills/minimax-xlsx/references/validate.md +772 -0
- package/skills/minimax-xlsx/scripts/formula_check.py +422 -0
- package/skills/minimax-xlsx/scripts/libreoffice_recalc.py +248 -0
- package/skills/minimax-xlsx/scripts/shared_strings_builder.py +163 -0
- package/skills/minimax-xlsx/scripts/style_audit.py +575 -0
- package/skills/minimax-xlsx/scripts/xlsx_add_column.py +395 -0
- package/skills/minimax-xlsx/scripts/xlsx_insert_row.py +274 -0
- package/skills/minimax-xlsx/scripts/xlsx_pack.py +87 -0
- package/skills/minimax-xlsx/scripts/xlsx_reader.py +362 -0
- package/skills/minimax-xlsx/scripts/xlsx_shift_rows.py +396 -0
- package/skills/minimax-xlsx/scripts/xlsx_unpack.py +130 -0
- package/skills/minimax-xlsx/templates/minimal_xlsx/[Content_Types].xml +9 -0
- package/skills/minimax-xlsx/templates/minimal_xlsx/_rels/.rels +6 -0
- package/skills/minimax-xlsx/templates/minimal_xlsx/xl/_rels/workbook.xml.rels +19 -0
- package/skills/minimax-xlsx/templates/minimal_xlsx/xl/sharedStrings.xml +33 -0
- package/skills/minimax-xlsx/templates/minimal_xlsx/xl/styles.xml +160 -0
- package/skills/minimax-xlsx/templates/minimal_xlsx/xl/workbook.xml +30 -0
- package/skills/minimax-xlsx/templates/minimal_xlsx/xl/worksheets/sheet1.xml +70 -0
- package/skills/newsletter-generation/SKILL.md +71 -0
- package/skills/oss-release-readiness/SKILL.md +47 -0
- package/skills/parallel-subagent-dispatch/SKILL.md +58 -0
- package/skills/performance-optimization/SKILL.md +364 -0
- package/skills/planning-and-task-breakdown/SKILL.md +290 -0
- package/skills/pptx-generator/SKILL.md +275 -0
- package/skills/pptx-generator/references/design-system.md +392 -0
- package/skills/pptx-generator/references/editing.md +162 -0
- package/skills/pptx-generator/references/pitfalls.md +112 -0
- package/skills/pptx-generator/references/pptxgenjs.md +420 -0
- package/skills/pptx-generator/references/slide-types.md +413 -0
- package/skills/pr-test-analysis/SKILL.md +24 -0
- package/skills/react-native-dev/SKILL.md +180 -0
- package/skills/react-native-dev/references/animations.md +254 -0
- package/skills/react-native-dev/references/components.md +124 -0
- package/skills/react-native-dev/references/engineering.md +527 -0
- package/skills/react-native-dev/references/forms.md +300 -0
- package/skills/react-native-dev/references/native-capabilities.md +163 -0
- package/skills/react-native-dev/references/navigation.md +271 -0
- package/skills/react-native-dev/references/networking.md +346 -0
- package/skills/react-native-dev/references/performance.md +215 -0
- package/skills/react-native-dev/references/state-management.md +230 -0
- package/skills/react-native-dev/references/styling.md +117 -0
- package/skills/react-native-dev/references/testing.md +341 -0
- package/skills/requirements-grilling/SKILL.md +447 -0
- package/skills/requirements-grilling/references/ADR-FORMAT.md +46 -0
- package/skills/requirements-grilling/references/CONTEXT-FORMAT.md +41 -0
- package/skills/requirements-grilling/references/INTERVIEW-PACKET-FORMAT.md +62 -0
- package/skills/requirements-grilling/references/MIT-LICENSE-MATT-POCOCK.md +25 -0
- package/skills/requirements-grilling/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/LICENSE +21 -0
- package/skills/requirements-grilling/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/NOTICE.md +9 -0
- package/skills/requirements-grilling/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/interview-me/SKILL.upstream.md +225 -0
- package/skills/requirements-grilling/references/upstream/mattpocock-skills/9603c1cc8118d08bc1b3bf34cf714f62178dea3b/LICENSE +21 -0
- package/skills/requirements-grilling/references/upstream/mattpocock-skills/9603c1cc8118d08bc1b3bf34cf714f62178dea3b/NOTICE.md +12 -0
- package/skills/requirements-grilling/references/upstream/mattpocock-skills/9603c1cc8118d08bc1b3bf34cf714f62178dea3b/batch-grill-me/SKILL.upstream.md +15 -0
- package/skills/requirements-grilling/references/upstream/mattpocock-skills/9603c1cc8118d08bc1b3bf34cf714f62178dea3b/grill-me/SKILL.upstream.md +7 -0
- package/skills/requirements-grilling/references/upstream/mattpocock-skills/9603c1cc8118d08bc1b3bf34cf714f62178dea3b/grilling/SKILL.upstream.md +12 -0
- package/skills/review-pipeline/SKILL.md +38 -0
- package/skills/rose-memory/SKILL.md +210 -0
- package/skills/rose-memory/references/README.md +48 -0
- package/skills/rose-memory/references/memory_cli.py +2384 -0
- package/skills/rose-memory/references/schema.sql +315 -0
- package/skills/security-and-hardening/SKILL.md +369 -0
- package/skills/session-handoff/SKILL.md +88 -0
- package/skills/session-handoff/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/LICENSE +21 -0
- package/skills/session-handoff/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/NOTICE.md +9 -0
- package/skills/session-handoff/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/productivity/handoff/SKILL.upstream.md +16 -0
- package/skills/session-handoff/scripts/session_handoff.py +764 -0
- package/skills/shader-dev/SKILL.md +316 -0
- package/skills/shader-dev/reference/ambient-occlusion.md +382 -0
- package/skills/shader-dev/reference/analytic-ray-tracing.md +651 -0
- package/skills/shader-dev/reference/anti-aliasing.md +71 -0
- package/skills/shader-dev/reference/atmospheric-scattering.md +571 -0
- package/skills/shader-dev/reference/camera-effects.md +80 -0
- package/skills/shader-dev/reference/cellular-automata.md +635 -0
- package/skills/shader-dev/reference/color-palette.md +481 -0
- package/skills/shader-dev/reference/csg-boolean-operations.md +466 -0
- package/skills/shader-dev/reference/domain-repetition.md +436 -0
- package/skills/shader-dev/reference/domain-warping.md +419 -0
- package/skills/shader-dev/reference/fluid-simulation.md +425 -0
- package/skills/shader-dev/reference/fractal-rendering.md +525 -0
- package/skills/shader-dev/reference/lighting-model.md +639 -0
- package/skills/shader-dev/reference/matrix-transform.md +535 -0
- package/skills/shader-dev/reference/multipass-buffer.md +571 -0
- package/skills/shader-dev/reference/normal-estimation.md +418 -0
- package/skills/shader-dev/reference/particle-system.md +589 -0
- package/skills/shader-dev/reference/path-tracing-gi.md +602 -0
- package/skills/shader-dev/reference/polar-uv-manipulation.md +521 -0
- package/skills/shader-dev/reference/post-processing.md +375 -0
- package/skills/shader-dev/reference/procedural-2d-pattern.md +439 -0
- package/skills/shader-dev/reference/procedural-noise.md +551 -0
- package/skills/shader-dev/reference/ray-marching.md +396 -0
- package/skills/shader-dev/reference/sdf-2d.md +724 -0
- package/skills/shader-dev/reference/sdf-3d.md +805 -0
- package/skills/shader-dev/reference/sdf-tricks.md +63 -0
- package/skills/shader-dev/reference/shadow-techniques.md +476 -0
- package/skills/shader-dev/reference/simulation-physics.md +644 -0
- package/skills/shader-dev/reference/sound-synthesis.md +578 -0
- package/skills/shader-dev/reference/terrain-rendering.md +839 -0
- package/skills/shader-dev/reference/texture-mapping-advanced.md +87 -0
- package/skills/shader-dev/reference/texture-sampling.md +553 -0
- package/skills/shader-dev/reference/volumetric-rendering.md +608 -0
- package/skills/shader-dev/reference/voronoi-cellular-noise.md +486 -0
- package/skills/shader-dev/reference/voxel-rendering.md +701 -0
- package/skills/shader-dev/reference/water-ocean.md +445 -0
- package/skills/shader-dev/reference/webgl-pitfalls.md +41 -0
- package/skills/shader-dev/techniques/ambient-occlusion.md +364 -0
- package/skills/shader-dev/techniques/analytic-ray-tracing.md +542 -0
- package/skills/shader-dev/techniques/anti-aliasing.md +124 -0
- package/skills/shader-dev/techniques/atmospheric-scattering.md +522 -0
- package/skills/shader-dev/techniques/camera-effects.md +115 -0
- package/skills/shader-dev/techniques/cellular-automata.md +531 -0
- package/skills/shader-dev/techniques/color-palette.md +380 -0
- package/skills/shader-dev/techniques/csg-boolean-operations.md +491 -0
- package/skills/shader-dev/techniques/domain-repetition.md +333 -0
- package/skills/shader-dev/techniques/domain-warping.md +414 -0
- package/skills/shader-dev/techniques/fluid-simulation.md +1175 -0
- package/skills/shader-dev/techniques/fractal-rendering.md +436 -0
- package/skills/shader-dev/techniques/lighting-model.md +527 -0
- package/skills/shader-dev/techniques/matrix-transform.md +455 -0
- package/skills/shader-dev/techniques/multipass-buffer.md +922 -0
- package/skills/shader-dev/techniques/normal-estimation.md +318 -0
- package/skills/shader-dev/techniques/particle-system.md +1203 -0
- package/skills/shader-dev/techniques/path-tracing-gi.md +623 -0
- package/skills/shader-dev/techniques/polar-uv-manipulation.md +373 -0
- package/skills/shader-dev/techniques/post-processing.md +788 -0
- package/skills/shader-dev/techniques/procedural-2d-pattern.md +346 -0
- package/skills/shader-dev/techniques/procedural-noise.md +554 -0
- package/skills/shader-dev/techniques/ray-marching.md +467 -0
- package/skills/shader-dev/techniques/sdf-2d.md +631 -0
- package/skills/shader-dev/techniques/sdf-3d.md +589 -0
- package/skills/shader-dev/techniques/sdf-tricks.md +100 -0
- package/skills/shader-dev/techniques/shadow-techniques.md +776 -0
- package/skills/shader-dev/techniques/simulation-physics.md +1542 -0
- package/skills/shader-dev/techniques/sound-synthesis.md +490 -0
- package/skills/shader-dev/techniques/terrain-rendering.md +408 -0
- package/skills/shader-dev/techniques/texture-mapping-advanced.md +121 -0
- package/skills/shader-dev/techniques/texture-sampling.md +382 -0
- package/skills/shader-dev/techniques/volumetric-rendering.md +375 -0
- package/skills/shader-dev/techniques/voronoi-cellular-noise.md +458 -0
- package/skills/shader-dev/techniques/voxel-rendering.md +985 -0
- package/skills/shader-dev/techniques/water-ocean.md +490 -0
- package/skills/shader-dev/techniques/webgl-pitfalls.md +170 -0
- package/skills/shipping-and-launch/SKILL.md +334 -0
- package/skills/silent-failure-hunting/SKILL.md +24 -0
- package/skills/source-driven-development/SKILL.md +223 -0
- package/skills/spec-driven-development/SKILL.md +235 -0
- package/skills/spec-driven-development/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/LICENSE +21 -0
- package/skills/spec-driven-development/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/NOTICE.md +9 -0
- package/skills/spec-driven-development/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/spec-driven-development/SKILL.upstream.md +206 -0
- package/skills/strategy-stress-test/SKILL.md +185 -0
- package/skills/systematic-literature-review/SKILL.md +77 -0
- package/skills/test-document-generator/SKILL.md +152 -0
- package/skills/test-document-generator/references/test-document-template.md +45 -0
- package/skills/test-driven-development/SKILL.md +399 -0
- package/skills/write-skills/GLOSSARY.md +113 -0
- package/skills/write-skills/SKILL.md +133 -0
- package/skills/write-skills/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/LICENSE +21 -0
- package/skills/write-skills/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/NOTICE.md +9 -0
- package/skills/write-skills/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/productivity/writing-great-skills/GLOSSARY.md +201 -0
- package/skills/write-skills/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/productivity/writing-great-skills/SKILL.upstream.md +83 -0
- package/src/runtime/child-guard.ts +70 -0
- package/src/runtime/conflicts.ts +25 -0
- package/src/runtime/contracts.ts +9 -0
- package/src/runtime/doctor.ts +134 -0
- package/src/runtime/global-resources.ts +152 -0
- package/src/runtime/index.ts +23 -0
- package/src/runtime/lifecycle.ts +19 -0
- package/src/runtime/native-integrations.ts +44 -0
- package/src/runtime/path-boundaries.ts +69 -0
- package/src/runtime/registry.ts +213 -0
- package/src/runtime/roles.ts +64 -0
- package/src/runtime/rose-context.ts +24 -0
- package/src/runtime/subagents.ts +426 -0
- package/templates/APPEND_SYSTEM.md +12 -0
- package/upstream/aili-workflows.lock.json +3226 -0
|
@@ -0,0 +1,2384 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import difflib
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import sqlite3
|
|
11
|
+
import sys
|
|
12
|
+
import uuid
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Callable, Iterable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
SCHEMA_VERSION = 1
|
|
19
|
+
MIGRATION_NAME = "v1_initial_memory_schema"
|
|
20
|
+
DEFAULT_DB = "memory/memory.db"
|
|
21
|
+
DEFAULT_AGENT = "rose"
|
|
22
|
+
DEFAULT_TARGET = "AGENTS.md"
|
|
23
|
+
|
|
24
|
+
SIGNAL_WEIGHTS = {
|
|
25
|
+
"long_term_instruction": 3,
|
|
26
|
+
"correction_after_drift": 2,
|
|
27
|
+
"review_rejection": 3,
|
|
28
|
+
"ordinary_preference": 1,
|
|
29
|
+
"evidence_backed_failure": 1,
|
|
30
|
+
"risk_signal": 3,
|
|
31
|
+
"user_confirmation": 5,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
SEVERITY_RANK = {
|
|
35
|
+
"low": 0,
|
|
36
|
+
"normal": 1,
|
|
37
|
+
"high": 2,
|
|
38
|
+
"critical": 3,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
REQUIRED_TABLES = {
|
|
42
|
+
"schema_migration",
|
|
43
|
+
"memory_receipt",
|
|
44
|
+
"session",
|
|
45
|
+
"task",
|
|
46
|
+
"memory_event",
|
|
47
|
+
"memory_fact",
|
|
48
|
+
"claim",
|
|
49
|
+
"finding",
|
|
50
|
+
"evidence",
|
|
51
|
+
"rule_candidate",
|
|
52
|
+
"rule_candidate_evidence",
|
|
53
|
+
"instruction_snapshot",
|
|
54
|
+
"rule_patch",
|
|
55
|
+
"rule_decision",
|
|
56
|
+
"rule_promotion",
|
|
57
|
+
"audit_run",
|
|
58
|
+
"search_doc",
|
|
59
|
+
"search_fts",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CliError(Exception):
|
|
64
|
+
def __init__(self, error_code: str, message: str, suggested_action: str = "") -> None:
|
|
65
|
+
super().__init__(message)
|
|
66
|
+
self.error_code = error_code
|
|
67
|
+
self.message = message
|
|
68
|
+
self.suggested_action = suggested_action
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def utc_now() -> str:
|
|
72
|
+
return datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def project_default() -> str:
|
|
76
|
+
return Path.cwd().name
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def json_dump(value: Any) -> str:
|
|
80
|
+
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def emit(value: dict[str, Any], status: int = 0) -> None:
|
|
84
|
+
print(json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True))
|
|
85
|
+
raise SystemExit(status)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def sha256_text(text: str) -> str:
|
|
89
|
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def file_hash(path: str) -> str:
|
|
93
|
+
target = Path(path)
|
|
94
|
+
if not target.exists():
|
|
95
|
+
return sha256_text("")
|
|
96
|
+
return hashlib.sha256(target.read_bytes()).hexdigest()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def read_text_if_exists(path: str) -> str:
|
|
100
|
+
target = Path(path)
|
|
101
|
+
if not target.exists():
|
|
102
|
+
return ""
|
|
103
|
+
return target.read_text(encoding="utf-8")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def write_text(path: str, content: str) -> None:
|
|
107
|
+
Path(path).write_text(content, encoding="utf-8")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def normalize_rule_text(rule_text: str) -> str:
|
|
111
|
+
normalized = re.sub(r"\s+", " ", rule_text.strip())
|
|
112
|
+
if normalized.endswith("."):
|
|
113
|
+
normalized = normalized[:-1]
|
|
114
|
+
return normalized.lower()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def normalize_rule_key(rule_text: str) -> str:
|
|
118
|
+
normalized = re.sub(r"[^a-z0-9]+", "_", normalize_rule_text(rule_text))
|
|
119
|
+
normalized = normalized.strip("_")
|
|
120
|
+
return normalized[:80] or f"rule_{uuid.uuid4().hex[:12]}"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def query_tokens(raw_query: str) -> list[str]:
|
|
124
|
+
tokens = re.findall(r"[A-Za-z0-9_]+", raw_query)
|
|
125
|
+
if not tokens:
|
|
126
|
+
raise CliError("INVALID_SEARCH_QUERY", "search query must contain at least one word token")
|
|
127
|
+
return tokens
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def safe_fts_query(raw_query: str) -> str:
|
|
131
|
+
tokens = query_tokens(raw_query)
|
|
132
|
+
return " OR ".join(f'"{token}"' for token in tokens)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def token_like_clause(columns: list[str], tokens: list[str]) -> tuple[str, list[str]]:
|
|
136
|
+
clauses: list[str] = []
|
|
137
|
+
values: list[str] = []
|
|
138
|
+
for token in tokens:
|
|
139
|
+
like_value = f"%{token}%"
|
|
140
|
+
clauses.append("(" + " OR ".join(f"{column} LIKE ?" for column in columns) + ")")
|
|
141
|
+
values.extend([like_value] * len(columns))
|
|
142
|
+
return "(" + " OR ".join(clauses) + ")", values
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def safe_metadata(raw: str | None) -> str:
|
|
146
|
+
if not raw:
|
|
147
|
+
return "{}"
|
|
148
|
+
try:
|
|
149
|
+
parsed = json.loads(raw)
|
|
150
|
+
except json.JSONDecodeError as exc:
|
|
151
|
+
raise CliError("INVALID_JSON", f"metadata_json is invalid JSON: {exc}") from exc
|
|
152
|
+
if not isinstance(parsed, dict):
|
|
153
|
+
raise CliError("INVALID_JSON", "metadata_json must be a JSON object")
|
|
154
|
+
return json_dump(parsed)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def sensitive_indexable(sensitive: bool, indexable: bool) -> tuple[int, int]:
|
|
158
|
+
if sensitive:
|
|
159
|
+
return 1, 0
|
|
160
|
+
return 0, 1 if indexable else 0
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def row_to_dict(row: sqlite3.Row | None) -> dict[str, Any] | None:
|
|
164
|
+
if row is None:
|
|
165
|
+
return None
|
|
166
|
+
return {key: row[key] for key in row.keys()}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def schema_sql() -> str:
|
|
170
|
+
return """
|
|
171
|
+
CREATE TABLE IF NOT EXISTS schema_migration (
|
|
172
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
173
|
+
version INTEGER NOT NULL,
|
|
174
|
+
name TEXT NOT NULL,
|
|
175
|
+
checksum TEXT NOT NULL,
|
|
176
|
+
applied_at TEXT NOT NULL,
|
|
177
|
+
UNIQUE(version)
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
CREATE TABLE IF NOT EXISTS memory_receipt (
|
|
181
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
182
|
+
receipt_key TEXT NOT NULL UNIQUE,
|
|
183
|
+
operation TEXT NOT NULL,
|
|
184
|
+
status TEXT NOT NULL CHECK (status IN ('ok','error')),
|
|
185
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
186
|
+
created_at TEXT NOT NULL
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
CREATE TABLE IF NOT EXISTS session (
|
|
190
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
191
|
+
session_key TEXT NOT NULL UNIQUE,
|
|
192
|
+
project TEXT NOT NULL,
|
|
193
|
+
agent TEXT NOT NULL,
|
|
194
|
+
started_at TEXT NOT NULL,
|
|
195
|
+
ended_at TEXT,
|
|
196
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
CREATE TABLE IF NOT EXISTS task (
|
|
200
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
201
|
+
task_key TEXT NOT NULL UNIQUE,
|
|
202
|
+
session_id INTEGER REFERENCES session(id) ON DELETE SET NULL,
|
|
203
|
+
project TEXT NOT NULL,
|
|
204
|
+
agent TEXT NOT NULL,
|
|
205
|
+
title TEXT NOT NULL,
|
|
206
|
+
scope_text TEXT NOT NULL DEFAULT '',
|
|
207
|
+
status TEXT NOT NULL CHECK (status IN ('active','blocked','completed','cancelled')),
|
|
208
|
+
started_at TEXT NOT NULL,
|
|
209
|
+
completed_at TEXT,
|
|
210
|
+
outcome TEXT,
|
|
211
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
CREATE TABLE IF NOT EXISTS memory_event (
|
|
215
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
216
|
+
session_id INTEGER REFERENCES session(id) ON DELETE SET NULL,
|
|
217
|
+
task_id INTEGER REFERENCES task(id) ON DELETE SET NULL,
|
|
218
|
+
event_type TEXT NOT NULL,
|
|
219
|
+
state TEXT,
|
|
220
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
221
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
222
|
+
created_at TEXT NOT NULL
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
CREATE TABLE IF NOT EXISTS memory_fact (
|
|
226
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
227
|
+
session_id INTEGER REFERENCES session(id) ON DELETE SET NULL,
|
|
228
|
+
project TEXT NOT NULL,
|
|
229
|
+
agent TEXT NOT NULL,
|
|
230
|
+
fact_key TEXT NOT NULL,
|
|
231
|
+
fact_text TEXT NOT NULL,
|
|
232
|
+
category TEXT NOT NULL,
|
|
233
|
+
confidence TEXT NOT NULL DEFAULT 'medium'
|
|
234
|
+
CHECK (confidence IN ('low','medium','high')),
|
|
235
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
236
|
+
CHECK (status IN ('active','superseded','rejected','expired')),
|
|
237
|
+
sensitive INTEGER NOT NULL DEFAULT 0 CHECK (sensitive IN (0,1)),
|
|
238
|
+
indexable INTEGER NOT NULL DEFAULT 1 CHECK (indexable IN (0,1)),
|
|
239
|
+
first_seen_at TEXT NOT NULL,
|
|
240
|
+
last_seen_at TEXT NOT NULL,
|
|
241
|
+
superseded_by INTEGER REFERENCES memory_fact(id),
|
|
242
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
243
|
+
UNIQUE(project, agent, fact_key)
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
CREATE TABLE IF NOT EXISTS claim (
|
|
247
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
248
|
+
session_id INTEGER REFERENCES session(id) ON DELETE SET NULL,
|
|
249
|
+
task_id INTEGER REFERENCES task(id) ON DELETE SET NULL,
|
|
250
|
+
project TEXT NOT NULL,
|
|
251
|
+
agent TEXT NOT NULL,
|
|
252
|
+
claim_key TEXT NOT NULL,
|
|
253
|
+
claim_text TEXT NOT NULL,
|
|
254
|
+
verification_status TEXT NOT NULL
|
|
255
|
+
CHECK (verification_status IN ('unverified','verified','disputed','superseded')),
|
|
256
|
+
evidence_summary TEXT NOT NULL DEFAULT '',
|
|
257
|
+
sensitive INTEGER NOT NULL DEFAULT 0 CHECK (sensitive IN (0,1)),
|
|
258
|
+
indexable INTEGER NOT NULL DEFAULT 1 CHECK (indexable IN (0,1)),
|
|
259
|
+
created_at TEXT NOT NULL,
|
|
260
|
+
updated_at TEXT NOT NULL,
|
|
261
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
262
|
+
UNIQUE(project, agent, claim_key)
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
CREATE TABLE IF NOT EXISTS finding (
|
|
266
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
267
|
+
session_id INTEGER REFERENCES session(id) ON DELETE SET NULL,
|
|
268
|
+
task_id INTEGER REFERENCES task(id) ON DELETE SET NULL,
|
|
269
|
+
project TEXT NOT NULL,
|
|
270
|
+
agent TEXT NOT NULL,
|
|
271
|
+
kind TEXT NOT NULL
|
|
272
|
+
CHECK (kind IN ('correction','failure','review_rejection','preference','risk','observation')),
|
|
273
|
+
category TEXT NOT NULL,
|
|
274
|
+
severity TEXT NOT NULL DEFAULT 'normal'
|
|
275
|
+
CHECK (severity IN ('low','normal','high','critical')),
|
|
276
|
+
wrong_behavior TEXT NOT NULL DEFAULT '',
|
|
277
|
+
correct_behavior TEXT NOT NULL DEFAULT '',
|
|
278
|
+
summary TEXT NOT NULL,
|
|
279
|
+
raw_ref TEXT NOT NULL DEFAULT '',
|
|
280
|
+
sensitive INTEGER NOT NULL DEFAULT 0 CHECK (sensitive IN (0,1)),
|
|
281
|
+
indexable INTEGER NOT NULL DEFAULT 1 CHECK (indexable IN (0,1)),
|
|
282
|
+
created_at TEXT NOT NULL,
|
|
283
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
CREATE TABLE IF NOT EXISTS evidence (
|
|
287
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
288
|
+
session_id INTEGER REFERENCES session(id) ON DELETE SET NULL,
|
|
289
|
+
task_id INTEGER REFERENCES task(id) ON DELETE SET NULL,
|
|
290
|
+
project TEXT NOT NULL,
|
|
291
|
+
agent TEXT NOT NULL,
|
|
292
|
+
source_type TEXT NOT NULL
|
|
293
|
+
CHECK (source_type IN ('finding','claim','memory_event','task','file','diff','test','user_confirmation')),
|
|
294
|
+
source_id INTEGER,
|
|
295
|
+
evidence_key TEXT NOT NULL,
|
|
296
|
+
evidence_text TEXT NOT NULL,
|
|
297
|
+
evidence_hash TEXT NOT NULL,
|
|
298
|
+
weight INTEGER NOT NULL DEFAULT 1 CHECK (weight >= 0),
|
|
299
|
+
sensitive INTEGER NOT NULL DEFAULT 0 CHECK (sensitive IN (0,1)),
|
|
300
|
+
indexable INTEGER NOT NULL DEFAULT 1 CHECK (indexable IN (0,1)),
|
|
301
|
+
created_at TEXT NOT NULL,
|
|
302
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
303
|
+
UNIQUE(project, agent, evidence_key)
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
CREATE TABLE IF NOT EXISTS rule_candidate (
|
|
307
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
308
|
+
project TEXT NOT NULL,
|
|
309
|
+
agent TEXT NOT NULL,
|
|
310
|
+
rule_key TEXT NOT NULL,
|
|
311
|
+
normalized_rule_hash TEXT NOT NULL,
|
|
312
|
+
rule_text TEXT NOT NULL,
|
|
313
|
+
category TEXT NOT NULL,
|
|
314
|
+
severity TEXT NOT NULL DEFAULT 'normal'
|
|
315
|
+
CHECK (severity IN ('low','normal','high','critical')),
|
|
316
|
+
status TEXT NOT NULL DEFAULT 'memory'
|
|
317
|
+
CHECK (status IN (
|
|
318
|
+
'memory',
|
|
319
|
+
'candidate',
|
|
320
|
+
'needs_reconciliation',
|
|
321
|
+
'approved',
|
|
322
|
+
'promoted',
|
|
323
|
+
'rejected',
|
|
324
|
+
'superseded',
|
|
325
|
+
'expired'
|
|
326
|
+
)),
|
|
327
|
+
score INTEGER NOT NULL DEFAULT 0 CHECK (score >= 0),
|
|
328
|
+
mention_count INTEGER NOT NULL DEFAULT 0 CHECK (mention_count >= 0),
|
|
329
|
+
session_count INTEGER NOT NULL DEFAULT 0 CHECK (session_count >= 0),
|
|
330
|
+
evidence_count INTEGER NOT NULL DEFAULT 0 CHECK (evidence_count >= 0),
|
|
331
|
+
promotion_target TEXT NOT NULL DEFAULT 'AGENTS.md',
|
|
332
|
+
target_scope TEXT NOT NULL DEFAULT 'project_root_agents'
|
|
333
|
+
CHECK (target_scope IN ('project_root_agents')),
|
|
334
|
+
conflicts_json TEXT NOT NULL DEFAULT '[]',
|
|
335
|
+
requires_user_approval INTEGER NOT NULL DEFAULT 1 CHECK (requires_user_approval IN (0,1)),
|
|
336
|
+
first_seen_at TEXT NOT NULL,
|
|
337
|
+
last_seen_at TEXT NOT NULL,
|
|
338
|
+
approved_at TEXT,
|
|
339
|
+
promoted_at TEXT,
|
|
340
|
+
superseded_by INTEGER REFERENCES rule_candidate(id),
|
|
341
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
342
|
+
UNIQUE(project, agent, rule_key)
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
CREATE TABLE IF NOT EXISTS rule_candidate_evidence (
|
|
346
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
347
|
+
rule_id INTEGER NOT NULL REFERENCES rule_candidate(id) ON DELETE CASCADE,
|
|
348
|
+
evidence_id INTEGER NOT NULL REFERENCES evidence(id) ON DELETE RESTRICT,
|
|
349
|
+
signal_type TEXT NOT NULL
|
|
350
|
+
CHECK (signal_type IN (
|
|
351
|
+
'long_term_instruction',
|
|
352
|
+
'correction_after_drift',
|
|
353
|
+
'review_rejection',
|
|
354
|
+
'ordinary_preference',
|
|
355
|
+
'evidence_backed_failure',
|
|
356
|
+
'risk_signal',
|
|
357
|
+
'user_confirmation'
|
|
358
|
+
)),
|
|
359
|
+
weight INTEGER NOT NULL CHECK (weight >= 0),
|
|
360
|
+
created_at TEXT NOT NULL,
|
|
361
|
+
UNIQUE(rule_id, evidence_id)
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
CREATE TABLE IF NOT EXISTS instruction_snapshot (
|
|
365
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
366
|
+
target_path TEXT NOT NULL,
|
|
367
|
+
target_scope TEXT NOT NULL
|
|
368
|
+
CHECK (target_scope IN ('project_root_agents')),
|
|
369
|
+
hash_algorithm TEXT NOT NULL DEFAULT 'sha256',
|
|
370
|
+
content_hash TEXT NOT NULL,
|
|
371
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
372
|
+
captured_at TEXT NOT NULL,
|
|
373
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
CREATE TABLE IF NOT EXISTS rule_patch (
|
|
377
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
378
|
+
rule_id INTEGER NOT NULL REFERENCES rule_candidate(id) ON DELETE CASCADE,
|
|
379
|
+
target_path TEXT NOT NULL DEFAULT 'AGENTS.md',
|
|
380
|
+
target_scope TEXT NOT NULL DEFAULT 'project_root_agents'
|
|
381
|
+
CHECK (target_scope IN ('project_root_agents')),
|
|
382
|
+
base_snapshot_id INTEGER NOT NULL REFERENCES instruction_snapshot(id),
|
|
383
|
+
base_hash TEXT NOT NULL,
|
|
384
|
+
patch_hash TEXT NOT NULL,
|
|
385
|
+
proposed_rule_hash TEXT NOT NULL,
|
|
386
|
+
patch_text TEXT NOT NULL,
|
|
387
|
+
status TEXT NOT NULL DEFAULT 'proposed'
|
|
388
|
+
CHECK (status IN ('proposed','approved','rejected','applied','stale','failed')),
|
|
389
|
+
created_at TEXT NOT NULL,
|
|
390
|
+
updated_at TEXT NOT NULL,
|
|
391
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
CREATE TABLE IF NOT EXISTS rule_decision (
|
|
395
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
396
|
+
rule_id INTEGER NOT NULL REFERENCES rule_candidate(id) ON DELETE CASCADE,
|
|
397
|
+
patch_id INTEGER NOT NULL REFERENCES rule_patch(id) ON DELETE CASCADE,
|
|
398
|
+
decision TEXT NOT NULL
|
|
399
|
+
CHECK (decision IN ('approved','rejected','superseded','needs_reconciliation')),
|
|
400
|
+
approved_rule_hash TEXT,
|
|
401
|
+
approved_patch_hash TEXT,
|
|
402
|
+
actor TEXT NOT NULL,
|
|
403
|
+
reason TEXT NOT NULL DEFAULT '',
|
|
404
|
+
decided_at TEXT NOT NULL,
|
|
405
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
CREATE TABLE IF NOT EXISTS rule_promotion (
|
|
409
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
410
|
+
rule_id INTEGER NOT NULL REFERENCES rule_candidate(id) ON DELETE CASCADE,
|
|
411
|
+
patch_id INTEGER NOT NULL REFERENCES rule_patch(id) ON DELETE CASCADE,
|
|
412
|
+
decision_id INTEGER NOT NULL REFERENCES rule_decision(id) ON DELETE RESTRICT,
|
|
413
|
+
mode TEXT NOT NULL CHECK (mode IN ('apply','record_applied')),
|
|
414
|
+
target_path TEXT NOT NULL,
|
|
415
|
+
old_target_hash TEXT NOT NULL,
|
|
416
|
+
new_target_hash TEXT NOT NULL,
|
|
417
|
+
applied_by TEXT NOT NULL,
|
|
418
|
+
applied_at TEXT NOT NULL,
|
|
419
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
420
|
+
UNIQUE(patch_id)
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
CREATE TABLE IF NOT EXISTS audit_run (
|
|
424
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
425
|
+
audit_key TEXT NOT NULL UNIQUE,
|
|
426
|
+
operation TEXT NOT NULL,
|
|
427
|
+
status TEXT NOT NULL CHECK (status IN ('ok','error')),
|
|
428
|
+
checks_json TEXT NOT NULL DEFAULT '{}',
|
|
429
|
+
created_at TEXT NOT NULL
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
CREATE TABLE IF NOT EXISTS search_doc (
|
|
433
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
434
|
+
project TEXT NOT NULL,
|
|
435
|
+
agent TEXT NOT NULL,
|
|
436
|
+
doc_type TEXT NOT NULL,
|
|
437
|
+
source_table TEXT,
|
|
438
|
+
source_id INTEGER,
|
|
439
|
+
title TEXT NOT NULL DEFAULT '',
|
|
440
|
+
body TEXT NOT NULL,
|
|
441
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
442
|
+
sensitive INTEGER NOT NULL DEFAULT 0 CHECK (sensitive IN (0,1)),
|
|
443
|
+
indexable INTEGER NOT NULL DEFAULT 1 CHECK (indexable IN (0,1)),
|
|
444
|
+
created_at TEXT NOT NULL,
|
|
445
|
+
updated_at TEXT NOT NULL,
|
|
446
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_search_doc_source
|
|
450
|
+
ON search_doc(project, agent, source_table, source_id)
|
|
451
|
+
WHERE source_table IS NOT NULL AND source_id IS NOT NULL;
|
|
452
|
+
|
|
453
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5(
|
|
454
|
+
title,
|
|
455
|
+
body,
|
|
456
|
+
summary,
|
|
457
|
+
content='search_doc',
|
|
458
|
+
content_rowid='id'
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
CREATE TRIGGER IF NOT EXISTS search_doc_ai AFTER INSERT ON search_doc
|
|
462
|
+
WHEN new.sensitive = 0 AND new.indexable = 1
|
|
463
|
+
BEGIN
|
|
464
|
+
INSERT INTO search_fts(rowid, title, body, summary)
|
|
465
|
+
VALUES (new.id, new.title, new.body, new.summary);
|
|
466
|
+
END;
|
|
467
|
+
|
|
468
|
+
CREATE TRIGGER IF NOT EXISTS search_doc_ad AFTER DELETE ON search_doc
|
|
469
|
+
WHEN old.sensitive = 0 AND old.indexable = 1
|
|
470
|
+
BEGIN
|
|
471
|
+
INSERT INTO search_fts(search_fts, rowid, title, body, summary)
|
|
472
|
+
VALUES ('delete', old.id, old.title, old.body, old.summary);
|
|
473
|
+
END;
|
|
474
|
+
|
|
475
|
+
CREATE TRIGGER IF NOT EXISTS search_doc_au AFTER UPDATE ON search_doc
|
|
476
|
+
BEGIN
|
|
477
|
+
INSERT INTO search_fts(search_fts, rowid, title, body, summary)
|
|
478
|
+
SELECT 'delete', old.id, old.title, old.body, old.summary
|
|
479
|
+
WHERE old.sensitive = 0 AND old.indexable = 1;
|
|
480
|
+
|
|
481
|
+
INSERT INTO search_fts(rowid, title, body, summary)
|
|
482
|
+
SELECT new.id, new.title, new.body, new.summary
|
|
483
|
+
WHERE new.sensitive = 0 AND new.indexable = 1;
|
|
484
|
+
END;
|
|
485
|
+
"""
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def schema_checksum() -> str:
|
|
489
|
+
return sha256_text(schema_sql())
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def connect(db_path: str, create_parent: bool = False) -> sqlite3.Connection:
|
|
493
|
+
if create_parent:
|
|
494
|
+
parent = Path(db_path).parent
|
|
495
|
+
if str(parent) and str(parent) != ".":
|
|
496
|
+
parent.mkdir(parents=True, exist_ok=True)
|
|
497
|
+
conn = sqlite3.connect(db_path)
|
|
498
|
+
conn.row_factory = sqlite3.Row
|
|
499
|
+
conn.execute("PRAGMA foreign_keys = ON")
|
|
500
|
+
conn.execute("PRAGMA busy_timeout = 5000")
|
|
501
|
+
conn.execute("PRAGMA journal_mode = WAL")
|
|
502
|
+
return conn
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def ensure_schema(conn: sqlite3.Connection) -> bool:
|
|
506
|
+
conn.executescript(schema_sql())
|
|
507
|
+
existing = conn.execute(
|
|
508
|
+
"SELECT id FROM schema_migration WHERE version = ?",
|
|
509
|
+
(SCHEMA_VERSION,),
|
|
510
|
+
).fetchone()
|
|
511
|
+
if existing:
|
|
512
|
+
return False
|
|
513
|
+
conn.execute(
|
|
514
|
+
"""
|
|
515
|
+
INSERT INTO schema_migration (version, name, checksum, applied_at)
|
|
516
|
+
VALUES (?, ?, ?, ?)
|
|
517
|
+
""",
|
|
518
|
+
(SCHEMA_VERSION, MIGRATION_NAME, schema_checksum(), utc_now()),
|
|
519
|
+
)
|
|
520
|
+
return True
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def write_receipt(
|
|
524
|
+
conn: sqlite3.Connection,
|
|
525
|
+
operation: str,
|
|
526
|
+
payload: dict[str, Any],
|
|
527
|
+
status: str = "ok",
|
|
528
|
+
) -> str:
|
|
529
|
+
receipt_key = f"wr_{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}_{uuid.uuid4().hex[:8]}"
|
|
530
|
+
conn.execute(
|
|
531
|
+
"""
|
|
532
|
+
INSERT INTO memory_receipt (receipt_key, operation, status, payload_json, created_at)
|
|
533
|
+
VALUES (?, ?, ?, ?, ?)
|
|
534
|
+
""",
|
|
535
|
+
(receipt_key, operation, status, json_dump(payload), utc_now()),
|
|
536
|
+
)
|
|
537
|
+
return receipt_key
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def require_schema(conn: sqlite3.Connection) -> None:
|
|
541
|
+
row = conn.execute(
|
|
542
|
+
"SELECT version, checksum FROM schema_migration WHERE version = ?",
|
|
543
|
+
(SCHEMA_VERSION,),
|
|
544
|
+
).fetchone()
|
|
545
|
+
if not row:
|
|
546
|
+
raise CliError(
|
|
547
|
+
"SCHEMA_MISSING",
|
|
548
|
+
"memory database is missing the required schema version",
|
|
549
|
+
"run: rose-memory init --db memory/memory.db",
|
|
550
|
+
)
|
|
551
|
+
if row["checksum"] != schema_checksum():
|
|
552
|
+
raise CliError(
|
|
553
|
+
"SCHEMA_CHECKSUM_MISMATCH",
|
|
554
|
+
"schema checksum does not match this rose-memory CLI version",
|
|
555
|
+
"run: rose-memory migrate --db memory/memory.db",
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def table_exists(conn: sqlite3.Connection, table_name: str) -> bool:
|
|
560
|
+
row = conn.execute(
|
|
561
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
562
|
+
(table_name,),
|
|
563
|
+
).fetchone()
|
|
564
|
+
return row is not None
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def dedupe_search_docs(conn: sqlite3.Connection) -> int:
|
|
568
|
+
if not table_exists(conn, "search_doc"):
|
|
569
|
+
return 0
|
|
570
|
+
duplicate_count = conn.execute(
|
|
571
|
+
"""
|
|
572
|
+
SELECT COUNT(*)
|
|
573
|
+
FROM search_doc
|
|
574
|
+
WHERE source_table IS NOT NULL
|
|
575
|
+
AND source_id IS NOT NULL
|
|
576
|
+
AND id NOT IN (
|
|
577
|
+
SELECT MAX(id)
|
|
578
|
+
FROM search_doc
|
|
579
|
+
WHERE source_table IS NOT NULL AND source_id IS NOT NULL
|
|
580
|
+
GROUP BY project, agent, source_table, source_id
|
|
581
|
+
)
|
|
582
|
+
"""
|
|
583
|
+
).fetchone()[0]
|
|
584
|
+
conn.execute(
|
|
585
|
+
"""
|
|
586
|
+
DELETE FROM search_doc
|
|
587
|
+
WHERE source_table IS NOT NULL
|
|
588
|
+
AND source_id IS NOT NULL
|
|
589
|
+
AND id NOT IN (
|
|
590
|
+
SELECT MAX(id)
|
|
591
|
+
FROM search_doc
|
|
592
|
+
WHERE source_table IS NOT NULL AND source_id IS NOT NULL
|
|
593
|
+
GROUP BY project, agent, source_table, source_id
|
|
594
|
+
)
|
|
595
|
+
"""
|
|
596
|
+
)
|
|
597
|
+
return int(duplicate_count)
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def rebuild_fts(conn: sqlite3.Connection) -> bool:
|
|
601
|
+
if not table_exists(conn, "search_fts"):
|
|
602
|
+
return False
|
|
603
|
+
conn.execute("INSERT INTO search_fts(search_fts) VALUES ('rebuild')")
|
|
604
|
+
return True
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def get_by_key(
|
|
608
|
+
conn: sqlite3.Connection,
|
|
609
|
+
table: str,
|
|
610
|
+
key_column: str,
|
|
611
|
+
key_value: str | None,
|
|
612
|
+
) -> sqlite3.Row | None:
|
|
613
|
+
if not key_value:
|
|
614
|
+
return None
|
|
615
|
+
return conn.execute(
|
|
616
|
+
f"SELECT * FROM {table} WHERE {key_column} = ?",
|
|
617
|
+
(key_value,),
|
|
618
|
+
).fetchone()
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def require_by_id(conn: sqlite3.Connection, table: str, row_id: int) -> sqlite3.Row:
|
|
622
|
+
row = conn.execute(f"SELECT * FROM {table} WHERE id = ?", (row_id,)).fetchone()
|
|
623
|
+
if not row:
|
|
624
|
+
raise CliError("NOT_FOUND", f"{table} id {row_id} does not exist")
|
|
625
|
+
return row
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def require_session(conn: sqlite3.Connection, session_key: str) -> sqlite3.Row:
|
|
629
|
+
row = get_by_key(conn, "session", "session_key", session_key)
|
|
630
|
+
if not row:
|
|
631
|
+
raise CliError("SESSION_NOT_FOUND", f"session {session_key!r} does not exist")
|
|
632
|
+
return row
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
def optional_task(conn: sqlite3.Connection, task_key: str | None) -> sqlite3.Row | None:
|
|
636
|
+
return get_by_key(conn, "task", "task_key", task_key)
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def index_search_doc(
|
|
640
|
+
conn: sqlite3.Connection,
|
|
641
|
+
project: str,
|
|
642
|
+
agent: str,
|
|
643
|
+
doc_type: str,
|
|
644
|
+
source_table: str,
|
|
645
|
+
source_id: int,
|
|
646
|
+
title: str,
|
|
647
|
+
body: str,
|
|
648
|
+
summary: str,
|
|
649
|
+
sensitive: int,
|
|
650
|
+
indexable: int,
|
|
651
|
+
metadata: dict[str, Any] | None = None,
|
|
652
|
+
) -> int | None:
|
|
653
|
+
conn.execute(
|
|
654
|
+
"""
|
|
655
|
+
DELETE FROM search_doc
|
|
656
|
+
WHERE project = ? AND agent = ? AND source_table = ? AND source_id = ?
|
|
657
|
+
""",
|
|
658
|
+
(project, agent, source_table, source_id),
|
|
659
|
+
)
|
|
660
|
+
if sensitive or not indexable:
|
|
661
|
+
return None
|
|
662
|
+
now = utc_now()
|
|
663
|
+
cursor = conn.execute(
|
|
664
|
+
"""
|
|
665
|
+
INSERT INTO search_doc (
|
|
666
|
+
project, agent, doc_type, source_table, source_id, title, body, summary,
|
|
667
|
+
sensitive, indexable, created_at, updated_at, metadata_json
|
|
668
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
669
|
+
""",
|
|
670
|
+
(
|
|
671
|
+
project,
|
|
672
|
+
agent,
|
|
673
|
+
doc_type,
|
|
674
|
+
source_table,
|
|
675
|
+
source_id,
|
|
676
|
+
title,
|
|
677
|
+
body,
|
|
678
|
+
summary,
|
|
679
|
+
sensitive,
|
|
680
|
+
indexable,
|
|
681
|
+
now,
|
|
682
|
+
now,
|
|
683
|
+
json_dump(metadata or {}),
|
|
684
|
+
),
|
|
685
|
+
)
|
|
686
|
+
return int(cursor.lastrowid)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def add_evidence_row(
|
|
690
|
+
conn: sqlite3.Connection,
|
|
691
|
+
session_id: int | None,
|
|
692
|
+
task_id: int | None,
|
|
693
|
+
project: str,
|
|
694
|
+
agent: str,
|
|
695
|
+
source_type: str,
|
|
696
|
+
source_id: int | None,
|
|
697
|
+
evidence_key: str,
|
|
698
|
+
evidence_text: str,
|
|
699
|
+
weight: int,
|
|
700
|
+
sensitive: int,
|
|
701
|
+
indexable: int,
|
|
702
|
+
metadata_json: str = "{}",
|
|
703
|
+
) -> int:
|
|
704
|
+
evidence_hash = sha256_text(evidence_text)
|
|
705
|
+
cursor = conn.execute(
|
|
706
|
+
"""
|
|
707
|
+
INSERT INTO evidence (
|
|
708
|
+
session_id, task_id, project, agent, source_type, source_id, evidence_key,
|
|
709
|
+
evidence_text, evidence_hash, weight, sensitive, indexable, created_at, metadata_json
|
|
710
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
711
|
+
""",
|
|
712
|
+
(
|
|
713
|
+
session_id,
|
|
714
|
+
task_id,
|
|
715
|
+
project,
|
|
716
|
+
agent,
|
|
717
|
+
source_type,
|
|
718
|
+
source_id,
|
|
719
|
+
evidence_key,
|
|
720
|
+
evidence_text,
|
|
721
|
+
evidence_hash,
|
|
722
|
+
weight,
|
|
723
|
+
sensitive,
|
|
724
|
+
indexable,
|
|
725
|
+
utc_now(),
|
|
726
|
+
metadata_json,
|
|
727
|
+
),
|
|
728
|
+
)
|
|
729
|
+
evidence_id = int(cursor.lastrowid)
|
|
730
|
+
index_search_doc(
|
|
731
|
+
conn,
|
|
732
|
+
project,
|
|
733
|
+
agent,
|
|
734
|
+
"evidence",
|
|
735
|
+
"evidence",
|
|
736
|
+
evidence_id,
|
|
737
|
+
evidence_key,
|
|
738
|
+
evidence_text,
|
|
739
|
+
evidence_text[:240],
|
|
740
|
+
sensitive,
|
|
741
|
+
indexable,
|
|
742
|
+
)
|
|
743
|
+
return evidence_id
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def write_command(
|
|
747
|
+
args: argparse.Namespace,
|
|
748
|
+
operation: str,
|
|
749
|
+
handler: Callable[[sqlite3.Connection], dict[str, Any]],
|
|
750
|
+
create_parent: bool = False,
|
|
751
|
+
) -> None:
|
|
752
|
+
try:
|
|
753
|
+
conn = connect(args.db, create_parent=create_parent)
|
|
754
|
+
with conn:
|
|
755
|
+
if operation not in {"init", "migrate"}:
|
|
756
|
+
require_schema(conn)
|
|
757
|
+
result = handler(conn)
|
|
758
|
+
receipt_key = write_receipt(conn, operation, result)
|
|
759
|
+
conn.close()
|
|
760
|
+
emit({"ok": True, "receipt_id": receipt_key, "operation": operation, "result": result})
|
|
761
|
+
except CliError as exc:
|
|
762
|
+
emit(
|
|
763
|
+
{
|
|
764
|
+
"ok": False,
|
|
765
|
+
"error_code": exc.error_code,
|
|
766
|
+
"message": exc.message,
|
|
767
|
+
"suggested_action": exc.suggested_action,
|
|
768
|
+
},
|
|
769
|
+
status=1,
|
|
770
|
+
)
|
|
771
|
+
except sqlite3.Error as exc:
|
|
772
|
+
emit(
|
|
773
|
+
{
|
|
774
|
+
"ok": False,
|
|
775
|
+
"error_code": "SQLITE_ERROR",
|
|
776
|
+
"message": str(exc),
|
|
777
|
+
"suggested_action": "run doctor or inspect the database schema",
|
|
778
|
+
},
|
|
779
|
+
status=1,
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
def read_command(
|
|
784
|
+
args: argparse.Namespace,
|
|
785
|
+
handler: Callable[[sqlite3.Connection], dict[str, Any]],
|
|
786
|
+
) -> None:
|
|
787
|
+
try:
|
|
788
|
+
if not Path(args.db).exists():
|
|
789
|
+
raise CliError("DB_MISSING", f"database {args.db!r} does not exist")
|
|
790
|
+
conn = connect(args.db)
|
|
791
|
+
require_schema(conn)
|
|
792
|
+
result = handler(conn)
|
|
793
|
+
conn.close()
|
|
794
|
+
emit({"ok": True, "result": result})
|
|
795
|
+
except CliError as exc:
|
|
796
|
+
emit(
|
|
797
|
+
{
|
|
798
|
+
"ok": False,
|
|
799
|
+
"error_code": exc.error_code,
|
|
800
|
+
"message": exc.message,
|
|
801
|
+
"suggested_action": exc.suggested_action,
|
|
802
|
+
},
|
|
803
|
+
status=1,
|
|
804
|
+
)
|
|
805
|
+
except sqlite3.Error as exc:
|
|
806
|
+
emit(
|
|
807
|
+
{
|
|
808
|
+
"ok": False,
|
|
809
|
+
"error_code": "SQLITE_ERROR",
|
|
810
|
+
"message": str(exc),
|
|
811
|
+
"suggested_action": "run doctor or inspect the database schema",
|
|
812
|
+
},
|
|
813
|
+
status=1,
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def command_init(args: argparse.Namespace) -> None:
|
|
818
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
819
|
+
created = ensure_schema(conn)
|
|
820
|
+
session_count = conn.execute("SELECT COUNT(*) AS c FROM session").fetchone()["c"]
|
|
821
|
+
if session_count == 0:
|
|
822
|
+
now = utc_now()
|
|
823
|
+
conn.execute(
|
|
824
|
+
"""
|
|
825
|
+
INSERT INTO session (session_key, project, agent, started_at, metadata_json)
|
|
826
|
+
VALUES (?, ?, ?, ?, ?)
|
|
827
|
+
""",
|
|
828
|
+
("bootstrap", args.project, args.agent, now, json_dump({"source": "init"})),
|
|
829
|
+
)
|
|
830
|
+
session_id = conn.execute(
|
|
831
|
+
"SELECT id FROM session WHERE session_key = ?", ("bootstrap",)
|
|
832
|
+
).fetchone()["id"]
|
|
833
|
+
conn.execute(
|
|
834
|
+
"""
|
|
835
|
+
INSERT INTO memory_event (session_id, event_type, state, summary, payload_json, created_at)
|
|
836
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
837
|
+
""",
|
|
838
|
+
(
|
|
839
|
+
session_id,
|
|
840
|
+
"CHECKPOINT",
|
|
841
|
+
"ACTIVE",
|
|
842
|
+
"Initial memory checkpoint.",
|
|
843
|
+
json_dump({"source": "init"}),
|
|
844
|
+
now,
|
|
845
|
+
),
|
|
846
|
+
)
|
|
847
|
+
return {
|
|
848
|
+
"db": args.db,
|
|
849
|
+
"schema_version": SCHEMA_VERSION,
|
|
850
|
+
"schema_created": created,
|
|
851
|
+
"checkpoint_exists": True,
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
write_command(args, "init", handler, create_parent=True)
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def command_migrate(args: argparse.Namespace) -> None:
|
|
858
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
859
|
+
deduped_search_docs = dedupe_search_docs(conn)
|
|
860
|
+
conn.executescript(schema_sql())
|
|
861
|
+
fts_rebuilt = rebuild_fts(conn)
|
|
862
|
+
existing = conn.execute(
|
|
863
|
+
"SELECT id, checksum FROM schema_migration WHERE version = ?",
|
|
864
|
+
(SCHEMA_VERSION,),
|
|
865
|
+
).fetchone()
|
|
866
|
+
now = utc_now()
|
|
867
|
+
if existing:
|
|
868
|
+
updated = existing["checksum"] != schema_checksum()
|
|
869
|
+
conn.execute(
|
|
870
|
+
"""
|
|
871
|
+
UPDATE schema_migration
|
|
872
|
+
SET name = ?, checksum = ?, applied_at = ?
|
|
873
|
+
WHERE version = ?
|
|
874
|
+
""",
|
|
875
|
+
(MIGRATION_NAME, schema_checksum(), now, SCHEMA_VERSION),
|
|
876
|
+
)
|
|
877
|
+
else:
|
|
878
|
+
updated = True
|
|
879
|
+
conn.execute(
|
|
880
|
+
"""
|
|
881
|
+
INSERT INTO schema_migration (version, name, checksum, applied_at)
|
|
882
|
+
VALUES (?, ?, ?, ?)
|
|
883
|
+
""",
|
|
884
|
+
(SCHEMA_VERSION, MIGRATION_NAME, schema_checksum(), now),
|
|
885
|
+
)
|
|
886
|
+
return {
|
|
887
|
+
"db": args.db,
|
|
888
|
+
"schema_version": SCHEMA_VERSION,
|
|
889
|
+
"migration_updated": updated,
|
|
890
|
+
"deduped_search_docs": deduped_search_docs,
|
|
891
|
+
"fts_rebuilt": fts_rebuilt,
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
write_command(args, "migrate", handler, create_parent=True)
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
def doctor_checks(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
898
|
+
tables = {
|
|
899
|
+
row["name"]
|
|
900
|
+
for row in conn.execute(
|
|
901
|
+
"SELECT name FROM sqlite_master WHERE type IN ('table','view')"
|
|
902
|
+
).fetchall()
|
|
903
|
+
}
|
|
904
|
+
missing = sorted(REQUIRED_TABLES - tables)
|
|
905
|
+
migrations = [row_to_dict(row) for row in conn.execute("SELECT * FROM schema_migration").fetchall()]
|
|
906
|
+
fk_errors = [dict(row) for row in conn.execute("PRAGMA foreign_key_check").fetchall()]
|
|
907
|
+
integrity = conn.execute("PRAGMA integrity_check").fetchone()[0]
|
|
908
|
+
foreign_keys = conn.execute("PRAGMA foreign_keys").fetchone()[0]
|
|
909
|
+
journal_mode = conn.execute("PRAGMA journal_mode").fetchone()[0]
|
|
910
|
+
sensitive_indexed = conn.execute(
|
|
911
|
+
"SELECT COUNT(*) FROM search_doc WHERE sensitive = 1 AND indexable = 1"
|
|
912
|
+
).fetchone()[0]
|
|
913
|
+
orphan_rule_evidence = conn.execute(
|
|
914
|
+
"""
|
|
915
|
+
SELECT COUNT(*)
|
|
916
|
+
FROM rule_candidate_evidence rce
|
|
917
|
+
LEFT JOIN evidence e ON e.id = rce.evidence_id
|
|
918
|
+
WHERE e.id IS NULL
|
|
919
|
+
"""
|
|
920
|
+
).fetchone()[0]
|
|
921
|
+
stale_approved = conn.execute(
|
|
922
|
+
"""
|
|
923
|
+
SELECT COUNT(*)
|
|
924
|
+
FROM rule_patch
|
|
925
|
+
WHERE status = 'approved'
|
|
926
|
+
AND target_path = ?
|
|
927
|
+
AND base_hash != ?
|
|
928
|
+
""",
|
|
929
|
+
(DEFAULT_TARGET, file_hash(DEFAULT_TARGET)),
|
|
930
|
+
).fetchone()[0]
|
|
931
|
+
status = (
|
|
932
|
+
"ok"
|
|
933
|
+
if not missing
|
|
934
|
+
and not fk_errors
|
|
935
|
+
and integrity == "ok"
|
|
936
|
+
and foreign_keys == 1
|
|
937
|
+
and sensitive_indexed == 0
|
|
938
|
+
and orphan_rule_evidence == 0
|
|
939
|
+
and stale_approved == 0
|
|
940
|
+
else "error"
|
|
941
|
+
)
|
|
942
|
+
return {
|
|
943
|
+
"status": status,
|
|
944
|
+
"schema_version": SCHEMA_VERSION,
|
|
945
|
+
"missing_tables": missing,
|
|
946
|
+
"migrations": migrations,
|
|
947
|
+
"foreign_keys_enabled": bool(foreign_keys),
|
|
948
|
+
"journal_mode": journal_mode,
|
|
949
|
+
"integrity_check": integrity,
|
|
950
|
+
"foreign_key_errors": fk_errors,
|
|
951
|
+
"sensitive_indexable_violations": sensitive_indexed,
|
|
952
|
+
"orphan_rule_candidate_evidence": orphan_rule_evidence,
|
|
953
|
+
"stale_approved_patches": stale_approved,
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def command_doctor(args: argparse.Namespace) -> None:
|
|
958
|
+
try:
|
|
959
|
+
if not Path(args.db).exists():
|
|
960
|
+
raise CliError("DB_MISSING", f"database {args.db!r} does not exist")
|
|
961
|
+
conn = connect(args.db)
|
|
962
|
+
checks = doctor_checks(conn)
|
|
963
|
+
if args.record:
|
|
964
|
+
with conn:
|
|
965
|
+
audit_key = f"audit_{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}_{uuid.uuid4().hex[:8]}"
|
|
966
|
+
conn.execute(
|
|
967
|
+
"""
|
|
968
|
+
INSERT INTO audit_run (audit_key, operation, status, checks_json, created_at)
|
|
969
|
+
VALUES (?, ?, ?, ?, ?)
|
|
970
|
+
""",
|
|
971
|
+
(audit_key, "doctor", checks["status"], json_dump(checks), utc_now()),
|
|
972
|
+
)
|
|
973
|
+
receipt_key = write_receipt(conn, "doctor", {"audit_key": audit_key, "checks": checks})
|
|
974
|
+
conn.close()
|
|
975
|
+
emit({"ok": checks["status"] == "ok", "receipt_id": receipt_key, "result": checks})
|
|
976
|
+
conn.close()
|
|
977
|
+
emit({"ok": checks["status"] == "ok", "result": checks}, status=0 if checks["status"] == "ok" else 1)
|
|
978
|
+
except CliError as exc:
|
|
979
|
+
emit(
|
|
980
|
+
{
|
|
981
|
+
"ok": False,
|
|
982
|
+
"error_code": exc.error_code,
|
|
983
|
+
"message": exc.message,
|
|
984
|
+
"suggested_action": exc.suggested_action,
|
|
985
|
+
},
|
|
986
|
+
status=1,
|
|
987
|
+
)
|
|
988
|
+
except sqlite3.Error as exc:
|
|
989
|
+
emit({"ok": False, "error_code": "SQLITE_ERROR", "message": str(exc)}, status=1)
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def command_session_start(args: argparse.Namespace) -> None:
|
|
993
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
994
|
+
session_key = args.session_key or f"session_{uuid.uuid4().hex[:12]}"
|
|
995
|
+
now = utc_now()
|
|
996
|
+
conn.execute(
|
|
997
|
+
"""
|
|
998
|
+
INSERT INTO session (session_key, project, agent, started_at, metadata_json)
|
|
999
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1000
|
+
""",
|
|
1001
|
+
(session_key, args.project, args.agent, now, safe_metadata(args.metadata_json)),
|
|
1002
|
+
)
|
|
1003
|
+
session_id = conn.execute(
|
|
1004
|
+
"SELECT id FROM session WHERE session_key = ?", (session_key,)
|
|
1005
|
+
).fetchone()["id"]
|
|
1006
|
+
conn.execute(
|
|
1007
|
+
"""
|
|
1008
|
+
INSERT INTO memory_event (session_id, event_type, state, summary, payload_json, created_at)
|
|
1009
|
+
VALUES (?, 'CHECKPOINT', 'ACTIVE', ?, ?, ?)
|
|
1010
|
+
""",
|
|
1011
|
+
(session_id, "Session started.", json_dump({"session_key": session_key}), now),
|
|
1012
|
+
)
|
|
1013
|
+
return {"session_id": session_id, "session_key": session_key}
|
|
1014
|
+
|
|
1015
|
+
write_command(args, "session.start", handler)
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
def command_session_end(args: argparse.Namespace) -> None:
|
|
1019
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1020
|
+
session = require_session(conn, args.session_key)
|
|
1021
|
+
now = utc_now()
|
|
1022
|
+
conn.execute("UPDATE session SET ended_at = ? WHERE id = ?", (now, session["id"]))
|
|
1023
|
+
conn.execute(
|
|
1024
|
+
"""
|
|
1025
|
+
INSERT INTO memory_event (session_id, event_type, state, summary, payload_json, created_at)
|
|
1026
|
+
VALUES (?, 'CHECKPOINT', 'IDLE', ?, ?, ?)
|
|
1027
|
+
""",
|
|
1028
|
+
(session["id"], args.summary or "Session ended.", json_dump({}), now),
|
|
1029
|
+
)
|
|
1030
|
+
return {"session_id": session["id"], "session_key": args.session_key, "ended_at": now}
|
|
1031
|
+
|
|
1032
|
+
write_command(args, "session.end", handler)
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def command_task_start(args: argparse.Namespace) -> None:
|
|
1036
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1037
|
+
session = require_session(conn, args.session_key)
|
|
1038
|
+
task_key = args.task_key or f"task_{uuid.uuid4().hex[:12]}"
|
|
1039
|
+
now = utc_now()
|
|
1040
|
+
conn.execute(
|
|
1041
|
+
"""
|
|
1042
|
+
INSERT INTO task (
|
|
1043
|
+
task_key, session_id, project, agent, title, scope_text, status, started_at, metadata_json
|
|
1044
|
+
) VALUES (?, ?, ?, ?, ?, ?, 'active', ?, ?)
|
|
1045
|
+
""",
|
|
1046
|
+
(
|
|
1047
|
+
task_key,
|
|
1048
|
+
session["id"],
|
|
1049
|
+
args.project,
|
|
1050
|
+
args.agent,
|
|
1051
|
+
args.title,
|
|
1052
|
+
args.scope_text or "",
|
|
1053
|
+
now,
|
|
1054
|
+
safe_metadata(args.metadata_json),
|
|
1055
|
+
),
|
|
1056
|
+
)
|
|
1057
|
+
task_id = conn.execute("SELECT id FROM task WHERE task_key = ?", (task_key,)).fetchone()["id"]
|
|
1058
|
+
conn.execute(
|
|
1059
|
+
"""
|
|
1060
|
+
INSERT INTO memory_event (session_id, task_id, event_type, state, summary, payload_json, created_at)
|
|
1061
|
+
VALUES (?, ?, 'CHECKPOINT', 'ACTIVE', ?, ?, ?)
|
|
1062
|
+
""",
|
|
1063
|
+
(session["id"], task_id, f"Task started: {args.title}", json_dump({"task_key": task_key}), now),
|
|
1064
|
+
)
|
|
1065
|
+
return {"task_id": task_id, "task_key": task_key, "session_id": session["id"]}
|
|
1066
|
+
|
|
1067
|
+
write_command(args, "task.start", handler)
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def command_task_update(args: argparse.Namespace) -> None:
|
|
1071
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1072
|
+
task = get_by_key(conn, "task", "task_key", args.task_key)
|
|
1073
|
+
if not task:
|
|
1074
|
+
raise CliError("TASK_NOT_FOUND", f"task {args.task_key!r} does not exist")
|
|
1075
|
+
status = args.status or task["status"]
|
|
1076
|
+
conn.execute(
|
|
1077
|
+
"""
|
|
1078
|
+
UPDATE task
|
|
1079
|
+
SET title = COALESCE(?, title),
|
|
1080
|
+
scope_text = COALESCE(?, scope_text),
|
|
1081
|
+
status = ?,
|
|
1082
|
+
outcome = COALESCE(?, outcome),
|
|
1083
|
+
metadata_json = CASE WHEN ? IS NULL THEN metadata_json ELSE ? END
|
|
1084
|
+
WHERE id = ?
|
|
1085
|
+
""",
|
|
1086
|
+
(
|
|
1087
|
+
args.title,
|
|
1088
|
+
args.scope_text,
|
|
1089
|
+
status,
|
|
1090
|
+
args.outcome,
|
|
1091
|
+
args.metadata_json,
|
|
1092
|
+
safe_metadata(args.metadata_json) if args.metadata_json else None,
|
|
1093
|
+
task["id"],
|
|
1094
|
+
),
|
|
1095
|
+
)
|
|
1096
|
+
return {"task_id": task["id"], "task_key": args.task_key, "status": status}
|
|
1097
|
+
|
|
1098
|
+
write_command(args, "task.update", handler)
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
def command_task_complete(args: argparse.Namespace) -> None:
|
|
1102
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1103
|
+
task = get_by_key(conn, "task", "task_key", args.task_key)
|
|
1104
|
+
if not task:
|
|
1105
|
+
raise CliError("TASK_NOT_FOUND", f"task {args.task_key!r} does not exist")
|
|
1106
|
+
now = utc_now()
|
|
1107
|
+
conn.execute(
|
|
1108
|
+
"""
|
|
1109
|
+
UPDATE task
|
|
1110
|
+
SET status = 'completed', completed_at = ?, outcome = ?
|
|
1111
|
+
WHERE id = ?
|
|
1112
|
+
""",
|
|
1113
|
+
(now, args.outcome or task["outcome"], task["id"]),
|
|
1114
|
+
)
|
|
1115
|
+
conn.execute(
|
|
1116
|
+
"""
|
|
1117
|
+
INSERT INTO memory_event (session_id, task_id, event_type, state, summary, payload_json, created_at)
|
|
1118
|
+
VALUES (?, ?, 'COMPLETE', 'IDLE', ?, ?, ?)
|
|
1119
|
+
""",
|
|
1120
|
+
(task["session_id"], task["id"], args.summary or "Task completed.", json_dump({}), now),
|
|
1121
|
+
)
|
|
1122
|
+
return {"task_id": task["id"], "task_key": args.task_key, "completed_at": now}
|
|
1123
|
+
|
|
1124
|
+
write_command(args, "task.complete", handler)
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
def command_event_add(args: argparse.Namespace) -> None:
|
|
1128
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1129
|
+
session = require_session(conn, args.session_key) if args.session_key else None
|
|
1130
|
+
task = optional_task(conn, args.task_key)
|
|
1131
|
+
if args.task_key and not task:
|
|
1132
|
+
raise CliError("TASK_NOT_FOUND", f"task {args.task_key!r} does not exist")
|
|
1133
|
+
cursor = conn.execute(
|
|
1134
|
+
"""
|
|
1135
|
+
INSERT INTO memory_event (session_id, task_id, event_type, state, summary, payload_json, created_at)
|
|
1136
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1137
|
+
""",
|
|
1138
|
+
(
|
|
1139
|
+
session["id"] if session else None,
|
|
1140
|
+
task["id"] if task else None,
|
|
1141
|
+
args.event_type,
|
|
1142
|
+
args.state,
|
|
1143
|
+
args.summary or "",
|
|
1144
|
+
safe_metadata(args.payload_json),
|
|
1145
|
+
utc_now(),
|
|
1146
|
+
),
|
|
1147
|
+
)
|
|
1148
|
+
return {"event_id": int(cursor.lastrowid), "event_type": args.event_type, "state": args.state}
|
|
1149
|
+
|
|
1150
|
+
write_command(args, "event.add", handler)
|
|
1151
|
+
|
|
1152
|
+
|
|
1153
|
+
def command_fact_add(args: argparse.Namespace) -> None:
|
|
1154
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1155
|
+
session = require_session(conn, args.session_key) if args.session_key else None
|
|
1156
|
+
sensitive, indexable = sensitive_indexable(args.sensitive, args.indexable)
|
|
1157
|
+
fact_key = args.fact_key or normalize_rule_key(args.fact_text)
|
|
1158
|
+
now = utc_now()
|
|
1159
|
+
conn.execute(
|
|
1160
|
+
"""
|
|
1161
|
+
INSERT INTO memory_fact (
|
|
1162
|
+
session_id, project, agent, fact_key, fact_text, category, confidence, status,
|
|
1163
|
+
sensitive, indexable, first_seen_at, last_seen_at, metadata_json
|
|
1164
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?)
|
|
1165
|
+
ON CONFLICT(project, agent, fact_key) DO UPDATE SET
|
|
1166
|
+
fact_text = excluded.fact_text,
|
|
1167
|
+
category = excluded.category,
|
|
1168
|
+
confidence = excluded.confidence,
|
|
1169
|
+
sensitive = excluded.sensitive,
|
|
1170
|
+
indexable = excluded.indexable,
|
|
1171
|
+
last_seen_at = excluded.last_seen_at,
|
|
1172
|
+
metadata_json = excluded.metadata_json
|
|
1173
|
+
""",
|
|
1174
|
+
(
|
|
1175
|
+
session["id"] if session else None,
|
|
1176
|
+
args.project,
|
|
1177
|
+
args.agent,
|
|
1178
|
+
fact_key,
|
|
1179
|
+
args.fact_text,
|
|
1180
|
+
args.category,
|
|
1181
|
+
args.confidence,
|
|
1182
|
+
sensitive,
|
|
1183
|
+
indexable,
|
|
1184
|
+
now,
|
|
1185
|
+
now,
|
|
1186
|
+
safe_metadata(args.metadata_json),
|
|
1187
|
+
),
|
|
1188
|
+
)
|
|
1189
|
+
fact = conn.execute(
|
|
1190
|
+
"SELECT * FROM memory_fact WHERE project = ? AND agent = ? AND fact_key = ?",
|
|
1191
|
+
(args.project, args.agent, fact_key),
|
|
1192
|
+
).fetchone()
|
|
1193
|
+
index_search_doc(
|
|
1194
|
+
conn,
|
|
1195
|
+
args.project,
|
|
1196
|
+
args.agent,
|
|
1197
|
+
"memory_fact",
|
|
1198
|
+
"memory_fact",
|
|
1199
|
+
fact["id"],
|
|
1200
|
+
fact_key,
|
|
1201
|
+
args.fact_text,
|
|
1202
|
+
args.fact_text[:240],
|
|
1203
|
+
sensitive,
|
|
1204
|
+
indexable,
|
|
1205
|
+
)
|
|
1206
|
+
return {"fact_id": fact["id"], "fact_key": fact_key}
|
|
1207
|
+
|
|
1208
|
+
write_command(args, "fact.add", handler)
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
def command_claim_add(args: argparse.Namespace) -> None:
|
|
1212
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1213
|
+
session = require_session(conn, args.session_key) if args.session_key else None
|
|
1214
|
+
task = optional_task(conn, args.task_key)
|
|
1215
|
+
if args.task_key and not task:
|
|
1216
|
+
raise CliError("TASK_NOT_FOUND", f"task {args.task_key!r} does not exist")
|
|
1217
|
+
sensitive, indexable = sensitive_indexable(args.sensitive, args.indexable)
|
|
1218
|
+
claim_key = args.claim_key or normalize_rule_key(args.claim_text)
|
|
1219
|
+
now = utc_now()
|
|
1220
|
+
conn.execute(
|
|
1221
|
+
"""
|
|
1222
|
+
INSERT INTO claim (
|
|
1223
|
+
session_id, task_id, project, agent, claim_key, claim_text, verification_status,
|
|
1224
|
+
evidence_summary, sensitive, indexable, created_at, updated_at, metadata_json
|
|
1225
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1226
|
+
ON CONFLICT(project, agent, claim_key) DO UPDATE SET
|
|
1227
|
+
claim_text = excluded.claim_text,
|
|
1228
|
+
verification_status = excluded.verification_status,
|
|
1229
|
+
evidence_summary = excluded.evidence_summary,
|
|
1230
|
+
sensitive = excluded.sensitive,
|
|
1231
|
+
indexable = excluded.indexable,
|
|
1232
|
+
updated_at = excluded.updated_at,
|
|
1233
|
+
metadata_json = excluded.metadata_json
|
|
1234
|
+
""",
|
|
1235
|
+
(
|
|
1236
|
+
session["id"] if session else None,
|
|
1237
|
+
task["id"] if task else None,
|
|
1238
|
+
args.project,
|
|
1239
|
+
args.agent,
|
|
1240
|
+
claim_key,
|
|
1241
|
+
args.claim_text,
|
|
1242
|
+
args.verification_status,
|
|
1243
|
+
args.evidence_summary or "",
|
|
1244
|
+
sensitive,
|
|
1245
|
+
indexable,
|
|
1246
|
+
now,
|
|
1247
|
+
now,
|
|
1248
|
+
safe_metadata(args.metadata_json),
|
|
1249
|
+
),
|
|
1250
|
+
)
|
|
1251
|
+
claim = conn.execute(
|
|
1252
|
+
"SELECT * FROM claim WHERE project = ? AND agent = ? AND claim_key = ?",
|
|
1253
|
+
(args.project, args.agent, claim_key),
|
|
1254
|
+
).fetchone()
|
|
1255
|
+
index_search_doc(
|
|
1256
|
+
conn,
|
|
1257
|
+
args.project,
|
|
1258
|
+
args.agent,
|
|
1259
|
+
"claim",
|
|
1260
|
+
"claim",
|
|
1261
|
+
claim["id"],
|
|
1262
|
+
claim_key,
|
|
1263
|
+
args.claim_text,
|
|
1264
|
+
args.evidence_summary or args.claim_text[:240],
|
|
1265
|
+
sensitive,
|
|
1266
|
+
indexable,
|
|
1267
|
+
)
|
|
1268
|
+
return {"claim_id": claim["id"], "claim_key": claim_key}
|
|
1269
|
+
|
|
1270
|
+
write_command(args, "claim.add", handler)
|
|
1271
|
+
|
|
1272
|
+
|
|
1273
|
+
def command_finding_add(args: argparse.Namespace) -> None:
|
|
1274
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1275
|
+
session = require_session(conn, args.session_key)
|
|
1276
|
+
task = optional_task(conn, args.task_key)
|
|
1277
|
+
if args.task_key and not task:
|
|
1278
|
+
raise CliError("TASK_NOT_FOUND", f"task {args.task_key!r} does not exist")
|
|
1279
|
+
sensitive, indexable = sensitive_indexable(args.sensitive, args.indexable)
|
|
1280
|
+
now = utc_now()
|
|
1281
|
+
cursor = conn.execute(
|
|
1282
|
+
"""
|
|
1283
|
+
INSERT INTO finding (
|
|
1284
|
+
session_id, task_id, project, agent, kind, category, severity, wrong_behavior,
|
|
1285
|
+
correct_behavior, summary, raw_ref, sensitive, indexable, created_at, metadata_json
|
|
1286
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1287
|
+
""",
|
|
1288
|
+
(
|
|
1289
|
+
session["id"],
|
|
1290
|
+
task["id"] if task else None,
|
|
1291
|
+
args.project,
|
|
1292
|
+
args.agent,
|
|
1293
|
+
args.kind,
|
|
1294
|
+
args.category,
|
|
1295
|
+
args.severity,
|
|
1296
|
+
args.wrong_behavior or "",
|
|
1297
|
+
args.correct_behavior or "",
|
|
1298
|
+
args.summary,
|
|
1299
|
+
args.raw_ref or "",
|
|
1300
|
+
sensitive,
|
|
1301
|
+
indexable,
|
|
1302
|
+
now,
|
|
1303
|
+
safe_metadata(args.metadata_json),
|
|
1304
|
+
),
|
|
1305
|
+
)
|
|
1306
|
+
finding_id = int(cursor.lastrowid)
|
|
1307
|
+
evidence_text = "\n".join(
|
|
1308
|
+
part
|
|
1309
|
+
for part in [
|
|
1310
|
+
args.summary,
|
|
1311
|
+
f"Wrong behavior: {args.wrong_behavior}" if args.wrong_behavior else "",
|
|
1312
|
+
f"Correct behavior: {args.correct_behavior}" if args.correct_behavior else "",
|
|
1313
|
+
f"Raw ref: {args.raw_ref}" if args.raw_ref else "",
|
|
1314
|
+
]
|
|
1315
|
+
if part
|
|
1316
|
+
)
|
|
1317
|
+
evidence_key = args.evidence_key or f"finding:{finding_id}:{sha256_text(evidence_text)[:12]}"
|
|
1318
|
+
evidence_id = add_evidence_row(
|
|
1319
|
+
conn,
|
|
1320
|
+
session["id"],
|
|
1321
|
+
task["id"] if task else None,
|
|
1322
|
+
args.project,
|
|
1323
|
+
args.agent,
|
|
1324
|
+
"finding",
|
|
1325
|
+
finding_id,
|
|
1326
|
+
evidence_key,
|
|
1327
|
+
evidence_text,
|
|
1328
|
+
1,
|
|
1329
|
+
sensitive,
|
|
1330
|
+
indexable,
|
|
1331
|
+
json_dump({"finding_id": finding_id}),
|
|
1332
|
+
)
|
|
1333
|
+
index_search_doc(
|
|
1334
|
+
conn,
|
|
1335
|
+
args.project,
|
|
1336
|
+
args.agent,
|
|
1337
|
+
"finding",
|
|
1338
|
+
"finding",
|
|
1339
|
+
finding_id,
|
|
1340
|
+
args.category,
|
|
1341
|
+
evidence_text,
|
|
1342
|
+
args.summary,
|
|
1343
|
+
sensitive,
|
|
1344
|
+
indexable,
|
|
1345
|
+
)
|
|
1346
|
+
return {"finding_id": finding_id, "evidence_id": evidence_id}
|
|
1347
|
+
|
|
1348
|
+
write_command(args, "finding.add", handler)
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
def command_evidence_add(args: argparse.Namespace) -> None:
|
|
1352
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1353
|
+
session = require_session(conn, args.session_key) if args.session_key else None
|
|
1354
|
+
task = optional_task(conn, args.task_key)
|
|
1355
|
+
if args.task_key and not task:
|
|
1356
|
+
raise CliError("TASK_NOT_FOUND", f"task {args.task_key!r} does not exist")
|
|
1357
|
+
sensitive, indexable = sensitive_indexable(args.sensitive, args.indexable)
|
|
1358
|
+
evidence_key = args.evidence_key or f"{args.source_type}:{sha256_text(args.evidence_text)[:16]}"
|
|
1359
|
+
evidence_id = add_evidence_row(
|
|
1360
|
+
conn,
|
|
1361
|
+
session["id"] if session else None,
|
|
1362
|
+
task["id"] if task else None,
|
|
1363
|
+
args.project,
|
|
1364
|
+
args.agent,
|
|
1365
|
+
args.source_type,
|
|
1366
|
+
args.source_id,
|
|
1367
|
+
evidence_key,
|
|
1368
|
+
args.evidence_text,
|
|
1369
|
+
args.weight,
|
|
1370
|
+
sensitive,
|
|
1371
|
+
indexable,
|
|
1372
|
+
safe_metadata(args.metadata_json),
|
|
1373
|
+
)
|
|
1374
|
+
return {"evidence_id": evidence_id, "evidence_key": evidence_key}
|
|
1375
|
+
|
|
1376
|
+
write_command(args, "evidence.add", handler)
|
|
1377
|
+
|
|
1378
|
+
|
|
1379
|
+
def recompute_rule(conn: sqlite3.Connection, rule_id: int) -> dict[str, Any]:
|
|
1380
|
+
rule = require_by_id(conn, "rule_candidate", rule_id)
|
|
1381
|
+
summary = conn.execute(
|
|
1382
|
+
"""
|
|
1383
|
+
SELECT
|
|
1384
|
+
COALESCE(SUM(rce.weight), 0) AS score,
|
|
1385
|
+
COUNT(rce.id) AS evidence_count,
|
|
1386
|
+
COUNT(rce.id) AS mention_count,
|
|
1387
|
+
COUNT(DISTINCT e.session_id) AS session_count
|
|
1388
|
+
FROM rule_candidate_evidence rce
|
|
1389
|
+
JOIN evidence e ON e.id = rce.evidence_id
|
|
1390
|
+
WHERE rce.rule_id = ?
|
|
1391
|
+
""",
|
|
1392
|
+
(rule_id,),
|
|
1393
|
+
).fetchone()
|
|
1394
|
+
score = int(summary["score"] or 0)
|
|
1395
|
+
evidence_count = int(summary["evidence_count"] or 0)
|
|
1396
|
+
mention_count = int(summary["mention_count"] or 0)
|
|
1397
|
+
session_count = int(summary["session_count"] or 0)
|
|
1398
|
+
current_status = rule["status"]
|
|
1399
|
+
status = current_status
|
|
1400
|
+
if current_status not in {"approved", "promoted", "rejected", "superseded", "expired"}:
|
|
1401
|
+
if rule["conflicts_json"] and rule["conflicts_json"] != "[]":
|
|
1402
|
+
status = "needs_reconciliation"
|
|
1403
|
+
elif score >= 5 and session_count >= 2 and evidence_count >= 1:
|
|
1404
|
+
status = "candidate"
|
|
1405
|
+
elif rule["severity"] in {"high", "critical"}:
|
|
1406
|
+
status = "candidate"
|
|
1407
|
+
else:
|
|
1408
|
+
status = "memory"
|
|
1409
|
+
conn.execute(
|
|
1410
|
+
"""
|
|
1411
|
+
UPDATE rule_candidate
|
|
1412
|
+
SET score = ?, mention_count = ?, session_count = ?, evidence_count = ?,
|
|
1413
|
+
status = ?, last_seen_at = ?
|
|
1414
|
+
WHERE id = ?
|
|
1415
|
+
""",
|
|
1416
|
+
(score, mention_count, session_count, evidence_count, status, utc_now(), rule_id),
|
|
1417
|
+
)
|
|
1418
|
+
return {
|
|
1419
|
+
"score": score,
|
|
1420
|
+
"mention_count": mention_count,
|
|
1421
|
+
"session_count": session_count,
|
|
1422
|
+
"evidence_count": evidence_count,
|
|
1423
|
+
"status": status,
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
|
|
1427
|
+
def command_rule_observe(args: argparse.Namespace) -> None:
|
|
1428
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1429
|
+
evidence = require_by_id(conn, "evidence", args.evidence_id)
|
|
1430
|
+
rule_key = args.rule_key or normalize_rule_key(args.rule_text)
|
|
1431
|
+
normalized_hash = sha256_text(normalize_rule_text(args.rule_text))
|
|
1432
|
+
existing = conn.execute(
|
|
1433
|
+
"SELECT * FROM rule_candidate WHERE project = ? AND agent = ? AND rule_key = ?",
|
|
1434
|
+
(args.project, args.agent, rule_key),
|
|
1435
|
+
).fetchone()
|
|
1436
|
+
status_before = existing["status"] if existing else None
|
|
1437
|
+
score_before = existing["score"] if existing else 0
|
|
1438
|
+
now = utc_now()
|
|
1439
|
+
severity = args.severity
|
|
1440
|
+
if existing and SEVERITY_RANK[existing["severity"]] > SEVERITY_RANK[severity]:
|
|
1441
|
+
severity = existing["severity"]
|
|
1442
|
+
if existing:
|
|
1443
|
+
conn.execute(
|
|
1444
|
+
"""
|
|
1445
|
+
UPDATE rule_candidate
|
|
1446
|
+
SET normalized_rule_hash = ?, rule_text = ?, category = ?, severity = ?, last_seen_at = ?
|
|
1447
|
+
WHERE id = ?
|
|
1448
|
+
""",
|
|
1449
|
+
(normalized_hash, args.rule_text, args.category, severity, now, existing["id"]),
|
|
1450
|
+
)
|
|
1451
|
+
rule_id = existing["id"]
|
|
1452
|
+
created = False
|
|
1453
|
+
else:
|
|
1454
|
+
cursor = conn.execute(
|
|
1455
|
+
"""
|
|
1456
|
+
INSERT INTO rule_candidate (
|
|
1457
|
+
project, agent, rule_key, normalized_rule_hash, rule_text, category, severity,
|
|
1458
|
+
status, promotion_target, target_scope, first_seen_at, last_seen_at, metadata_json
|
|
1459
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 'memory', 'AGENTS.md', 'project_root_agents', ?, ?, ?)
|
|
1460
|
+
""",
|
|
1461
|
+
(
|
|
1462
|
+
args.project,
|
|
1463
|
+
args.agent,
|
|
1464
|
+
rule_key,
|
|
1465
|
+
normalized_hash,
|
|
1466
|
+
args.rule_text,
|
|
1467
|
+
args.category,
|
|
1468
|
+
severity,
|
|
1469
|
+
now,
|
|
1470
|
+
now,
|
|
1471
|
+
safe_metadata(args.metadata_json),
|
|
1472
|
+
),
|
|
1473
|
+
)
|
|
1474
|
+
rule_id = int(cursor.lastrowid)
|
|
1475
|
+
created = True
|
|
1476
|
+
weight = SIGNAL_WEIGHTS[args.signal_type]
|
|
1477
|
+
conn.execute(
|
|
1478
|
+
"""
|
|
1479
|
+
INSERT OR IGNORE INTO rule_candidate_evidence (rule_id, evidence_id, signal_type, weight, created_at)
|
|
1480
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1481
|
+
""",
|
|
1482
|
+
(rule_id, evidence["id"], args.signal_type, weight, now),
|
|
1483
|
+
)
|
|
1484
|
+
summary = recompute_rule(conn, rule_id)
|
|
1485
|
+
return {
|
|
1486
|
+
"created": created,
|
|
1487
|
+
"rule_id": rule_id,
|
|
1488
|
+
"rule_key": rule_key,
|
|
1489
|
+
"status_before": status_before,
|
|
1490
|
+
"status_after": summary["status"],
|
|
1491
|
+
"score_before": score_before,
|
|
1492
|
+
"score_after": summary["score"],
|
|
1493
|
+
"session_count": summary["session_count"],
|
|
1494
|
+
"evidence_count": summary["evidence_count"],
|
|
1495
|
+
"requires_user_approval": True,
|
|
1496
|
+
"suggest_project_agents_promotion": summary["score"] >= 7 or severity in {"high", "critical"},
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
write_command(args, "rule.observe", handler)
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
def command_rule_list(args: argparse.Namespace) -> None:
|
|
1503
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1504
|
+
values: list[Any] = [args.project, args.agent]
|
|
1505
|
+
where = "WHERE project = ? AND agent = ?"
|
|
1506
|
+
if args.status:
|
|
1507
|
+
where += " AND status = ?"
|
|
1508
|
+
values.append(args.status)
|
|
1509
|
+
rows = conn.execute(
|
|
1510
|
+
f"SELECT * FROM rule_candidate {where} ORDER BY last_seen_at DESC LIMIT ?",
|
|
1511
|
+
(*values, args.limit),
|
|
1512
|
+
).fetchall()
|
|
1513
|
+
return {"rules": [row_to_dict(row) for row in rows]}
|
|
1514
|
+
|
|
1515
|
+
read_command(args, handler)
|
|
1516
|
+
|
|
1517
|
+
|
|
1518
|
+
def build_agents_content(base_content: str, rule_text: str) -> str:
|
|
1519
|
+
rule_line = f"- {rule_text.strip()}"
|
|
1520
|
+
if rule_line in base_content:
|
|
1521
|
+
raise CliError("RULE_ALREADY_PRESENT", "target AGENTS.md already contains this rule")
|
|
1522
|
+
if not base_content.strip():
|
|
1523
|
+
return f"# Agent Instructions\n\n## Rule Promotion\n\n{rule_line}\n"
|
|
1524
|
+
separator = "" if base_content.endswith("\n") else "\n"
|
|
1525
|
+
return f"{base_content}{separator}\n## Rule Promotion\n\n{rule_line}\n"
|
|
1526
|
+
|
|
1527
|
+
|
|
1528
|
+
def unified_patch(old: str, new: str, target: str) -> str:
|
|
1529
|
+
return "".join(
|
|
1530
|
+
difflib.unified_diff(
|
|
1531
|
+
old.splitlines(keepends=True),
|
|
1532
|
+
new.splitlines(keepends=True),
|
|
1533
|
+
fromfile=f"a/{target}",
|
|
1534
|
+
tofile=f"b/{target}",
|
|
1535
|
+
)
|
|
1536
|
+
)
|
|
1537
|
+
|
|
1538
|
+
|
|
1539
|
+
def command_rule_propose(args: argparse.Namespace) -> None:
|
|
1540
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1541
|
+
if args.target != DEFAULT_TARGET:
|
|
1542
|
+
raise CliError("UNSUPPORTED_TARGET", "v1 rule promotion only targets repo root AGENTS.md")
|
|
1543
|
+
rule = require_by_id(conn, "rule_candidate", args.rule_id)
|
|
1544
|
+
if rule["status"] not in {"candidate", "memory", "needs_reconciliation"}:
|
|
1545
|
+
raise CliError("INVALID_RULE_STATUS", f"cannot propose patch from status {rule['status']!r}")
|
|
1546
|
+
if rule["status"] == "needs_reconciliation":
|
|
1547
|
+
raise CliError("NEEDS_RECONCILIATION", "resolve rule conflicts before proposing a patch")
|
|
1548
|
+
base_content = read_text_if_exists(args.target)
|
|
1549
|
+
proposed_content = build_agents_content(base_content, rule["rule_text"])
|
|
1550
|
+
base_hash = sha256_text(base_content)
|
|
1551
|
+
new_hash = sha256_text(proposed_content)
|
|
1552
|
+
patch_text = unified_patch(base_content, proposed_content, args.target)
|
|
1553
|
+
patch_hash = sha256_text(patch_text)
|
|
1554
|
+
now = utc_now()
|
|
1555
|
+
snapshot_cursor = conn.execute(
|
|
1556
|
+
"""
|
|
1557
|
+
INSERT INTO instruction_snapshot (
|
|
1558
|
+
target_path, target_scope, content_hash, summary, captured_at, metadata_json
|
|
1559
|
+
) VALUES (?, 'project_root_agents', ?, ?, ?, ?)
|
|
1560
|
+
""",
|
|
1561
|
+
(
|
|
1562
|
+
args.target,
|
|
1563
|
+
base_hash,
|
|
1564
|
+
f"Snapshot before proposing rule {rule['rule_key']}",
|
|
1565
|
+
now,
|
|
1566
|
+
json_dump({"target_exists": Path(args.target).exists()}),
|
|
1567
|
+
),
|
|
1568
|
+
)
|
|
1569
|
+
patch_cursor = conn.execute(
|
|
1570
|
+
"""
|
|
1571
|
+
INSERT INTO rule_patch (
|
|
1572
|
+
rule_id, target_path, target_scope, base_snapshot_id, base_hash, patch_hash,
|
|
1573
|
+
proposed_rule_hash, patch_text, status, created_at, updated_at, metadata_json
|
|
1574
|
+
) VALUES (?, ?, 'project_root_agents', ?, ?, ?, ?, ?, 'proposed', ?, ?, ?)
|
|
1575
|
+
""",
|
|
1576
|
+
(
|
|
1577
|
+
rule["id"],
|
|
1578
|
+
args.target,
|
|
1579
|
+
int(snapshot_cursor.lastrowid),
|
|
1580
|
+
base_hash,
|
|
1581
|
+
patch_hash,
|
|
1582
|
+
rule["normalized_rule_hash"],
|
|
1583
|
+
patch_text,
|
|
1584
|
+
now,
|
|
1585
|
+
now,
|
|
1586
|
+
json_dump({"proposed_content_hash": new_hash, "proposed_content": proposed_content}),
|
|
1587
|
+
),
|
|
1588
|
+
)
|
|
1589
|
+
return {
|
|
1590
|
+
"rule_id": rule["id"],
|
|
1591
|
+
"patch_id": int(patch_cursor.lastrowid),
|
|
1592
|
+
"target_path": args.target,
|
|
1593
|
+
"base_hash": base_hash,
|
|
1594
|
+
"patch_hash": patch_hash,
|
|
1595
|
+
"proposed_content_hash": new_hash,
|
|
1596
|
+
"patch_preview": patch_text,
|
|
1597
|
+
"requires_user_approval": True,
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
write_command(args, "rule.propose", handler)
|
|
1601
|
+
|
|
1602
|
+
|
|
1603
|
+
def command_rule_approve(args: argparse.Namespace) -> None:
|
|
1604
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1605
|
+
patch = require_by_id(conn, "rule_patch", args.patch_id)
|
|
1606
|
+
rule = require_by_id(conn, "rule_candidate", patch["rule_id"])
|
|
1607
|
+
if patch["status"] != "proposed":
|
|
1608
|
+
raise CliError("INVALID_PATCH_STATUS", f"patch status is {patch['status']!r}, expected 'proposed'")
|
|
1609
|
+
if args.patch_hash != patch["patch_hash"]:
|
|
1610
|
+
raise CliError("PATCH_HASH_MISMATCH", "approved patch hash does not match the proposed patch")
|
|
1611
|
+
if args.approval_evidence_id:
|
|
1612
|
+
evidence = require_by_id(conn, "evidence", args.approval_evidence_id)
|
|
1613
|
+
if evidence["source_type"] != "user_confirmation":
|
|
1614
|
+
raise CliError("INVALID_APPROVAL_EVIDENCE", "approval evidence must have source_type='user_confirmation'")
|
|
1615
|
+
now = utc_now()
|
|
1616
|
+
decision_cursor = conn.execute(
|
|
1617
|
+
"""
|
|
1618
|
+
INSERT INTO rule_decision (
|
|
1619
|
+
rule_id, patch_id, decision, approved_rule_hash, approved_patch_hash,
|
|
1620
|
+
actor, reason, decided_at, metadata_json
|
|
1621
|
+
) VALUES (?, ?, 'approved', ?, ?, ?, ?, ?, ?)
|
|
1622
|
+
""",
|
|
1623
|
+
(
|
|
1624
|
+
rule["id"],
|
|
1625
|
+
patch["id"],
|
|
1626
|
+
rule["normalized_rule_hash"],
|
|
1627
|
+
patch["patch_hash"],
|
|
1628
|
+
args.actor,
|
|
1629
|
+
args.reason or "",
|
|
1630
|
+
now,
|
|
1631
|
+
json_dump({"approval_evidence_id": args.approval_evidence_id}),
|
|
1632
|
+
),
|
|
1633
|
+
)
|
|
1634
|
+
conn.execute("UPDATE rule_patch SET status = 'approved', updated_at = ? WHERE id = ?", (now, patch["id"]))
|
|
1635
|
+
conn.execute("UPDATE rule_candidate SET status = 'approved', approved_at = ? WHERE id = ?", (now, rule["id"]))
|
|
1636
|
+
return {"decision_id": int(decision_cursor.lastrowid), "rule_id": rule["id"], "patch_id": patch["id"]}
|
|
1637
|
+
|
|
1638
|
+
write_command(args, "rule.approve", handler)
|
|
1639
|
+
|
|
1640
|
+
|
|
1641
|
+
def command_rule_reject(args: argparse.Namespace) -> None:
|
|
1642
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1643
|
+
patch = require_by_id(conn, "rule_patch", args.patch_id)
|
|
1644
|
+
rule = require_by_id(conn, "rule_candidate", patch["rule_id"])
|
|
1645
|
+
now = utc_now()
|
|
1646
|
+
decision_cursor = conn.execute(
|
|
1647
|
+
"""
|
|
1648
|
+
INSERT INTO rule_decision (rule_id, patch_id, decision, actor, reason, decided_at, metadata_json)
|
|
1649
|
+
VALUES (?, ?, 'rejected', ?, ?, ?, ?)
|
|
1650
|
+
""",
|
|
1651
|
+
(rule["id"], patch["id"], args.actor, args.reason or "", now, "{}"),
|
|
1652
|
+
)
|
|
1653
|
+
conn.execute("UPDATE rule_patch SET status = 'rejected', updated_at = ? WHERE id = ?", (now, patch["id"]))
|
|
1654
|
+
conn.execute("UPDATE rule_candidate SET status = 'rejected' WHERE id = ?", (rule["id"],))
|
|
1655
|
+
return {"decision_id": int(decision_cursor.lastrowid), "rule_id": rule["id"], "patch_id": patch["id"]}
|
|
1656
|
+
|
|
1657
|
+
write_command(args, "rule.reject", handler)
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
def command_rule_reconcile(args: argparse.Namespace) -> None:
|
|
1661
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1662
|
+
rule = require_by_id(conn, "rule_candidate", args.rule_id)
|
|
1663
|
+
now = utc_now()
|
|
1664
|
+
conn.execute(
|
|
1665
|
+
"""
|
|
1666
|
+
UPDATE rule_candidate
|
|
1667
|
+
SET status = ?, conflicts_json = ?, last_seen_at = ?
|
|
1668
|
+
WHERE id = ?
|
|
1669
|
+
""",
|
|
1670
|
+
(args.status, args.conflicts_json or "[]", now, rule["id"]),
|
|
1671
|
+
)
|
|
1672
|
+
return {"rule_id": rule["id"], "status_before": rule["status"], "status_after": args.status}
|
|
1673
|
+
|
|
1674
|
+
write_command(args, "rule.reconcile", handler)
|
|
1675
|
+
|
|
1676
|
+
|
|
1677
|
+
def latest_approved_decision(conn: sqlite3.Connection, patch_id: int) -> sqlite3.Row:
|
|
1678
|
+
decision = conn.execute(
|
|
1679
|
+
"""
|
|
1680
|
+
SELECT * FROM rule_decision
|
|
1681
|
+
WHERE patch_id = ? AND decision = 'approved'
|
|
1682
|
+
ORDER BY decided_at DESC, id DESC
|
|
1683
|
+
LIMIT 1
|
|
1684
|
+
""",
|
|
1685
|
+
(patch_id,),
|
|
1686
|
+
).fetchone()
|
|
1687
|
+
if not decision:
|
|
1688
|
+
raise CliError("APPROVAL_MISSING", "patch has no approved user decision")
|
|
1689
|
+
return decision
|
|
1690
|
+
|
|
1691
|
+
|
|
1692
|
+
def proposed_content_from_patch(patch: sqlite3.Row) -> str:
|
|
1693
|
+
metadata = json.loads(patch["metadata_json"] or "{}")
|
|
1694
|
+
proposed_content = metadata.get("proposed_content")
|
|
1695
|
+
if not isinstance(proposed_content, str):
|
|
1696
|
+
raise CliError("PATCH_CONTENT_MISSING", "rule_patch metadata is missing proposed_content")
|
|
1697
|
+
return proposed_content
|
|
1698
|
+
|
|
1699
|
+
|
|
1700
|
+
def validate_promotion(conn: sqlite3.Connection, patch_id: int) -> tuple[sqlite3.Row, sqlite3.Row, sqlite3.Row, str]:
|
|
1701
|
+
patch = require_by_id(conn, "rule_patch", patch_id)
|
|
1702
|
+
rule = require_by_id(conn, "rule_candidate", patch["rule_id"])
|
|
1703
|
+
decision = latest_approved_decision(conn, patch_id)
|
|
1704
|
+
proposed_content = proposed_content_from_patch(patch)
|
|
1705
|
+
if rule["status"] != "approved":
|
|
1706
|
+
raise CliError("INVALID_RULE_STATUS", f"rule status is {rule['status']!r}, expected 'approved'")
|
|
1707
|
+
if patch["status"] != "approved":
|
|
1708
|
+
raise CliError("INVALID_PATCH_STATUS", f"patch status is {patch['status']!r}, expected 'approved'")
|
|
1709
|
+
if decision["patch_id"] != patch["id"]:
|
|
1710
|
+
raise CliError("APPROVAL_PATCH_MISMATCH", "approval decision does not match patch")
|
|
1711
|
+
if decision["approved_patch_hash"] != patch["patch_hash"]:
|
|
1712
|
+
raise CliError("APPROVAL_PATCH_HASH_MISMATCH", "approved patch hash does not match current patch hash")
|
|
1713
|
+
if decision["approved_rule_hash"] != rule["normalized_rule_hash"]:
|
|
1714
|
+
raise CliError("APPROVAL_RULE_HASH_MISMATCH", "approved rule hash does not match current rule hash")
|
|
1715
|
+
if patch["target_path"] != DEFAULT_TARGET or patch["target_scope"] != "project_root_agents":
|
|
1716
|
+
raise CliError("UNSUPPORTED_TARGET", "v1 promotion only supports repo root AGENTS.md")
|
|
1717
|
+
return patch, rule, decision, proposed_content
|
|
1718
|
+
|
|
1719
|
+
|
|
1720
|
+
def command_rule_promote(args: argparse.Namespace) -> None:
|
|
1721
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1722
|
+
patch, rule, decision, proposed_content = validate_promotion(conn, args.patch_id)
|
|
1723
|
+
current_hash = file_hash(patch["target_path"])
|
|
1724
|
+
new_hash = sha256_text(proposed_content)
|
|
1725
|
+
if args.apply:
|
|
1726
|
+
if current_hash != patch["base_hash"]:
|
|
1727
|
+
conn.execute("UPDATE rule_patch SET status = 'stale', updated_at = ? WHERE id = ?", (utc_now(), patch["id"]))
|
|
1728
|
+
raise CliError("PATCH_STALE", "AGENTS.md changed after the patch was proposed", "run rule propose again")
|
|
1729
|
+
write_text(patch["target_path"], proposed_content)
|
|
1730
|
+
observed_new_hash = file_hash(patch["target_path"])
|
|
1731
|
+
old_target_hash = current_hash
|
|
1732
|
+
else:
|
|
1733
|
+
if current_hash != new_hash:
|
|
1734
|
+
raise CliError("TARGET_HASH_MISMATCH", "target file does not match the approved proposed content")
|
|
1735
|
+
observed_new_hash = current_hash
|
|
1736
|
+
old_target_hash = patch["base_hash"]
|
|
1737
|
+
if observed_new_hash != new_hash:
|
|
1738
|
+
raise CliError("TARGET_HASH_MISMATCH", "applied target hash does not match approved proposed content")
|
|
1739
|
+
now = utc_now()
|
|
1740
|
+
cursor = conn.execute(
|
|
1741
|
+
"""
|
|
1742
|
+
INSERT INTO rule_promotion (
|
|
1743
|
+
rule_id, patch_id, decision_id, mode, target_path, old_target_hash, new_target_hash,
|
|
1744
|
+
applied_by, applied_at, metadata_json
|
|
1745
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1746
|
+
""",
|
|
1747
|
+
(
|
|
1748
|
+
rule["id"],
|
|
1749
|
+
patch["id"],
|
|
1750
|
+
decision["id"],
|
|
1751
|
+
"apply" if args.apply else "record_applied",
|
|
1752
|
+
patch["target_path"],
|
|
1753
|
+
old_target_hash,
|
|
1754
|
+
observed_new_hash,
|
|
1755
|
+
args.actor,
|
|
1756
|
+
now,
|
|
1757
|
+
"{}",
|
|
1758
|
+
),
|
|
1759
|
+
)
|
|
1760
|
+
conn.execute("UPDATE rule_patch SET status = 'applied', updated_at = ? WHERE id = ?", (now, patch["id"]))
|
|
1761
|
+
conn.execute("UPDATE rule_candidate SET status = 'promoted', promoted_at = ? WHERE id = ?", (now, rule["id"]))
|
|
1762
|
+
return {
|
|
1763
|
+
"promotion_id": int(cursor.lastrowid),
|
|
1764
|
+
"rule_id": rule["id"],
|
|
1765
|
+
"patch_id": patch["id"],
|
|
1766
|
+
"mode": "apply" if args.apply else "record_applied",
|
|
1767
|
+
"target_path": patch["target_path"],
|
|
1768
|
+
"new_target_hash": observed_new_hash,
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
write_command(args, "rule.promote", handler)
|
|
1772
|
+
|
|
1773
|
+
|
|
1774
|
+
def command_search(args: argparse.Namespace) -> None:
|
|
1775
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1776
|
+
query = safe_fts_query(args.query)
|
|
1777
|
+
rows = conn.execute(
|
|
1778
|
+
"""
|
|
1779
|
+
SELECT d.id, d.doc_type, d.source_table, d.source_id, d.title, d.summary,
|
|
1780
|
+
snippet(search_fts, 1, '[', ']', '...', 12) AS snippet
|
|
1781
|
+
FROM search_fts
|
|
1782
|
+
JOIN search_doc d ON d.id = search_fts.rowid
|
|
1783
|
+
WHERE search_fts MATCH ?
|
|
1784
|
+
AND d.project = ?
|
|
1785
|
+
AND d.agent = ?
|
|
1786
|
+
AND d.sensitive = 0
|
|
1787
|
+
AND d.indexable = 1
|
|
1788
|
+
LIMIT ?
|
|
1789
|
+
""",
|
|
1790
|
+
(query, args.project, args.agent, args.limit),
|
|
1791
|
+
).fetchall()
|
|
1792
|
+
return {"items": [row_to_dict(row) for row in rows]}
|
|
1793
|
+
|
|
1794
|
+
read_command(args, handler)
|
|
1795
|
+
|
|
1796
|
+
|
|
1797
|
+
def command_pack(args: argparse.Namespace) -> None:
|
|
1798
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1799
|
+
limit = 8 if args.mode == "direct" else 16
|
|
1800
|
+
tokens = query_tokens(args.query)
|
|
1801
|
+
fts_query = safe_fts_query(args.query)
|
|
1802
|
+
rule_clause, rule_values = token_like_clause(["rule_key", "rule_text", "category"], tokens)
|
|
1803
|
+
fact_clause, fact_values = token_like_clause(["fact_key", "fact_text", "category"], tokens)
|
|
1804
|
+
search_rows = conn.execute(
|
|
1805
|
+
"""
|
|
1806
|
+
SELECT d.id, d.doc_type, d.source_table, d.source_id, d.title, d.summary,
|
|
1807
|
+
snippet(search_fts, 1, '[', ']', '...', 12) AS snippet
|
|
1808
|
+
FROM search_fts
|
|
1809
|
+
JOIN search_doc d ON d.id = search_fts.rowid
|
|
1810
|
+
WHERE search_fts MATCH ?
|
|
1811
|
+
AND d.project = ?
|
|
1812
|
+
AND d.agent = ?
|
|
1813
|
+
AND d.sensitive = 0
|
|
1814
|
+
AND d.indexable = 1
|
|
1815
|
+
LIMIT ?
|
|
1816
|
+
""",
|
|
1817
|
+
(fts_query, args.project, args.agent, limit),
|
|
1818
|
+
).fetchall()
|
|
1819
|
+
candidates = conn.execute(
|
|
1820
|
+
f"""
|
|
1821
|
+
SELECT id, rule_key, rule_text, status, score, session_count, evidence_count
|
|
1822
|
+
FROM rule_candidate
|
|
1823
|
+
WHERE project = ? AND agent = ?
|
|
1824
|
+
AND status IN ('candidate','needs_reconciliation','approved','promoted')
|
|
1825
|
+
AND {rule_clause}
|
|
1826
|
+
ORDER BY score DESC, last_seen_at DESC
|
|
1827
|
+
LIMIT ?
|
|
1828
|
+
""",
|
|
1829
|
+
(args.project, args.agent, *rule_values, limit),
|
|
1830
|
+
).fetchall()
|
|
1831
|
+
facts = conn.execute(
|
|
1832
|
+
f"""
|
|
1833
|
+
SELECT id, fact_key, fact_text, category, confidence
|
|
1834
|
+
FROM memory_fact
|
|
1835
|
+
WHERE project = ? AND agent = ? AND status = 'active' AND sensitive = 0
|
|
1836
|
+
AND {fact_clause}
|
|
1837
|
+
ORDER BY last_seen_at DESC
|
|
1838
|
+
LIMIT ?
|
|
1839
|
+
""",
|
|
1840
|
+
(args.project, args.agent, *fact_values, limit),
|
|
1841
|
+
).fetchall()
|
|
1842
|
+
return {
|
|
1843
|
+
"mode": args.mode,
|
|
1844
|
+
"budget": args.budget,
|
|
1845
|
+
"items": [{"type": "search_doc", **row_to_dict(row)} for row in search_rows]
|
|
1846
|
+
+ [
|
|
1847
|
+
{"type": "rule_candidate", **row_to_dict(row)} for row in candidates
|
|
1848
|
+
]
|
|
1849
|
+
+ [{"type": "memory_fact", **row_to_dict(row)} for row in facts],
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
read_command(args, handler)
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
def command_receipt_show(args: argparse.Namespace) -> None:
|
|
1856
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1857
|
+
row = conn.execute(
|
|
1858
|
+
"SELECT * FROM memory_receipt WHERE receipt_key = ?",
|
|
1859
|
+
(args.receipt_key,),
|
|
1860
|
+
).fetchone()
|
|
1861
|
+
if not row:
|
|
1862
|
+
raise CliError("RECEIPT_NOT_FOUND", f"receipt {args.receipt_key!r} does not exist")
|
|
1863
|
+
return {"receipt": row_to_dict(row)}
|
|
1864
|
+
|
|
1865
|
+
read_command(args, handler)
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
def command_complete(args: argparse.Namespace) -> None:
|
|
1869
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1870
|
+
session = require_session(conn, args.session_key) if args.session_key else None
|
|
1871
|
+
task = optional_task(conn, args.task_key)
|
|
1872
|
+
if args.task_key and not task:
|
|
1873
|
+
raise CliError("TASK_NOT_FOUND", f"task {args.task_key!r} does not exist")
|
|
1874
|
+
now = utc_now()
|
|
1875
|
+
if task:
|
|
1876
|
+
conn.execute(
|
|
1877
|
+
"""
|
|
1878
|
+
UPDATE task SET status = 'completed', completed_at = ?, outcome = COALESCE(?, outcome)
|
|
1879
|
+
WHERE id = ?
|
|
1880
|
+
""",
|
|
1881
|
+
(now, args.outcome, task["id"]),
|
|
1882
|
+
)
|
|
1883
|
+
evidence_ids = args.evidence_id or []
|
|
1884
|
+
evidence_refs = args.evidence_ref or []
|
|
1885
|
+
if not evidence_ids and not evidence_refs and not args.no_durable_memory_promoted:
|
|
1886
|
+
raise CliError(
|
|
1887
|
+
"COMPLETION_EVIDENCE_REQUIRED",
|
|
1888
|
+
"complete requires --evidence-id/--evidence-ref or --no-durable-memory-promoted",
|
|
1889
|
+
)
|
|
1890
|
+
for evidence_id in evidence_ids:
|
|
1891
|
+
require_by_id(conn, "evidence", evidence_id)
|
|
1892
|
+
event_cursor = conn.execute(
|
|
1893
|
+
"""
|
|
1894
|
+
INSERT INTO memory_event (session_id, task_id, event_type, state, summary, payload_json, created_at)
|
|
1895
|
+
VALUES (?, ?, 'COMPLETE', 'IDLE', ?, ?, ?)
|
|
1896
|
+
""",
|
|
1897
|
+
(
|
|
1898
|
+
session["id"] if session else (task["session_id"] if task else None),
|
|
1899
|
+
task["id"] if task else None,
|
|
1900
|
+
args.summary or "Task complete.",
|
|
1901
|
+
json_dump(
|
|
1902
|
+
{
|
|
1903
|
+
"outcome": args.outcome or "",
|
|
1904
|
+
"evidence_ids": evidence_ids,
|
|
1905
|
+
"evidence_refs": evidence_refs,
|
|
1906
|
+
"no_durable_memory_promoted": args.no_durable_memory_promoted,
|
|
1907
|
+
}
|
|
1908
|
+
),
|
|
1909
|
+
now,
|
|
1910
|
+
),
|
|
1911
|
+
)
|
|
1912
|
+
return {"event_id": int(event_cursor.lastrowid), "state": "IDLE"}
|
|
1913
|
+
|
|
1914
|
+
write_command(args, "complete", handler)
|
|
1915
|
+
|
|
1916
|
+
|
|
1917
|
+
def command_remember_requirement(args: argparse.Namespace) -> None:
|
|
1918
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1919
|
+
session = require_session(conn, args.session_key) if args.session_key else None
|
|
1920
|
+
task = optional_task(conn, args.task_key)
|
|
1921
|
+
if args.task_key and not task:
|
|
1922
|
+
raise CliError("TASK_NOT_FOUND", f"task {args.task_key!r} does not exist")
|
|
1923
|
+
fact_key = args.fact_key or normalize_rule_key(args.text)
|
|
1924
|
+
now = utc_now()
|
|
1925
|
+
metadata = {
|
|
1926
|
+
"source": args.source,
|
|
1927
|
+
"task_key": args.task_key or "",
|
|
1928
|
+
"memory_layer": "requirement",
|
|
1929
|
+
}
|
|
1930
|
+
conn.execute(
|
|
1931
|
+
"""
|
|
1932
|
+
INSERT INTO memory_fact (
|
|
1933
|
+
session_id, project, agent, fact_key, fact_text, category, confidence, status,
|
|
1934
|
+
sensitive, indexable, first_seen_at, last_seen_at, metadata_json
|
|
1935
|
+
) VALUES (?, ?, ?, ?, ?, 'requirement', ?, 'active', 0, 1, ?, ?, ?)
|
|
1936
|
+
ON CONFLICT(project, agent, fact_key) DO UPDATE SET
|
|
1937
|
+
fact_text = excluded.fact_text,
|
|
1938
|
+
category = excluded.category,
|
|
1939
|
+
confidence = excluded.confidence,
|
|
1940
|
+
last_seen_at = excluded.last_seen_at,
|
|
1941
|
+
metadata_json = excluded.metadata_json
|
|
1942
|
+
""",
|
|
1943
|
+
(
|
|
1944
|
+
session["id"] if session else None,
|
|
1945
|
+
args.project,
|
|
1946
|
+
args.agent,
|
|
1947
|
+
fact_key,
|
|
1948
|
+
args.text,
|
|
1949
|
+
args.confidence,
|
|
1950
|
+
now,
|
|
1951
|
+
now,
|
|
1952
|
+
json_dump(metadata),
|
|
1953
|
+
),
|
|
1954
|
+
)
|
|
1955
|
+
fact = conn.execute(
|
|
1956
|
+
"SELECT * FROM memory_fact WHERE project = ? AND agent = ? AND fact_key = ?",
|
|
1957
|
+
(args.project, args.agent, fact_key),
|
|
1958
|
+
).fetchone()
|
|
1959
|
+
index_search_doc(
|
|
1960
|
+
conn,
|
|
1961
|
+
args.project,
|
|
1962
|
+
args.agent,
|
|
1963
|
+
"memory_fact",
|
|
1964
|
+
"memory_fact",
|
|
1965
|
+
fact["id"],
|
|
1966
|
+
fact_key,
|
|
1967
|
+
args.text,
|
|
1968
|
+
args.text[:240],
|
|
1969
|
+
0,
|
|
1970
|
+
1,
|
|
1971
|
+
metadata,
|
|
1972
|
+
)
|
|
1973
|
+
return {"fact_id": fact["id"], "fact_key": fact_key, "task_key": args.task_key or ""}
|
|
1974
|
+
|
|
1975
|
+
write_command(args, "remember-requirement", handler)
|
|
1976
|
+
|
|
1977
|
+
|
|
1978
|
+
def command_checkpoint(args: argparse.Namespace) -> None:
|
|
1979
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
1980
|
+
session = require_session(conn, args.session_key) if args.session_key else None
|
|
1981
|
+
task = optional_task(conn, args.task_key)
|
|
1982
|
+
if args.task_key and not task:
|
|
1983
|
+
raise CliError("TASK_NOT_FOUND", f"task {args.task_key!r} does not exist")
|
|
1984
|
+
summary_parts = [
|
|
1985
|
+
f"Goal: {args.goal}",
|
|
1986
|
+
f"Scope: {args.scope}",
|
|
1987
|
+
f"Progress: {args.progress}",
|
|
1988
|
+
]
|
|
1989
|
+
if args.evidence_ref:
|
|
1990
|
+
summary_parts.append("Evidence: " + "; ".join(args.evidence_ref))
|
|
1991
|
+
payload = {
|
|
1992
|
+
"goal": args.goal,
|
|
1993
|
+
"scope": args.scope,
|
|
1994
|
+
"progress": args.progress,
|
|
1995
|
+
"files_touched": args.file or [],
|
|
1996
|
+
"evidence_refs": args.evidence_ref or [],
|
|
1997
|
+
"memory_layer": "task_checkpoint",
|
|
1998
|
+
}
|
|
1999
|
+
cursor = conn.execute(
|
|
2000
|
+
"""
|
|
2001
|
+
INSERT INTO memory_event (session_id, task_id, event_type, state, summary, payload_json, created_at)
|
|
2002
|
+
VALUES (?, ?, 'CHECKPOINT', 'ACTIVE', ?, ?, ?)
|
|
2003
|
+
""",
|
|
2004
|
+
(
|
|
2005
|
+
session["id"] if session else (task["session_id"] if task else None),
|
|
2006
|
+
task["id"] if task else None,
|
|
2007
|
+
" | ".join(summary_parts),
|
|
2008
|
+
json_dump(payload),
|
|
2009
|
+
utc_now(),
|
|
2010
|
+
),
|
|
2011
|
+
)
|
|
2012
|
+
return {"event_id": int(cursor.lastrowid), "state": "ACTIVE", "task_key": args.task_key or ""}
|
|
2013
|
+
|
|
2014
|
+
write_command(args, "checkpoint", handler)
|
|
2015
|
+
|
|
2016
|
+
|
|
2017
|
+
def command_pack_current(args: argparse.Namespace) -> None:
|
|
2018
|
+
def handler(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
2019
|
+
task = optional_task(conn, args.task_key)
|
|
2020
|
+
if args.task_key and not task:
|
|
2021
|
+
raise CliError("TASK_NOT_FOUND", f"task {args.task_key!r} does not exist")
|
|
2022
|
+
if not task:
|
|
2023
|
+
task = conn.execute(
|
|
2024
|
+
"""
|
|
2025
|
+
SELECT * FROM task
|
|
2026
|
+
WHERE project = ? AND agent = ? AND status = 'active'
|
|
2027
|
+
ORDER BY started_at DESC, id DESC
|
|
2028
|
+
LIMIT 1
|
|
2029
|
+
""",
|
|
2030
|
+
(args.project, args.agent),
|
|
2031
|
+
).fetchone()
|
|
2032
|
+
checkpoint_sql = """
|
|
2033
|
+
SELECT e.id, e.event_type, e.state, e.summary, e.payload_json, e.created_at,
|
|
2034
|
+
t.task_key, t.title AS task_title
|
|
2035
|
+
FROM memory_event e
|
|
2036
|
+
LEFT JOIN task t ON t.id = e.task_id
|
|
2037
|
+
WHERE e.event_type = 'CHECKPOINT'
|
|
2038
|
+
AND e.state = 'ACTIVE'
|
|
2039
|
+
"""
|
|
2040
|
+
checkpoint_values: list[Any] = []
|
|
2041
|
+
if task:
|
|
2042
|
+
checkpoint_sql += " AND e.task_id = ?"
|
|
2043
|
+
checkpoint_values.append(task["id"])
|
|
2044
|
+
checkpoint_sql += " ORDER BY e.created_at DESC, e.id DESC LIMIT 3"
|
|
2045
|
+
checkpoints = conn.execute(checkpoint_sql, checkpoint_values).fetchall()
|
|
2046
|
+
requirements = conn.execute(
|
|
2047
|
+
"""
|
|
2048
|
+
SELECT id, fact_key, fact_text, category, confidence, last_seen_at, metadata_json
|
|
2049
|
+
FROM memory_fact
|
|
2050
|
+
WHERE project = ? AND agent = ? AND status = 'active' AND sensitive = 0
|
|
2051
|
+
AND category IN ('requirement', 'preference', 'decision')
|
|
2052
|
+
ORDER BY last_seen_at DESC, id DESC
|
|
2053
|
+
LIMIT 8
|
|
2054
|
+
""",
|
|
2055
|
+
(args.project, args.agent),
|
|
2056
|
+
).fetchall()
|
|
2057
|
+
findings = conn.execute(
|
|
2058
|
+
"""
|
|
2059
|
+
SELECT id, kind, category, severity, summary, correct_behavior, raw_ref, created_at
|
|
2060
|
+
FROM finding
|
|
2061
|
+
WHERE project = ? AND agent = ? AND sensitive = 0
|
|
2062
|
+
AND kind IN ('correction', 'failure', 'preference', 'risk', 'observation')
|
|
2063
|
+
ORDER BY created_at DESC, id DESC
|
|
2064
|
+
LIMIT 6
|
|
2065
|
+
""",
|
|
2066
|
+
(args.project, args.agent),
|
|
2067
|
+
).fetchall()
|
|
2068
|
+
evidence = conn.execute(
|
|
2069
|
+
"""
|
|
2070
|
+
SELECT id, source_type, evidence_key, evidence_text, weight, created_at
|
|
2071
|
+
FROM evidence
|
|
2072
|
+
WHERE project = ? AND agent = ? AND sensitive = 0
|
|
2073
|
+
AND source_type IN ('file', 'diff', 'test', 'user_confirmation')
|
|
2074
|
+
ORDER BY created_at DESC, id DESC
|
|
2075
|
+
LIMIT 6
|
|
2076
|
+
""",
|
|
2077
|
+
(args.project, args.agent),
|
|
2078
|
+
).fetchall()
|
|
2079
|
+
return {
|
|
2080
|
+
"budget": args.budget,
|
|
2081
|
+
"query": "current active task requirements decisions evidence",
|
|
2082
|
+
"active_task": row_to_dict(task),
|
|
2083
|
+
"active_checkpoints": [row_to_dict(row) for row in checkpoints],
|
|
2084
|
+
"recent_requirement_decisions": [row_to_dict(row) for row in requirements],
|
|
2085
|
+
"relevant_durable_findings": [row_to_dict(row) for row in findings],
|
|
2086
|
+
"recent_evidence": [row_to_dict(row) for row in evidence],
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
read_command(args, handler)
|
|
2090
|
+
|
|
2091
|
+
|
|
2092
|
+
def add_db_arg(parser: argparse.ArgumentParser) -> None:
|
|
2093
|
+
parser.add_argument("--db", default=DEFAULT_DB, help="SQLite memory DB path")
|
|
2094
|
+
|
|
2095
|
+
|
|
2096
|
+
def add_project_agent_args(parser: argparse.ArgumentParser) -> None:
|
|
2097
|
+
parser.add_argument("--project", default=project_default())
|
|
2098
|
+
parser.add_argument("--agent", default=DEFAULT_AGENT)
|
|
2099
|
+
|
|
2100
|
+
|
|
2101
|
+
def add_metadata_arg(parser: argparse.ArgumentParser) -> None:
|
|
2102
|
+
parser.add_argument("--metadata-json", default="{}")
|
|
2103
|
+
|
|
2104
|
+
|
|
2105
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
2106
|
+
parser = argparse.ArgumentParser(description="ROSE project-local SQLite memory CLI")
|
|
2107
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
2108
|
+
|
|
2109
|
+
init = sub.add_parser("init")
|
|
2110
|
+
add_db_arg(init)
|
|
2111
|
+
add_project_agent_args(init)
|
|
2112
|
+
init.set_defaults(func=command_init)
|
|
2113
|
+
|
|
2114
|
+
doctor = sub.add_parser("doctor")
|
|
2115
|
+
add_db_arg(doctor)
|
|
2116
|
+
doctor.add_argument("--record", action="store_true")
|
|
2117
|
+
doctor.set_defaults(func=command_doctor)
|
|
2118
|
+
|
|
2119
|
+
migrate = sub.add_parser("migrate")
|
|
2120
|
+
add_db_arg(migrate)
|
|
2121
|
+
migrate.set_defaults(func=command_migrate)
|
|
2122
|
+
|
|
2123
|
+
session = sub.add_parser("session")
|
|
2124
|
+
session_sub = session.add_subparsers(dest="session_command", required=True)
|
|
2125
|
+
session_start = session_sub.add_parser("start")
|
|
2126
|
+
add_db_arg(session_start)
|
|
2127
|
+
add_project_agent_args(session_start)
|
|
2128
|
+
add_metadata_arg(session_start)
|
|
2129
|
+
session_start.add_argument("--session-key")
|
|
2130
|
+
session_start.set_defaults(func=command_session_start)
|
|
2131
|
+
session_end = session_sub.add_parser("end")
|
|
2132
|
+
add_db_arg(session_end)
|
|
2133
|
+
session_end.add_argument("--session-key", required=True)
|
|
2134
|
+
session_end.add_argument("--summary")
|
|
2135
|
+
session_end.set_defaults(func=command_session_end)
|
|
2136
|
+
|
|
2137
|
+
task = sub.add_parser("task")
|
|
2138
|
+
task_sub = task.add_subparsers(dest="task_command", required=True)
|
|
2139
|
+
task_start = task_sub.add_parser("start")
|
|
2140
|
+
add_db_arg(task_start)
|
|
2141
|
+
add_project_agent_args(task_start)
|
|
2142
|
+
add_metadata_arg(task_start)
|
|
2143
|
+
task_start.add_argument("--session-key", required=True)
|
|
2144
|
+
task_start.add_argument("--task-key")
|
|
2145
|
+
task_start.add_argument("--title", required=True)
|
|
2146
|
+
task_start.add_argument("--scope-text")
|
|
2147
|
+
task_start.set_defaults(func=command_task_start)
|
|
2148
|
+
task_update = task_sub.add_parser("update")
|
|
2149
|
+
add_db_arg(task_update)
|
|
2150
|
+
add_metadata_arg(task_update)
|
|
2151
|
+
task_update.add_argument("--task-key", required=True)
|
|
2152
|
+
task_update.add_argument("--title")
|
|
2153
|
+
task_update.add_argument("--scope-text")
|
|
2154
|
+
task_update.add_argument("--status", choices=["active", "blocked", "completed", "cancelled"])
|
|
2155
|
+
task_update.add_argument("--outcome")
|
|
2156
|
+
task_update.set_defaults(func=command_task_update)
|
|
2157
|
+
task_complete = task_sub.add_parser("complete")
|
|
2158
|
+
add_db_arg(task_complete)
|
|
2159
|
+
task_complete.add_argument("--task-key", required=True)
|
|
2160
|
+
task_complete.add_argument("--outcome")
|
|
2161
|
+
task_complete.add_argument("--summary")
|
|
2162
|
+
task_complete.set_defaults(func=command_task_complete)
|
|
2163
|
+
|
|
2164
|
+
event = sub.add_parser("event")
|
|
2165
|
+
event_sub = event.add_subparsers(dest="event_command", required=True)
|
|
2166
|
+
event_add = event_sub.add_parser("add")
|
|
2167
|
+
add_db_arg(event_add)
|
|
2168
|
+
event_add.add_argument("--session-key")
|
|
2169
|
+
event_add.add_argument("--task-key")
|
|
2170
|
+
event_add.add_argument("--event-type", required=True)
|
|
2171
|
+
event_add.add_argument("--state")
|
|
2172
|
+
event_add.add_argument("--summary")
|
|
2173
|
+
event_add.add_argument("--payload-json", default="{}")
|
|
2174
|
+
event_add.set_defaults(func=command_event_add)
|
|
2175
|
+
|
|
2176
|
+
fact = sub.add_parser("fact")
|
|
2177
|
+
fact_sub = fact.add_subparsers(dest="fact_command", required=True)
|
|
2178
|
+
fact_add = fact_sub.add_parser("add")
|
|
2179
|
+
add_db_arg(fact_add)
|
|
2180
|
+
add_project_agent_args(fact_add)
|
|
2181
|
+
add_metadata_arg(fact_add)
|
|
2182
|
+
fact_add.add_argument("--session-key")
|
|
2183
|
+
fact_add.add_argument("--fact-key")
|
|
2184
|
+
fact_add.add_argument("--fact-text", required=True)
|
|
2185
|
+
fact_add.add_argument("--category", required=True)
|
|
2186
|
+
fact_add.add_argument("--confidence", choices=["low", "medium", "high"], default="medium")
|
|
2187
|
+
fact_add.add_argument("--sensitive", action="store_true")
|
|
2188
|
+
fact_add.add_argument("--no-index", dest="indexable", action="store_false", default=True)
|
|
2189
|
+
fact_add.set_defaults(func=command_fact_add)
|
|
2190
|
+
|
|
2191
|
+
claim = sub.add_parser("claim")
|
|
2192
|
+
claim_sub = claim.add_subparsers(dest="claim_command", required=True)
|
|
2193
|
+
claim_add = claim_sub.add_parser("add")
|
|
2194
|
+
add_db_arg(claim_add)
|
|
2195
|
+
add_project_agent_args(claim_add)
|
|
2196
|
+
add_metadata_arg(claim_add)
|
|
2197
|
+
claim_add.add_argument("--session-key")
|
|
2198
|
+
claim_add.add_argument("--task-key")
|
|
2199
|
+
claim_add.add_argument("--claim-key")
|
|
2200
|
+
claim_add.add_argument("--claim-text", required=True)
|
|
2201
|
+
claim_add.add_argument(
|
|
2202
|
+
"--verification-status",
|
|
2203
|
+
choices=["unverified", "verified", "disputed", "superseded"],
|
|
2204
|
+
default="unverified",
|
|
2205
|
+
)
|
|
2206
|
+
claim_add.add_argument("--evidence-summary")
|
|
2207
|
+
claim_add.add_argument("--sensitive", action="store_true")
|
|
2208
|
+
claim_add.add_argument("--no-index", dest="indexable", action="store_false", default=True)
|
|
2209
|
+
claim_add.set_defaults(func=command_claim_add)
|
|
2210
|
+
|
|
2211
|
+
finding = sub.add_parser("finding")
|
|
2212
|
+
finding_sub = finding.add_subparsers(dest="finding_command", required=True)
|
|
2213
|
+
finding_add = finding_sub.add_parser("add")
|
|
2214
|
+
add_db_arg(finding_add)
|
|
2215
|
+
add_project_agent_args(finding_add)
|
|
2216
|
+
add_metadata_arg(finding_add)
|
|
2217
|
+
finding_add.add_argument("--session-key", required=True)
|
|
2218
|
+
finding_add.add_argument("--task-key")
|
|
2219
|
+
finding_add.add_argument(
|
|
2220
|
+
"--kind",
|
|
2221
|
+
required=True,
|
|
2222
|
+
choices=["correction", "failure", "review_rejection", "preference", "risk", "observation"],
|
|
2223
|
+
)
|
|
2224
|
+
finding_add.add_argument("--category", required=True)
|
|
2225
|
+
finding_add.add_argument("--severity", choices=["low", "normal", "high", "critical"], default="normal")
|
|
2226
|
+
finding_add.add_argument("--summary", required=True)
|
|
2227
|
+
finding_add.add_argument("--wrong-behavior")
|
|
2228
|
+
finding_add.add_argument("--correct-behavior")
|
|
2229
|
+
finding_add.add_argument("--raw-ref")
|
|
2230
|
+
finding_add.add_argument("--evidence-key")
|
|
2231
|
+
finding_add.add_argument("--sensitive", action="store_true")
|
|
2232
|
+
finding_add.add_argument("--no-index", dest="indexable", action="store_false", default=True)
|
|
2233
|
+
finding_add.set_defaults(func=command_finding_add)
|
|
2234
|
+
|
|
2235
|
+
evidence = sub.add_parser("evidence")
|
|
2236
|
+
evidence_sub = evidence.add_subparsers(dest="evidence_command", required=True)
|
|
2237
|
+
evidence_add = evidence_sub.add_parser("add")
|
|
2238
|
+
add_db_arg(evidence_add)
|
|
2239
|
+
add_project_agent_args(evidence_add)
|
|
2240
|
+
add_metadata_arg(evidence_add)
|
|
2241
|
+
evidence_add.add_argument("--session-key")
|
|
2242
|
+
evidence_add.add_argument("--task-key")
|
|
2243
|
+
evidence_add.add_argument(
|
|
2244
|
+
"--source-type",
|
|
2245
|
+
required=True,
|
|
2246
|
+
choices=["finding", "claim", "memory_event", "task", "file", "diff", "test", "user_confirmation"],
|
|
2247
|
+
)
|
|
2248
|
+
evidence_add.add_argument("--source-id", type=int)
|
|
2249
|
+
evidence_add.add_argument("--evidence-key")
|
|
2250
|
+
evidence_add.add_argument("--evidence-text", required=True)
|
|
2251
|
+
evidence_add.add_argument("--weight", type=int, default=1)
|
|
2252
|
+
evidence_add.add_argument("--sensitive", action="store_true")
|
|
2253
|
+
evidence_add.add_argument("--no-index", dest="indexable", action="store_false", default=True)
|
|
2254
|
+
evidence_add.set_defaults(func=command_evidence_add)
|
|
2255
|
+
|
|
2256
|
+
rule = sub.add_parser("rule")
|
|
2257
|
+
rule_sub = rule.add_subparsers(dest="rule_command", required=True)
|
|
2258
|
+
rule_observe = rule_sub.add_parser("observe")
|
|
2259
|
+
add_db_arg(rule_observe)
|
|
2260
|
+
add_project_agent_args(rule_observe)
|
|
2261
|
+
add_metadata_arg(rule_observe)
|
|
2262
|
+
rule_observe.add_argument("--evidence-id", type=int, required=True)
|
|
2263
|
+
rule_observe.add_argument("--rule-key")
|
|
2264
|
+
rule_observe.add_argument("--rule-text", required=True)
|
|
2265
|
+
rule_observe.add_argument("--category", required=True)
|
|
2266
|
+
rule_observe.add_argument("--severity", choices=["low", "normal", "high", "critical"], default="normal")
|
|
2267
|
+
rule_observe.add_argument("--signal-type", required=True, choices=sorted(SIGNAL_WEIGHTS))
|
|
2268
|
+
rule_observe.set_defaults(func=command_rule_observe)
|
|
2269
|
+
rule_list = rule_sub.add_parser("list")
|
|
2270
|
+
add_db_arg(rule_list)
|
|
2271
|
+
add_project_agent_args(rule_list)
|
|
2272
|
+
rule_list.add_argument(
|
|
2273
|
+
"--status",
|
|
2274
|
+
choices=["memory", "candidate", "needs_reconciliation", "approved", "promoted", "rejected", "superseded", "expired"],
|
|
2275
|
+
)
|
|
2276
|
+
rule_list.add_argument("--limit", type=int, default=50)
|
|
2277
|
+
rule_list.set_defaults(func=command_rule_list)
|
|
2278
|
+
rule_propose = rule_sub.add_parser("propose")
|
|
2279
|
+
add_db_arg(rule_propose)
|
|
2280
|
+
rule_propose.add_argument("--rule-id", type=int, required=True)
|
|
2281
|
+
rule_propose.add_argument("--target", default=DEFAULT_TARGET)
|
|
2282
|
+
rule_propose.set_defaults(func=command_rule_propose)
|
|
2283
|
+
rule_approve = rule_sub.add_parser("approve")
|
|
2284
|
+
add_db_arg(rule_approve)
|
|
2285
|
+
rule_approve.add_argument("--patch-id", type=int, required=True)
|
|
2286
|
+
rule_approve.add_argument("--patch-hash", required=True)
|
|
2287
|
+
rule_approve.add_argument("--actor", required=True)
|
|
2288
|
+
rule_approve.add_argument("--reason")
|
|
2289
|
+
rule_approve.add_argument("--approval-evidence-id", type=int)
|
|
2290
|
+
rule_approve.set_defaults(func=command_rule_approve)
|
|
2291
|
+
rule_reject = rule_sub.add_parser("reject")
|
|
2292
|
+
add_db_arg(rule_reject)
|
|
2293
|
+
rule_reject.add_argument("--patch-id", type=int, required=True)
|
|
2294
|
+
rule_reject.add_argument("--actor", required=True)
|
|
2295
|
+
rule_reject.add_argument("--reason")
|
|
2296
|
+
rule_reject.set_defaults(func=command_rule_reject)
|
|
2297
|
+
rule_reconcile = rule_sub.add_parser("reconcile")
|
|
2298
|
+
add_db_arg(rule_reconcile)
|
|
2299
|
+
rule_reconcile.add_argument("--rule-id", type=int, required=True)
|
|
2300
|
+
rule_reconcile.add_argument("--status", required=True, choices=["candidate", "rejected", "superseded"])
|
|
2301
|
+
rule_reconcile.add_argument("--conflicts-json", default="[]")
|
|
2302
|
+
rule_reconcile.set_defaults(func=command_rule_reconcile)
|
|
2303
|
+
rule_promote = rule_sub.add_parser("promote")
|
|
2304
|
+
add_db_arg(rule_promote)
|
|
2305
|
+
rule_promote.add_argument("--patch-id", type=int, required=True)
|
|
2306
|
+
mode = rule_promote.add_mutually_exclusive_group(required=True)
|
|
2307
|
+
mode.add_argument("--apply", action="store_true")
|
|
2308
|
+
mode.add_argument("--record-applied", action="store_true")
|
|
2309
|
+
rule_promote.add_argument("--actor", required=True)
|
|
2310
|
+
rule_promote.set_defaults(func=command_rule_promote)
|
|
2311
|
+
|
|
2312
|
+
search = sub.add_parser("search")
|
|
2313
|
+
add_db_arg(search)
|
|
2314
|
+
add_project_agent_args(search)
|
|
2315
|
+
search.add_argument("query")
|
|
2316
|
+
search.add_argument("--limit", type=int, default=10)
|
|
2317
|
+
search.set_defaults(func=command_search)
|
|
2318
|
+
|
|
2319
|
+
pack = sub.add_parser("pack")
|
|
2320
|
+
add_db_arg(pack)
|
|
2321
|
+
add_project_agent_args(pack)
|
|
2322
|
+
pack.add_argument("query")
|
|
2323
|
+
pack.add_argument("--mode", choices=["direct", "spec", "research"], default="direct")
|
|
2324
|
+
pack.add_argument("--budget", type=int, default=800)
|
|
2325
|
+
pack.set_defaults(func=command_pack)
|
|
2326
|
+
|
|
2327
|
+
receipt = sub.add_parser("receipt")
|
|
2328
|
+
receipt_sub = receipt.add_subparsers(dest="receipt_command", required=True)
|
|
2329
|
+
receipt_show = receipt_sub.add_parser("show")
|
|
2330
|
+
add_db_arg(receipt_show)
|
|
2331
|
+
receipt_show.add_argument("receipt_key")
|
|
2332
|
+
receipt_show.set_defaults(func=command_receipt_show)
|
|
2333
|
+
|
|
2334
|
+
complete = sub.add_parser("complete")
|
|
2335
|
+
add_db_arg(complete)
|
|
2336
|
+
complete.add_argument("--session-key")
|
|
2337
|
+
complete.add_argument("--task-key")
|
|
2338
|
+
complete.add_argument("--outcome")
|
|
2339
|
+
complete.add_argument("--summary")
|
|
2340
|
+
complete.add_argument("--evidence-id", type=int, action="append")
|
|
2341
|
+
complete.add_argument("--evidence-ref", action="append")
|
|
2342
|
+
complete.add_argument("--no-durable-memory-promoted", action="store_true")
|
|
2343
|
+
complete.set_defaults(func=command_complete)
|
|
2344
|
+
|
|
2345
|
+
remember_requirement = sub.add_parser("remember-requirement")
|
|
2346
|
+
add_db_arg(remember_requirement)
|
|
2347
|
+
add_project_agent_args(remember_requirement)
|
|
2348
|
+
remember_requirement.add_argument("--session-key")
|
|
2349
|
+
remember_requirement.add_argument("--task-key")
|
|
2350
|
+
remember_requirement.add_argument("--fact-key")
|
|
2351
|
+
remember_requirement.add_argument("--text", required=True)
|
|
2352
|
+
remember_requirement.add_argument("--source", default="conversation")
|
|
2353
|
+
remember_requirement.add_argument("--confidence", choices=["low", "medium", "high"], default="high")
|
|
2354
|
+
remember_requirement.set_defaults(func=command_remember_requirement)
|
|
2355
|
+
|
|
2356
|
+
checkpoint = sub.add_parser("checkpoint")
|
|
2357
|
+
add_db_arg(checkpoint)
|
|
2358
|
+
checkpoint.add_argument("--session-key")
|
|
2359
|
+
checkpoint.add_argument("--task-key")
|
|
2360
|
+
checkpoint.add_argument("--goal", required=True)
|
|
2361
|
+
checkpoint.add_argument("--scope", required=True)
|
|
2362
|
+
checkpoint.add_argument("--progress", required=True)
|
|
2363
|
+
checkpoint.add_argument("--file", action="append")
|
|
2364
|
+
checkpoint.add_argument("--evidence-ref", action="append")
|
|
2365
|
+
checkpoint.set_defaults(func=command_checkpoint)
|
|
2366
|
+
|
|
2367
|
+
pack_current = sub.add_parser("pack-current")
|
|
2368
|
+
add_db_arg(pack_current)
|
|
2369
|
+
add_project_agent_args(pack_current)
|
|
2370
|
+
pack_current.add_argument("--task-key")
|
|
2371
|
+
pack_current.add_argument("--budget", type=int, default=1200)
|
|
2372
|
+
pack_current.set_defaults(func=command_pack_current)
|
|
2373
|
+
|
|
2374
|
+
return parser
|
|
2375
|
+
|
|
2376
|
+
|
|
2377
|
+
def main(argv: Iterable[str] | None = None) -> None:
|
|
2378
|
+
parser = build_parser()
|
|
2379
|
+
args = parser.parse_args(list(argv) if argv is not None else None)
|
|
2380
|
+
args.func(args)
|
|
2381
|
+
|
|
2382
|
+
|
|
2383
|
+
if __name__ == "__main__":
|
|
2384
|
+
main()
|