marvisx-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (587) hide show
  1. core/api/__init__.py +0 -0
  2. core/api/agents/__init__.py +0 -0
  3. core/api/agents/session_health.py +59 -0
  4. core/api/agents/session_manager.py +206 -0
  5. core/api/bin/marvisx-state-hook.py +182 -0
  6. core/api/config.py +533 -0
  7. core/api/db.py +1516 -0
  8. core/api/dependencies/__init__.py +0 -0
  9. core/api/dependencies/tenant.py +34 -0
  10. core/api/main.py +1641 -0
  11. core/api/mcp/__init__.py +8 -0
  12. core/api/mcp/_adapter.py +184 -0
  13. core/api/mcp/server.py +58 -0
  14. core/api/mcp/tools/__init__.py +59 -0
  15. core/api/mcp/tools/brain.py +599 -0
  16. core/api/mcp/tools/graph.py +380 -0
  17. core/api/mcp/tools/handoffs.py +112 -0
  18. core/api/mcp/tools/ingest.py +326 -0
  19. core/api/mcp/tools/learnings.py +144 -0
  20. core/api/mcp/tools/projects.py +99 -0
  21. core/api/mcp/tools/pull_requests.py +173 -0
  22. core/api/mcp/tools/safety.py +111 -0
  23. core/api/mcp/tools/search.py +79 -0
  24. core/api/mcp/tools/tasks.py +258 -0
  25. core/api/middleware/__init__.py +0 -0
  26. core/api/middleware/tool_call_audit.py +111 -0
  27. core/api/models/__init__.py +346 -0
  28. core/api/models/auth.py +48 -0
  29. core/api/models/brain.py +1006 -0
  30. core/api/models/common.py +76 -0
  31. core/api/models/costs.py +91 -0
  32. core/api/models/graph.py +66 -0
  33. core/api/models/graph_cosmo.py +125 -0
  34. core/api/models/graph_pr_impact.py +257 -0
  35. core/api/models/graph_ux.py +141 -0
  36. core/api/models/inbox.py +230 -0
  37. core/api/models/ingest_keys.py +108 -0
  38. core/api/models/kg.py +41 -0
  39. core/api/models/llm_config.py +56 -0
  40. core/api/models/monitoring.py +234 -0
  41. core/api/models/projects.py +161 -0
  42. core/api/models/search.py +42 -0
  43. core/api/models/sessions.py +322 -0
  44. core/api/models/tasks.py +184 -0
  45. core/api/models/teams.py +63 -0
  46. core/api/models/users.py +184 -0
  47. core/api/observability/__init__.py +0 -0
  48. core/api/observability/tracing.py +92 -0
  49. core/api/paths.py +26 -0
  50. core/api/rate_limit.py +24 -0
  51. core/api/rbac.py +112 -0
  52. core/api/routers/__init__.py +0 -0
  53. core/api/routers/_adapter.py +24 -0
  54. core/api/routers/admin_pr_impact.py +230 -0
  55. core/api/routers/admin_settings.py +147 -0
  56. core/api/routers/agent.py +1079 -0
  57. core/api/routers/agent_tokens.py +276 -0
  58. core/api/routers/app_settings.py +112 -0
  59. core/api/routers/audit.py +89 -0
  60. core/api/routers/auth.py +586 -0
  61. core/api/routers/bench.py +161 -0
  62. core/api/routers/brain.py +881 -0
  63. core/api/routers/brain_directions.py +527 -0
  64. core/api/routers/ci_checks.py +140 -0
  65. core/api/routers/comments.py +273 -0
  66. core/api/routers/costs.py +148 -0
  67. core/api/routers/docs_coverage.py +217 -0
  68. core/api/routers/docs_governance.py +63 -0
  69. core/api/routers/documents.py +318 -0
  70. core/api/routers/files.py +163 -0
  71. core/api/routers/finder.py +987 -0
  72. core/api/routers/graph.py +836 -0
  73. core/api/routers/handoffs.py +156 -0
  74. core/api/routers/inbox.py +496 -0
  75. core/api/routers/ingest_api_keys.py +205 -0
  76. core/api/routers/ingest_triage.py +1227 -0
  77. core/api/routers/judge.py +306 -0
  78. core/api/routers/kg.py +336 -0
  79. core/api/routers/learnings.py +253 -0
  80. core/api/routers/llm_config.py +130 -0
  81. core/api/routers/monitoring.py +347 -0
  82. core/api/routers/notifications.py +125 -0
  83. core/api/routers/pr_impact.py +315 -0
  84. core/api/routers/projects.py +1061 -0
  85. core/api/routers/pull_requests.py +312 -0
  86. core/api/routers/push.py +67 -0
  87. core/api/routers/raci.py +228 -0
  88. core/api/routers/search.py +125 -0
  89. core/api/routers/sessions.py +3100 -0
  90. core/api/routers/settings.py +90 -0
  91. core/api/routers/share_repo.py +68 -0
  92. core/api/routers/status_updates.py +96 -0
  93. core/api/routers/tags.py +45 -0
  94. core/api/routers/tasks.py +526 -0
  95. core/api/routers/teams.py +425 -0
  96. core/api/routers/terminal.py +105 -0
  97. core/api/routers/users.py +331 -0
  98. core/api/routers/webhooks.py +330 -0
  99. core/api/runtime_settings.py +84 -0
  100. core/api/security.py +652 -0
  101. core/api/services/__init__.py +0 -0
  102. core/api/services/audit.py +58 -0
  103. core/api/services/auto_approval.py +82 -0
  104. core/api/services/brain/__init__.py +52 -0
  105. core/api/services/brain/baseline.py +230 -0
  106. core/api/services/brain/capabilities.py +75 -0
  107. core/api/services/brain/cascade_rollup.py +388 -0
  108. core/api/services/brain/compound_bridge.py +215 -0
  109. core/api/services/brain/cycle.py +1242 -0
  110. core/api/services/brain/cycle_snapshot.py +371 -0
  111. core/api/services/brain/digest_collector.py +147 -0
  112. core/api/services/brain/direction.py +421 -0
  113. core/api/services/brain/drift.py +356 -0
  114. core/api/services/brain/drift_router.py +409 -0
  115. core/api/services/brain/edge_metrics.py +79 -0
  116. core/api/services/brain/events_reader.py +222 -0
  117. core/api/services/brain/findings.py +1379 -0
  118. core/api/services/brain/findings_reader.py +1006 -0
  119. core/api/services/brain/jobs.py +733 -0
  120. core/api/services/brain/journal.py +206 -0
  121. core/api/services/brain/knowledge_forms.py +92 -0
  122. core/api/services/brain/llm/__init__.py +37 -0
  123. core/api/services/brain/llm/_runner.py +62 -0
  124. core/api/services/brain/llm/base.py +70 -0
  125. core/api/services/brain/llm/cache.py +99 -0
  126. core/api/services/brain/llm/constants.py +46 -0
  127. core/api/services/brain/llm/direction_alignment.py +289 -0
  128. core/api/services/brain/llm/factory.py +132 -0
  129. core/api/services/brain/llm/finding_reasoning.py +98 -0
  130. core/api/services/brain/llm/finding_summary.py +92 -0
  131. core/api/services/brain/llm/grounding.py +46 -0
  132. core/api/services/brain/llm/journal_polish.py +96 -0
  133. core/api/services/brain/llm/local_gateway.py +426 -0
  134. core/api/services/brain/llm/parsers.py +71 -0
  135. core/api/services/brain/llm/router_glue.py +422 -0
  136. core/api/services/brain/memory_ops.py +1677 -0
  137. core/api/services/brain/models.py +140 -0
  138. core/api/services/brain/owner_hint.py +211 -0
  139. core/api/services/brain/recap.py +307 -0
  140. core/api/services/brain/rules/__init__.py +65 -0
  141. core/api/services/brain/rules/_signals.py +205 -0
  142. core/api/services/brain/rules/dr1_activity_without_status.py +108 -0
  143. core/api/services/brain/rules/dr2_decision_without_adr.py +110 -0
  144. core/api/services/brain/rules/dr3_stale_open_loop.py +127 -0
  145. core/api/services/brain/rules/dr4_docs_governance_drift.py +79 -0
  146. core/api/services/brain/rules/dr5_playbook_changed.py +99 -0
  147. core/api/services/brain/rules/dr6_external_update_unpropagated.py +100 -0
  148. core/api/services/brain/rules/dr7_claimed_decision_gap.py +123 -0
  149. core/api/services/brain/rules/dr8_direction_misalignment.py +230 -0
  150. core/api/services/brain/runs_reader.py +485 -0
  151. core/api/services/brain/scope.py +79 -0
  152. core/api/services/brain/sources/__init__.py +46 -0
  153. core/api/services/brain/sources/base.py +86 -0
  154. core/api/services/brain/sources/git_kg.py +393 -0
  155. core/api/services/brain/sources/handoffs.py +157 -0
  156. core/api/services/brain/sources/ingestor.py +130 -0
  157. core/api/services/brain/sources/learnings.py +121 -0
  158. core/api/services/brain/sources/pir_tasks.py +245 -0
  159. core/api/services/brain/watermarks.py +147 -0
  160. core/api/services/brain/ws_emitter.py +170 -0
  161. core/api/services/cc_tasks_reader.py +76 -0
  162. core/api/services/ci_service.py +263 -0
  163. core/api/services/claude_metrics.py +796 -0
  164. core/api/services/codex_metrics.py +364 -0
  165. core/api/services/conversation_reader.py +102 -0
  166. core/api/services/cost_service.py +243 -0
  167. core/api/services/crypto.py +147 -0
  168. core/api/services/docs_governance/__init__.py +1 -0
  169. core/api/services/docs_governance/confidence.py +230 -0
  170. core/api/services/docs_governance/config.py +87 -0
  171. core/api/services/docs_governance/enrichment.py +65 -0
  172. core/api/services/docs_governance/frontmatter_validator.py +83 -0
  173. core/api/services/docs_governance/hard_gates.py +221 -0
  174. core/api/services/docs_governance/triage_orchestrator.py +98 -0
  175. core/api/services/embedding_internal.py +395 -0
  176. core/api/services/embedding_service.py +832 -0
  177. core/api/services/event_dispatcher.py +167 -0
  178. core/api/services/events.py +70 -0
  179. core/api/services/git_ops.py +621 -0
  180. core/api/services/graph_cosmo_service.py +440 -0
  181. core/api/services/graph_ranker.py +306 -0
  182. core/api/services/graph_service.py +1589 -0
  183. core/api/services/inbox.py +800 -0
  184. core/api/services/inbox_digest.py +221 -0
  185. core/api/services/inbox_digest_deep_research.py +80 -0
  186. core/api/services/inbox_digest_jobs.py +595 -0
  187. core/api/services/inbox_gmail_sync.py +167 -0
  188. core/api/services/inbox_llm_classifier.py +906 -0
  189. core/api/services/inbox_source_identity.py +116 -0
  190. core/api/services/inbox_sources.py +456 -0
  191. core/api/services/inbox_taxonomy.py +195 -0
  192. core/api/services/inbox_tldr.py +1079 -0
  193. core/api/services/inbox_triage.py +899 -0
  194. core/api/services/ingest/__init__.py +13 -0
  195. core/api/services/ingest/api_key_auth.py +136 -0
  196. core/api/services/ingest/auto_approve.py +120 -0
  197. core/api/services/ingest/classifier.py +138 -0
  198. core/api/services/ingest/confidence.py +173 -0
  199. core/api/services/ingest/dispatch.py +88 -0
  200. core/api/services/ingest/embedding_router.py +272 -0
  201. core/api/services/ingest/events.py +33 -0
  202. core/api/services/ingest/ignore_patterns.py +79 -0
  203. core/api/services/ingest/image_probe.py +218 -0
  204. core/api/services/ingest/ingress.py +263 -0
  205. core/api/services/ingest/insert_saga.py +793 -0
  206. core/api/services/ingest/llm/__init__.py +13 -0
  207. core/api/services/ingest/llm/anthropic_haiku.py +23 -0
  208. core/api/services/ingest/llm/base.py +59 -0
  209. core/api/services/ingest/llm/byok_provider.py +130 -0
  210. core/api/services/ingest/llm/classification_context.py +301 -0
  211. core/api/services/ingest/llm/config_store.py +246 -0
  212. core/api/services/ingest/llm/factory.py +24 -0
  213. core/api/services/ingest/llm/kg_enricher.py +306 -0
  214. core/api/services/ingest/llm/local_gateway.py +821 -0
  215. core/api/services/ingest/llm/local_vllm.py +23 -0
  216. core/api/services/ingest/llm/openai_nano.py +349 -0
  217. core/api/services/ingest/lock_advisory.py +57 -0
  218. core/api/services/ingest/parser_router.py +1756 -0
  219. core/api/services/ingest/parsers/__init__.py +1 -0
  220. core/api/services/ingest/parsers/docling_parser.py +142 -0
  221. core/api/services/ingest/parsers/docparse_gateway.py +178 -0
  222. core/api/services/ingest/parsers/docx_parser.py +127 -0
  223. core/api/services/ingest/parsers/folder_unpacker.py +85 -0
  224. core/api/services/ingest/parsers/gateway_aux.py +147 -0
  225. core/api/services/ingest/parsers/image_parser.py +251 -0
  226. core/api/services/ingest/parsers/internal_markdown.py +89 -0
  227. core/api/services/ingest/parsers/ocr_gateway.py +117 -0
  228. core/api/services/ingest/parsers/ocr_pdf_parser.py +112 -0
  229. core/api/services/ingest/parsers/pdf_types.py +13 -0
  230. core/api/services/ingest/parsers/transcript_parser.py +445 -0
  231. core/api/services/ingest/parsers/vision_gateway.py +186 -0
  232. core/api/services/ingest/parsers/xlsx_parser.py +91 -0
  233. core/api/services/ingest/parsers/zip_unpacker.py +126 -0
  234. core/api/services/ingest/preflight.py +393 -0
  235. core/api/services/ingest/retry_voyage.py +88 -0
  236. core/api/services/ingest/routing_policy.py +307 -0
  237. core/api/services/ingest/serializers/__init__.py +1 -0
  238. core/api/services/ingest/serializers/xlsx_to_markdown.py +80 -0
  239. core/api/services/ingest/skip_log.py +74 -0
  240. core/api/services/ingest/watcher.py +637 -0
  241. core/api/services/kg/__init__.py +0 -0
  242. core/api/services/kg/audit.py +49 -0
  243. core/api/services/kg/hybrid_search.py +691 -0
  244. core/api/services/kg/lens.py +339 -0
  245. core/api/services/kg/pr_impact.py +770 -0
  246. core/api/services/kg/queries.py +152 -0
  247. core/api/services/kg/ranking.py +89 -0
  248. core/api/services/kg/rrf.py +143 -0
  249. core/api/services/kg_watcher_control.py +161 -0
  250. core/api/services/local_llm/__init__.py +19 -0
  251. core/api/services/local_llm/async_client.py +385 -0
  252. core/api/services/local_llm/client.py +173 -0
  253. core/api/services/local_llm/url_validator.py +44 -0
  254. core/api/services/metrics_collector.py +646 -0
  255. core/api/services/metrics_providers.py +65 -0
  256. core/api/services/model_registry.py +266 -0
  257. core/api/services/model_router.py +137 -0
  258. core/api/services/n8n_client.py +77 -0
  259. core/api/services/newsletter_llm_gateway.py +66 -0
  260. core/api/services/notification_service.py +134 -0
  261. core/api/services/openai_responses.py +55 -0
  262. core/api/services/opencode_metrics.py +375 -0
  263. core/api/services/opencode_sessions.py +173 -0
  264. core/api/services/pii_redactor.py +138 -0
  265. core/api/services/pr_impact_pipeline/__init__.py +21 -0
  266. core/api/services/pr_impact_pipeline/differ.py +421 -0
  267. core/api/services/pr_impact_pipeline/dispatcher.py +415 -0
  268. core/api/services/pr_impact_pipeline/gc.py +93 -0
  269. core/api/services/pr_impact_pipeline/languages.py +192 -0
  270. core/api/services/pr_impact_pipeline/parser.py +178 -0
  271. core/api/services/pr_impact_pipeline/writer.py +394 -0
  272. core/api/services/pr_service.py +1393 -0
  273. core/api/services/project_paths.py +70 -0
  274. core/api/services/project_status_updates.py +265 -0
  275. core/api/services/providers.py +276 -0
  276. core/api/services/push_service.py +170 -0
  277. core/api/services/reminder_service.py +89 -0
  278. core/api/services/runas.py +41 -0
  279. core/api/services/salience_service.py +69 -0
  280. core/api/services/security_collector.py +281 -0
  281. core/api/services/session_catalog.py +385 -0
  282. core/api/services/session_metrics_service.py +301 -0
  283. core/api/services/session_ops.py +272 -0
  284. core/api/services/session_state.py +173 -0
  285. core/api/services/share_links.py +222 -0
  286. core/api/services/task_transitions.py +146 -0
  287. core/api/services/terminal_metrics.py +462 -0
  288. core/api/services/terminal_metrics_dump.py +203 -0
  289. core/api/services/tmux.py +1205 -0
  290. core/api/services/webhook_service.py +422 -0
  291. core/api/services/workspace_sync.py +164 -0
  292. core/api/templates/__init__.py +1 -0
  293. core/api/templates/markdown_share.py +164 -0
  294. core/api/terminal.py +1031 -0
  295. core/api/tests/__init__.py +0 -0
  296. core/api/tests/test_agent_facing_auth_dependencies.py +132 -0
  297. core/api/tests/test_audit_permissions.py +133 -0
  298. core/api/tests/test_backfill_session_conversations.py +90 -0
  299. core/api/tests/test_backfill_working_seconds_msg.py +129 -0
  300. core/api/tests/test_claude_metrics.py +326 -0
  301. core/api/tests/test_codex_metrics.py +189 -0
  302. core/api/tests/test_finder_paths.py +74 -0
  303. core/api/tests/test_git_ops_merge.py +155 -0
  304. core/api/tests/test_learnings_check_search.py +81 -0
  305. core/api/tests/test_metrics_providers.py +133 -0
  306. core/api/tests/test_migration_087.py +164 -0
  307. core/api/tests/test_migration_088.py +94 -0
  308. core/api/tests/test_migration_089.py +116 -0
  309. core/api/tests/test_openai_responses.py +24 -0
  310. core/api/tests/test_opencode_metrics.py +740 -0
  311. core/api/tests/test_opencode_sessions.py +321 -0
  312. core/api/tests/test_pr_workflow_e2e.py +457 -0
  313. core/api/tests/test_projects_handoffs.py +31 -0
  314. core/api/tests/test_providers.py +138 -0
  315. core/api/tests/test_safety_bridge.py +347 -0
  316. core/api/tests/test_session_catalog.py +142 -0
  317. core/api/tests/test_session_conversations.py +512 -0
  318. core/api/tests/test_session_metrics_service.py +270 -0
  319. core/api/tests/test_session_resume_paths.py +548 -0
  320. core/api/tests/test_session_theme_mode_migration.py +56 -0
  321. core/api/tests/test_sessions_rbac.py +131 -0
  322. core/api/tests/test_share_edit.py +398 -0
  323. core/api/tests/test_share_repo.py +200 -0
  324. core/api/tests/test_terminal_session_manager.py +98 -0
  325. core/api/tests/test_terminal_upload.py +34 -0
  326. core/api/tests/test_tmux.py +272 -0
  327. core/api/tests/test_workspace_sync.py +186 -0
  328. core/api/tests/test_ws_ticket_in_memory.py +73 -0
  329. core/api/use_cases/__init__.py +11 -0
  330. core/api/use_cases/_context.py +89 -0
  331. core/api/use_cases/_errors.py +62 -0
  332. core/api/use_cases/_roles.py +16 -0
  333. core/api/use_cases/audit.py +171 -0
  334. core/api/use_cases/brain.py +1232 -0
  335. core/api/use_cases/costs.py +249 -0
  336. core/api/use_cases/graph.py +1153 -0
  337. core/api/use_cases/handoffs.py +506 -0
  338. core/api/use_cases/ingest_triage.py +1229 -0
  339. core/api/use_cases/learnings.py +538 -0
  340. core/api/use_cases/projects.py +705 -0
  341. core/api/use_cases/pull_requests.py +415 -0
  342. core/api/use_cases/search.py +926 -0
  343. core/api/use_cases/tasks.py +1495 -0
  344. core/api/visibility.py +141 -0
  345. core/cli/__init__.py +5 -0
  346. core/cli/_index_source.py +632 -0
  347. core/cli/_runtime_ctx.py +160 -0
  348. core/cli/_transmute.py +241 -0
  349. core/cli/marvis_doctor.py +704 -0
  350. core/cli/marvis_feedback.py +396 -0
  351. core/cli/marvis_governance.py +315 -0
  352. core/cli/marvis_hooks.py +515 -0
  353. core/cli/marvis_init.py +757 -0
  354. core/cli/marvis_mcp.py +401 -0
  355. core/cli/marvis_runtime.py +855 -0
  356. core/cli/marvis_telemetry.py +228 -0
  357. core/scripts/_drift_check.py +716 -0
  358. core/scripts/_frontmatter.py +66 -0
  359. core/scripts/_graph_writer.py +189 -0
  360. core/scripts/ast_parser.py +1553 -0
  361. core/scripts/install_hooks/__init__.py +1 -0
  362. core/scripts/install_hooks/_config.sh +109 -0
  363. core/scripts/install_hooks/block-dangerous-bash.sh +23 -0
  364. core/scripts/install_hooks/block-db-direct-write.sh +23 -0
  365. core/scripts/install_hooks/block-push-no-task.sh +23 -0
  366. core/scripts/install_hooks/block-staging-to-prod.sh +23 -0
  367. core/scripts/install_hooks/block-subtree-push.sh +23 -0
  368. core/scripts/install_hooks/config.json +53 -0
  369. core/scripts/install_hooks/enforce-no-merge-main.sh +23 -0
  370. core/scripts/install_hooks/enforce-worktree.sh +23 -0
  371. core/scripts/install_hooks/quality-gate.sh +170 -0
  372. core/scripts/install_hooks/safety_bridge.py +968 -0
  373. core/scripts/install_hooks/secret-scan.sh +23 -0
  374. core/scripts/migrate_spike_node_ids.py +122 -0
  375. core/scripts/populate_artifacts.py +2198 -0
  376. core/scripts/populate_cross_project.py +2457 -0
  377. core/scripts/populate_inbox_nodes.py +357 -0
  378. core/scripts/populate_pr_impact.py +267 -0
  379. core/scripts/populate_project_nodes.py +603 -0
  380. core/scripts/populate_touch_counter.py +337 -0
  381. core/scripts/reparse_failed.py +57 -0
  382. core/scripts/safety_bridge.py +968 -0
  383. core/telemetry/__init__.py +9 -0
  384. core/telemetry/client.py +405 -0
  385. core/telemetry/schema.py +122 -0
  386. core/wizard/__init__.py +65 -0
  387. core/wizard/byok_vault.py +147 -0
  388. core/wizard/defaults.py +58 -0
  389. core/wizard/state.py +117 -0
  390. core/wizard/steps.py +70 -0
  391. core/wizard/validation.py +136 -0
  392. marvisx_cli-0.1.0.dist-info/METADATA +201 -0
  393. marvisx_cli-0.1.0.dist-info/RECORD +587 -0
  394. marvisx_cli-0.1.0.dist-info/WHEEL +5 -0
  395. marvisx_cli-0.1.0.dist-info/entry_points.txt +3 -0
  396. marvisx_cli-0.1.0.dist-info/licenses/LICENSE +98 -0
  397. marvisx_cli-0.1.0.dist-info/top_level.txt +3 -0
  398. migrations/001_initial.sql +33 -0
  399. migrations/002_tasks.sql +30 -0
  400. migrations/003_session_management.sql +7 -0
  401. migrations/004_projects_comments.sql +65 -0
  402. migrations/005_session_intelligence.sql +15 -0
  403. migrations/006_settings.sql +12 -0
  404. migrations/007_task_scoring.sql +12 -0
  405. migrations/008_cost_tracking.sql +31 -0
  406. migrations/009_session_card_metrics.sql +3 -0
  407. migrations/010_monitoring.sql +55 -0
  408. migrations/012_agent_api.sql +21 -0
  409. migrations/013_session_complete.sql +8 -0
  410. migrations/015_pull_requests.sql +43 -0
  411. migrations/015_pull_requests_down.sql +5 -0
  412. migrations/016_users_raci.sql +116 -0
  413. migrations/017_task_cost_entries.sql +87 -0
  414. migrations/018_agents.sql +73 -0
  415. migrations/018_agents_down.sql +13 -0
  416. migrations/019_review_feedback.sql +18 -0
  417. migrations/020_pr_commit_sha.sql +4 -0
  418. migrations/021_webhook_events.sql +18 -0
  419. migrations/022_devx_agent_managed.sql +11 -0
  420. migrations/022_devx_agent_managed_down.sql +6 -0
  421. migrations/023_devx_p1_gate.sql +7 -0
  422. migrations/023_devx_p1_gate_down.sql +3 -0
  423. migrations/024_chat_messages.sql +16 -0
  424. migrations/024_pr_conversation_id.sql +8 -0
  425. migrations/024_task_indexes.sql +21 -0
  426. migrations/024_task_indexes_down.sql +7 -0
  427. migrations/025_audit_log.sql +17 -0
  428. migrations/026_agent_tokens.sql +20 -0
  429. migrations/027_teams_auth_phase_b.sql +35 -0
  430. migrations/028_learnings.sql +23 -0
  431. migrations/029_team_roles.sql +14 -0
  432. migrations/030_finder_pins.sql +10 -0
  433. migrations/031_pr_deploy_status.sql +9 -0
  434. migrations/032_task_reminders.sql +7 -0
  435. migrations/033_events_retry_count.sql +6 -0
  436. migrations/033_session_owner.sql +9 -0
  437. migrations/034_notifications.sql +38 -0
  438. migrations/035_shared_links.sql +15 -0
  439. migrations/036_session_index_upgrade.sql +29 -0
  440. migrations/037_pr_approval.sql +15 -0
  441. migrations/038_pr_submitted_by.sql +6 -0
  442. migrations/039_push_subscriptions.sql +17 -0
  443. migrations/040_semantic_search.sql +16 -0
  444. migrations/041_workspaces.sql +63 -0
  445. migrations/042_oidc_providers.sql +24 -0
  446. migrations/043_ci_checks.sql +31 -0
  447. migrations/044_agent_metrics.sql +30 -0
  448. migrations/045_documents_doc_type.sql +5 -0
  449. migrations/046_salience.sql +13 -0
  450. migrations/047_seed_missing_agents.sql +6 -0
  451. migrations/048_fix_agent_paths_roles.sql +5 -0
  452. migrations/049_agent_role_and_learnings_schema.sql +3 -0
  453. migrations/050_session_provider.sql +2 -0
  454. migrations/051_session_launch_profile.sql +4 -0
  455. migrations/052_session_theme_mode.sql +2 -0
  456. migrations/052_task_kind.sql +4 -0
  457. migrations/053_inbox_items.sql +31 -0
  458. migrations/054_inbox_triage_contract.sql +30 -0
  459. migrations/055_inbox_topic_treatment.sql +12 -0
  460. migrations/056_inbox_treatment_read_save.sql +57 -0
  461. migrations/057_session_theme_mode_backfill.sql +4 -0
  462. migrations/058_inbox_item_status_lifecycle.sql +13 -0
  463. migrations/059_inbox_tldr_and_source_scores.sql +18 -0
  464. migrations/060_newsletter.sql +16 -0
  465. migrations/061_inbox_redesign.sql +69 -0
  466. migrations/062_fix_inbox_sources_backfill.sql +37 -0
  467. migrations/063_task_completion_mode.sql +23 -0
  468. migrations/064_judge_mode_setting.sql +4 -0
  469. migrations/065_knowledge_graph_spike.sql +40 -0
  470. migrations/066_digest_ranking_inputs.sql +10 -0
  471. migrations/066_kg_artifact_nodes.sql +129 -0
  472. migrations/067_inbox_digest_selections.sql +28 -0
  473. migrations/067_kg_temporal.sql +53 -0
  474. migrations/068_inbox_digest_app_settings.sql +9 -0
  475. migrations/068_kg_touch_counter.sql +52 -0
  476. migrations/069_kg_doc_types.sql +117 -0
  477. migrations/070_digest_ranking_inputs_recovery.sql +3 -0
  478. migrations/071_inbox_digest_selections_recovery.sql +3 -0
  479. migrations/072_inbox_digest_app_settings_recovery.sql +3 -0
  480. migrations/073_kg_cross_project.sql +216 -0
  481. migrations/073_kg_cross_project_down.sql +77 -0
  482. migrations/074_kg_infra_types.sql +208 -0
  483. migrations/074_kg_infra_types_down.sql +80 -0
  484. migrations/075_kg_file_state_recovery.sql +35 -0
  485. migrations/075_kg_file_state_recovery_down.sql +5 -0
  486. migrations/076_kg_watcher_state.sql +33 -0
  487. migrations/076_kg_watcher_state_down.sql +3 -0
  488. migrations/077_kg_doc_types_extend.sql +226 -0
  489. migrations/077_kg_doc_types_extend_down.sql +80 -0
  490. migrations/078_kg_fts5.sql +102 -0
  491. migrations/078_kg_fts5_down.sql +14 -0
  492. migrations/079_kg_missing_indexes.sql +31 -0
  493. migrations/079_kg_missing_indexes_down.sql +10 -0
  494. migrations/080_kg_fts5_extended.sql +232 -0
  495. migrations/080_kg_fts5_extended_down.sql +25 -0
  496. migrations/081_kg_lens_indexes.sql +9 -0
  497. migrations/081_kg_lens_indexes_down.sql +3 -0
  498. migrations/082_kg_pins.sql +26 -0
  499. migrations/082_kg_pins_down.sql +14 -0
  500. migrations/083_kg_graph_nodes_degree.sql +20 -0
  501. migrations/083_kg_graph_nodes_degree_down.sql +15 -0
  502. migrations/084_drop_legacy_scheduler_tables.sql +58 -0
  503. migrations/084_drop_legacy_scheduler_tables_down.sql +112 -0
  504. migrations/085_kg_edge_resolves_to.sql +142 -0
  505. migrations/085_kg_edge_resolves_to_down.sql +66 -0
  506. migrations/086_project_status_updates_feed.sql +20 -0
  507. migrations/086_project_status_updates_feed_down.sql +36 -0
  508. migrations/087_session_metrics_dual.sql +50 -0
  509. migrations/087_session_metrics_dual_down.sql +21 -0
  510. migrations/088_rename_context_pct_legacy.sql +23 -0
  511. migrations/088_rename_context_pct_legacy_down.sql +8 -0
  512. migrations/089_session_metrics_equivalent_cost.sql +26 -0
  513. migrations/089_session_metrics_equivalent_cost_down.sql +11 -0
  514. migrations/090_kg_inbox_node_type.sql +26 -0
  515. migrations/090_kg_inbox_node_type_down.sql +20 -0
  516. migrations/091_kg_inbox_node_type_check.sql +265 -0
  517. migrations/091_kg_inbox_node_type_check_down.sql +129 -0
  518. migrations/092_sessions_activity_state_ts.sql +29 -0
  519. migrations/092_sessions_activity_state_ts_down.sql +14 -0
  520. migrations/093_sessions_activity_state_column.sql +29 -0
  521. migrations/093_sessions_activity_state_column_down.sql +10 -0
  522. migrations/094_ingest_pending.sql +55 -0
  523. migrations/094_ingest_pending_down.sql +15 -0
  524. migrations/095_kg_intent_first.sql +77 -0
  525. migrations/095_kg_intent_first_down.sql +25 -0
  526. migrations/096_kg_xlsx_artifact_prefix.sql +17 -0
  527. migrations/096_kg_xlsx_artifact_prefix_down.sql +11 -0
  528. migrations/097_ingest_change_history.sql +37 -0
  529. migrations/097_ingest_change_history_down.sql +13 -0
  530. migrations/098_kg_node_type_business.sql +254 -0
  531. migrations/098_kg_node_type_business_down.sql +195 -0
  532. migrations/099_kg_edges_restore_weight.sql +58 -0
  533. migrations/099_kg_edges_restore_weight_down.sql +12 -0
  534. migrations/100_kg_enriched_at.sql +25 -0
  535. migrations/100_kg_enriched_at_down.sql +12 -0
  536. migrations/101_local_llm_shadow_comparisons.sql +66 -0
  537. migrations/101_local_llm_shadow_comparisons_down.sql +15 -0
  538. migrations/102_promote_llm_costs.sql +69 -0
  539. migrations/102_promote_llm_costs_down.sql +19 -0
  540. migrations/103_ingest_skipped_log.sql +46 -0
  541. migrations/103_ingest_skipped_log_down.sql +15 -0
  542. migrations/120_docs_governance.sql +50 -0
  543. migrations/120_docs_governance_down.sql +11 -0
  544. migrations/121_notification_event_fk_cleanup.sql +21 -0
  545. migrations/121_notification_event_fk_cleanup_down.sql +10 -0
  546. migrations/122_docs_drift_history.sql +34 -0
  547. migrations/122_docs_drift_history_down.sql +15 -0
  548. migrations/123_ingest_parser_waiting_status.sql +69 -0
  549. migrations/123_ingest_parser_waiting_status_down.sql +69 -0
  550. migrations/124_heypocket_recordings.sql +63 -0
  551. migrations/124_heypocket_recordings_down.sql +13 -0
  552. migrations/125_kg_node_type_record.sql +219 -0
  553. migrations/125_kg_node_type_record_down.sql +205 -0
  554. migrations/126_ingest_terminal_upload_source_kind.sql +69 -0
  555. migrations/126_ingest_terminal_upload_source_kind_down.sql +69 -0
  556. migrations/127_brain_v1_substrate.sql +200 -0
  557. migrations/127_brain_v1_substrate_down.sql +32 -0
  558. migrations/128_brain_drift_signals.sql +157 -0
  559. migrations/128_brain_drift_signals_down.sql +23 -0
  560. migrations/129_brain_memory_operations.sql +232 -0
  561. migrations/129_brain_memory_operations_down.sql +27 -0
  562. migrations/130_brain_findings.sql +258 -0
  563. migrations/130_brain_findings_down.sql +29 -0
  564. migrations/132_kg_pr_modifies.sql +242 -0
  565. migrations/132_kg_pr_modifies_down.sql +99 -0
  566. migrations/133_brain_v1_2_direction_schema.sql +476 -0
  567. migrations/133_brain_v1_2_direction_schema_down.sql +273 -0
  568. migrations/134_brain_journal_narrative_polished.sql +8 -0
  569. migrations/134_brain_journal_narrative_polished_down.sql +6 -0
  570. migrations/135_kg_edges_provider.sql +21 -0
  571. migrations/136_documents_fts.sql +56 -0
  572. migrations/137_promote_llm_costs.sql +59 -0
  573. migrations/137_promote_llm_costs_down.sql +19 -0
  574. migrations/138_ingest_api_keys.sql +39 -0
  575. migrations/138_ingest_api_keys_down.sql +8 -0
  576. migrations/139_ingest_pending_ingress.sql +91 -0
  577. migrations/139_ingest_pending_ingress_down.sql +73 -0
  578. migrations/140_ingest_idempotency_quota.sql +45 -0
  579. migrations/140_ingest_idempotency_quota_down.sql +9 -0
  580. migrations/141_ingest_pending_metadata.sql +16 -0
  581. migrations/141_ingest_pending_metadata_down.sql +7 -0
  582. migrations/142_llm_function_config.sql +36 -0
  583. migrations/142_llm_function_config_down.sql +8 -0
  584. migrations/143_kg_code_embeddings.sql +25 -0
  585. migrations/143_kg_code_embeddings_down.sql +5 -0
  586. migrations/__init__.py +4 -0
  587. projects/_template/project.yaml +46 -0
@@ -0,0 +1,599 @@
1
+ # v1.0.0 - 2026-05-27 - S1 F3.1c: brain reflection MCP tool group (use_cases-direct, no HTTP)
2
+ """Brain (reflection) MCP tools — port of the Node ``brain_*`` group, use_cases-direct.
3
+
4
+ Same TEMPLATE as ``tasks.py`` / ``learnings.py`` / ``graph.py``: the Node HTTP proxy
5
+ (``get`` / ``post`` / ``patchWithIdempotency`` -> ``:8100``) is replaced by an
6
+ in-process ``await brain_uc.<fn>(LOCAL_CTX, ...)``. Docstrings are copied VERBATIM
7
+ from ``core/mcp-pir/index.mjs`` (curated, carry the QUANDO USARLO / NON USARLO /
8
+ RESTITUISCE blocks).
9
+
10
+ Schema port (Zod -> Pydantic), per S1 F3:
11
+ * ``z.enum([...])`` -> ``Literal[...]``
12
+ * ``z.string().min(1).max(N)`` -> ``Annotated[str, Field(min_length=, max_length=)]``
13
+ * ``z.number().int().min().max()`` -> ``Annotated[int, Field(ge=, le=)]``
14
+ * ``z.number().min(0).max(1)`` -> ``Annotated[float, Field(ge=, le=)]``
15
+ * optional -> ``X | None = None`` (or ``= <default>``)
16
+ * ``z.array(z.enum([...]))`` -> ``list[Literal[...]] | None``
17
+
18
+ DEVIATION from the graph/tasks template (db ownership). The ``graph`` / ``tasks``
19
+ use_cases take a ``db`` connection the tool acquires via ``acquire_db()`` /
20
+ ``acquire_write_db()``. The ``brain`` use_cases do NOT: they own their connection
21
+ internally (the brain services — ``events_reader`` / ``drift_router`` /
22
+ ``memory_ops`` / ``findings_reader`` / ``runs_reader`` / ``journal`` /
23
+ ``capabilities`` / ``jobs`` — open their OWN ``acquire_db()`` / ``write_db()`` from
24
+ the process-level pool, see ``use_cases/brain.py`` RESPONSIBILITY 3). So the tool
25
+ body here calls ``await brain_uc.<fn>(LOCAL_CTX, ...)`` directly, with no
26
+ ``acquire_db()`` wrapper — wrapping one would acquire a pool connection the
27
+ use_case never touches. The process-level pool is initialised by the server
28
+ lifecycle (and by the ``mcp_db`` fixture in the smoke test).
29
+
30
+ Visibility: the MCP surface is local single-user. The brain use_cases resolve
31
+ project visibility through ``UserInfo.teams`` (which ``CallerContext`` drops), so
32
+ the local surface passes ``user=None`` = unrestricted (the services treat
33
+ ``user is None`` as "no visibility restriction", the same DECISION the other
34
+ groups take with ``visible_projects=None``). ``LOCAL_CTX`` is ``operator``, so the
35
+ operator+ lifecycle patches + ``brain_cycles_recompute`` pass ``require_role_ctx``.
36
+
37
+ Error mapping: brain raises ``BrainDetailError`` (a ``ServiceError`` subclass
38
+ carrying a structured ``brain_detail`` body for the HTTP contract) AND plain
39
+ ``NotFoundError`` / ``ServiceError``. The MCP surface ignores ``http_status`` /
40
+ ``brain_detail`` (HTTP is not its transport) and maps ``code`` + ``message`` via
41
+ ``raise_mcp_error`` — the same single ``except ServiceError`` the template uses
42
+ (``BrainDetailError`` IS a ``ServiceError``, so it is caught here too).
43
+
44
+ The ``*_apply`` tools (``brain_findings_apply`` / ``brain_memory_operations_apply``)
45
+ are GUIDANCE-only: the use_cases call ``get_apply_guidance`` which only READS the
46
+ precondition state and returns a next-action envelope — no write transaction, no
47
+ mutation. That invariant is preserved verbatim (no ``acquire_write_db``, nothing
48
+ to write).
49
+
50
+ fastapi-free invariant: ``use_cases.brain`` is fastapi-free at import time (it
51
+ imports every brain service FUNCTION-LOCAL inside each use_case), so it is a
52
+ module-top import here — no fastapi enters the MCP import path.
53
+
54
+ SKIPPED: none. All 18 ``brain_*`` Node tools map 1:1 to a ``use_cases.brain``
55
+ function. (The use_cases module also carries non-MCP admin fns —
56
+ ``discard_brain_run`` / ``promote_brain_run`` / ``reset_brain_watermarks`` /
57
+ ``get_counters`` / ``get_cycle_recap`` / ``list_journal`` admin/polish — but those
58
+ are NOT exposed as Node ``server.tool()`` entries, so they are out of scope for the
59
+ MCP surface parity.)
60
+ """
61
+ from __future__ import annotations
62
+
63
+ from typing import Annotated, Any, Literal
64
+
65
+ from pydantic import Field
66
+
67
+ from core.api.mcp._adapter import (
68
+ LOCAL_CTX,
69
+ dump,
70
+ raise_mcp_error,
71
+ )
72
+ from core.api.use_cases import brain as brain_uc
73
+ from core.api.use_cases._errors import ServiceError
74
+
75
+ # Zod enums -> Literals (mirror the Node tool signatures).
76
+ RunStatus = Literal["running", "succeeded", "partial", "failed", "superseded"]
77
+ RunTrigger = Literal["batch", "manual", "backfill"]
78
+ ScopeType = Literal["company", "program", "project"]
79
+ SeverityMin = Literal["low", "medium", "high", "critical"]
80
+ ConfidenceMin = Literal["low", "medium", "high"]
81
+ DriftState = Literal["open", "superseded", "resolved", "dismissed"]
82
+ DriftAxis = Literal["intent", "context", "both"]
83
+ DriftAction = Literal["dismiss", "acknowledge", "resolve", "reopen"]
84
+ MemoryOpApprovalState = Literal["approved", "dismissed", "rejected"]
85
+ FindingApprovalState = Literal["approved", "dismissed", "resolved"]
86
+ RecomputeSource = Literal["digest", "drift", "memory_ops", "learn"]
87
+
88
+
89
+ def register(mcp) -> None:
90
+ """Register the brain (reflection) tool group on the shared FastMCP instance."""
91
+
92
+ # -----------------------------------------------------------------------
93
+ # Sub-05 — runs (cycle envelope discovery primitive)
94
+ # -----------------------------------------------------------------------
95
+
96
+ @mcp.tool()
97
+ async def brain_runs(
98
+ cycle_key: str | None = None,
99
+ status: list[RunStatus] | None = None,
100
+ trigger: list[RunTrigger] | None = None,
101
+ include_superseded: bool = False,
102
+ cursor: str | None = None,
103
+ limit: Annotated[int, Field(ge=1, le=200)] = 50,
104
+ ) -> dict[str, Any]:
105
+ """List Brain cycle runs (envelope) per sub-05 §2.
106
+
107
+ QUANDO USARLO: verificare quale ciclo Brain ha pubblicato per ultimo, stato (running|succeeded|partial|failed|superseded), trigger (batch|manual|backfill) e contatori. Con `cycle_key='latest'` il server risolve a `MAX(cycle_key) WHERE status='succeeded' AND superseded_by_run_id IS NULL` — usalo come primitivo di discovery prima di brain_events/brain_journal/brain_drift/brain_memory_operations/brain_findings (cosi' non devi sincronizzare cycle_key tra chiamate). Default agent: `cycle_key='latest', status=['succeeded']`.
108
+ QUANDO NON USARLO: per leggere contenuto eventi/journal — quelle sono chiamate downstream. Non usarlo come 'health check' del Brain: lo stato `partial` e' normale (una source fallita non blocca le altre). Per il singolo run usa brain_runs_get.
109
+ RESTITUISCE: {items:[{run_id, cycle_key, status, trigger, event_count, partial_failures, duration_ms, started_at, finished_at}], next_cursor, cycle_key, total_returned}."""
110
+ try:
111
+ result = await brain_uc.list_brain_runs(
112
+ LOCAL_CTX,
113
+ cycle_key=cycle_key,
114
+ status=list(status) if status else None,
115
+ trigger=list(trigger) if trigger else None,
116
+ include_superseded=include_superseded,
117
+ cursor=cursor,
118
+ limit=limit,
119
+ user=None,
120
+ )
121
+ return dump(result)
122
+ except ServiceError as e:
123
+ raise_mcp_error(e)
124
+
125
+ @mcp.tool()
126
+ async def brain_runs_get(
127
+ run_id: Annotated[str, Field(min_length=1)],
128
+ ) -> dict[str, Any]:
129
+ """Fetch a single Brain cycle run by run_id.
130
+
131
+ QUANDO USARLO: hai un run_id (da brain_runs o WebSocket payload) e vuoi vedere envelope completo del ciclo (event_count, partial_failures, durata, scope, trigger). Utile dopo ricezione `marvisx:brain_cycle_changed` per ispezionare il run appena cambiato.
132
+ QUANDO NON USARLO: per cercare l'ultimo ciclo — usa brain_runs con `cycle_key='latest'`. Per il contenuto del journal/drift/memory_ops/findings — usa i tool dedicati.
133
+ RESTITUISCE: {run_id, workspace_id, cycle_key, status, trigger, started_at, finished_at, event_count, partial_failures, duration_ms}."""
134
+ try:
135
+ result = await brain_uc.get_brain_run(
136
+ LOCAL_CTX, run_id=run_id, user=None,
137
+ )
138
+ return dump(result)
139
+ except ServiceError as e:
140
+ raise_mcp_error(e)
141
+
142
+ # -----------------------------------------------------------------------
143
+ # Sub-01 D6 — events (raw digest evidence)
144
+ # -----------------------------------------------------------------------
145
+
146
+ @mcp.tool()
147
+ async def brain_events(
148
+ cycle_key: str | None = None,
149
+ run_id: str | None = None,
150
+ event_type: list[str] | None = None,
151
+ source_project: str | None = None,
152
+ cursor: str | None = None,
153
+ limit: Annotated[int, Field(ge=1, le=200)] = 50,
154
+ ) -> dict[str, Any]:
155
+ """List raw Brain digest events for a cycle (sub-01 D6).
156
+
157
+ QUANDO USARLO: ispezionare gli eventi grezzi (digest) di un ciclo — la base di evidenza che drift/memory-ops/findings citano. Quando una finding cita `digest_event:abc123` e vuoi vedere il contesto, brain_events e' la fonte di verita'. Cursor pagination stable `(observed_at DESC, event_id)`. Default: `cycle_key='latest', limit=50`.
158
+ QUANDO NON USARLO: come surface narrativo per umano — usa brain_journal (aggrega + materializza per scope). Non usarlo per 'voglio sapere se qualcosa e' cambiato' — quelli sono drift signals. Gli eventi sono fatti, non interpretazioni: non aggregarli come 'punteggio di salute progetto'.
159
+ RESTITUISCE: {items:[DigestEvent|DigestEventRedacted], next_cursor, cycle_key, run_id, redacted_count, total_returned}."""
160
+ try:
161
+ result = await brain_uc.list_events(
162
+ LOCAL_CTX,
163
+ cycle_key=cycle_key,
164
+ run_id=run_id,
165
+ event_type=list(event_type) if event_type else None,
166
+ source_project=source_project,
167
+ cursor=cursor,
168
+ limit=limit,
169
+ user=None,
170
+ )
171
+ return dump(result)
172
+ except ServiceError as e:
173
+ raise_mcp_error(e)
174
+
175
+ @mcp.tool()
176
+ async def brain_journal(
177
+ cycle_key: str | None = None,
178
+ run_id: str | None = None,
179
+ scope_type: ScopeType | None = None,
180
+ scope_key: str | None = None,
181
+ program_key: str | None = None,
182
+ limit: Annotated[int, Field(ge=1, le=200)] = 50,
183
+ ) -> dict[str, Any]:
184
+ """List Brain journal entries (narrative L2 layer) for a cycle.
185
+
186
+ QUANDO USARLO: ottenere il narrativo company/program/project di un ciclo — `{what_changed, decisions_observed, open_loops, notable_context, sources, tomorrow_watch}`. E' la vista materializzata sopra brain_events: leggibile da umano, con `sources[]` che linkano agli event_id originali. Default: `cycle_key='latest'`. Per il journal di un progetto: `scope_type=project, scope_key=marvisx`.
187
+ QUANDO NON USARLO: per cercare segnali di problema (usa brain_drift), proposte di azione (brain_memory_operations) o conclusioni approvabili (brain_findings). Il journal e' contesto: dice 'cosa e' successo', non 'cosa fare'. Non scriverlo a mano: e' generato dal cycle aggregator (sub-01 D2), agent-write su brain_journal_entries e' bloccato dal router.
188
+ RESTITUISCE: {items:[JournalEntry], cycle_key, run_id, total_returned}."""
189
+ try:
190
+ result = await brain_uc.list_journal(
191
+ LOCAL_CTX,
192
+ cycle_key=cycle_key,
193
+ run_id=run_id,
194
+ scope_type=scope_type,
195
+ scope_key=scope_key,
196
+ program_key=program_key,
197
+ limit=limit,
198
+ user=None,
199
+ )
200
+ return dump(result)
201
+ except ServiceError as e:
202
+ raise_mcp_error(e)
203
+
204
+ # -----------------------------------------------------------------------
205
+ # Sub-02 — drift signals
206
+ # -----------------------------------------------------------------------
207
+
208
+ @mcp.tool()
209
+ async def brain_drift(
210
+ cycle_key: str | None = None,
211
+ run_id: str | None = None,
212
+ scope_type: ScopeType | None = None,
213
+ scope_key: str | None = None,
214
+ signal_type: list[str] | None = None,
215
+ knowledge_form: list[str] | None = None,
216
+ severity_min: SeverityMin = "low",
217
+ confidence_min: Annotated[float, Field(ge=0, le=1)] = 0,
218
+ state: list[DriftState] | None = None,
219
+ include_resolved: bool = False,
220
+ drift_axis: list[DriftAxis] | None = None,
221
+ rule_id: list[str] | None = None,
222
+ cursor: str | None = None,
223
+ limit: Annotated[int, Field(ge=1, le=200)] = 50,
224
+ ) -> dict[str, Any]:
225
+ """List Brain drift signals (sub-02 §11.2) — knowledge gaps between observed and expected.
226
+
227
+ QUANDO USARLO: trovare drift signals aperti (default `state=['open'], severity_min='low'`) per capire dove il sistema 'sta sterzando' rispetto agli ADR/spec/playbook. Filtri: signal_type, knowledge_form (adr|spec|playbook|tribal_memory|external_update|claimed_decision|unknown), severity_min, drift_axis (intent|context|both — CE4). Cursor pagination stable per `(severity DESC, detected_at DESC)`.
228
+ QUANDO NON USARLO: per leggere il narrativo del ciclo — usa brain_journal. Per le azioni proposte sulla memoria — usa brain_memory_operations. Drift signal != finding: signal e' fatto osservato, finding e' conclusione approvabile.
229
+ RESTITUISCE: {items:[DriftSignal|DriftSignalRedacted], cycle_key, run_id, redacted_count, total_returned, next_cursor}."""
230
+ try:
231
+ result = await brain_uc.list_drift(
232
+ LOCAL_CTX,
233
+ cycle_key=cycle_key,
234
+ run_id=run_id,
235
+ scope_type=scope_type,
236
+ scope_key=scope_key,
237
+ signal_type=list(signal_type) if signal_type else None,
238
+ knowledge_form=list(knowledge_form) if knowledge_form else None,
239
+ severity_min=severity_min,
240
+ confidence_min=confidence_min,
241
+ state=list(state) if state else None,
242
+ include_resolved=include_resolved,
243
+ drift_axis=list(drift_axis) if drift_axis else None,
244
+ rule_id=list(rule_id) if rule_id else None,
245
+ cursor=cursor,
246
+ limit=limit,
247
+ user=None,
248
+ )
249
+ return dump(result)
250
+ except ServiceError as e:
251
+ raise_mcp_error(e)
252
+
253
+ @mcp.tool()
254
+ async def brain_drift_get(
255
+ signal_id: Annotated[str, Field(min_length=1)],
256
+ ) -> dict[str, Any]:
257
+ """Fetch a single drift signal by signal_id.
258
+
259
+ QUANDO USARLO: una finding/handoff cita `signal:abc123` e vuoi vedere il contesto completo (observed_delta, expected_direction_ref, evidence_chain, classifier_version). Usa anche dopo PATCH per verificare il nuovo `state`.
260
+ QUANDO NON USARLO: per cercare per recurrence_key o pattern — usa brain_drift. Per cambiare stato — usa brain_drift_patch.
261
+ RESTITUISCE: DriftSignal completo (o DriftSignalRedacted se cross-scope con qualche progetto invisibile)."""
262
+ try:
263
+ result = await brain_uc.get_drift_signal(
264
+ LOCAL_CTX, signal_id=signal_id, user=None,
265
+ )
266
+ return dump(result)
267
+ except ServiceError as e:
268
+ raise_mcp_error(e)
269
+
270
+ @mcp.tool()
271
+ async def brain_drift_patch(
272
+ signal_id: Annotated[str, Field(min_length=1)],
273
+ action: DriftAction,
274
+ reason: Annotated[str, Field(max_length=500)] | None = None,
275
+ idempotency_key: str | None = None,
276
+ ) -> dict[str, Any]:
277
+ """Lifecycle action on a drift signal (dismiss / acknowledge / resolve / reopen).
278
+
279
+ QUANDO USARLO: operator gate — dopo aver letto il signal (brain_drift_get), decidere se 'dismiss' (rumore), 'acknowledge' (visto, non agire), 'resolve' (corretto upstream), 'reopen' (riaperto post-resolve). `reason` raccomandato per audit. Idempotency-Key obbligatoria per evitare double-write audit log.
280
+ QUANDO NON USARLO: NON usarlo per cambiare evidence o classification — quelle sono immutabili. NON usarlo per chiudere findings (usa brain_findings_patch). Drift signal e' osservazione, dismiss=non utile, resolve=corretto upstream — non e' la stessa cosa di 'fix' del codice.
281
+ RESTITUISCE: DriftSignal aggiornato con nuovo state + dismissed_by/dismissed_at o resolved_at."""
282
+ # The use_case owns its own write connection internally (drift_router); no
283
+ # acquire_write_db wrapper here. idempotency_key is a transport replay guard
284
+ # the use_case signature does not carry (HTTP header concern), so it is
285
+ # accepted on the surface for Node parity but not forwarded.
286
+ try:
287
+ result = await brain_uc.patch_drift_signal(
288
+ LOCAL_CTX,
289
+ signal_id=signal_id,
290
+ action=action,
291
+ reason=reason,
292
+ user=None,
293
+ )
294
+ return dump(result)
295
+ except ServiceError as e:
296
+ raise_mcp_error(e)
297
+
298
+ # -----------------------------------------------------------------------
299
+ # Sub-03 — memory operations
300
+ # -----------------------------------------------------------------------
301
+
302
+ @mcp.tool()
303
+ async def brain_memory_operations(
304
+ cycle_key: str | None = None,
305
+ run_id: str | None = None,
306
+ scope_type: ScopeType | None = None,
307
+ scope_key: str | None = None,
308
+ operation_type: list[str] | None = None,
309
+ approval_state: list[str] | None = None,
310
+ include_terminal: bool = False,
311
+ recurrence_min: Annotated[int, Field(ge=1)] = 1,
312
+ score_min: Annotated[float, Field(ge=0, le=1)] = 0,
313
+ cursor: str | None = None,
314
+ limit: Annotated[int, Field(ge=1, le=200)] = 50,
315
+ ) -> dict[str, Any]:
316
+ """List Brain memory operations (sub-03 §11.2) — proposte di azione sulla memoria.
317
+
318
+ QUANDO USARLO: trovare proposte pendenti (default `approval_state=['pending']`) di operation_type: REINFORCE, CONSOLIDATE, SUPERSEDE_CANDIDATE, PROVENANCE_HARDENING, ORPHAN_DETECTED, CONTRADICTION_DETECTED. Ogni op contiene `proposed_write.target_type` (task|learning|adr|guide|doc_patch|context_md_append|kg_edge_metric|none). Default agent: `cycle_key='latest', approval_state=['pending'], recurrence_min=1`.
319
+ QUANDO NON USARLO: per signal di drift — usa brain_drift. Per findings approvabili (output finale) — usa brain_findings. Per applicare una proposta: brain_memory_operations_apply ritorna GUIDANCE, NON scrive.
320
+ RESTITUISCE: {items:[MemoryOperation|MemoryOperationRedacted], cycle_key, run_id, redacted_count, total_returned, next_cursor}."""
321
+ try:
322
+ result = await brain_uc.list_memory_operations(
323
+ LOCAL_CTX,
324
+ cycle_key=cycle_key,
325
+ run_id=run_id,
326
+ scope_type=scope_type,
327
+ scope_key=scope_key,
328
+ operation_type=list(operation_type) if operation_type else None,
329
+ approval_state=list(approval_state) if approval_state else None,
330
+ include_terminal=include_terminal,
331
+ recurrence_min=recurrence_min,
332
+ score_min=score_min,
333
+ cursor=cursor,
334
+ limit=limit,
335
+ user=None,
336
+ )
337
+ return dump(result)
338
+ except ServiceError as e:
339
+ raise_mcp_error(e)
340
+
341
+ @mcp.tool()
342
+ async def brain_memory_operations_get(
343
+ operation_id: Annotated[str, Field(min_length=1)],
344
+ ) -> dict[str, Any]:
345
+ """Fetch a single memory operation by operation_id.
346
+
347
+ QUANDO USARLO: hai un operation_id (da brain_memory_operations) e vuoi vedere proposed_write completo + evidence chain + recurrence info. Necessario prima di chiamare brain_memory_operations_apply per leggere il guidance contract.
348
+ QUANDO NON USARLO: per cercare per recurrence_key — usa brain_memory_operations. Per applicare: vedi brain_memory_operations_apply (NO write).
349
+ RESTITUISCE: MemoryOperation completa (o MemoryOperationRedacted se cross-scope con progetto invisibile)."""
350
+ try:
351
+ result = await brain_uc.get_memory_operation(
352
+ LOCAL_CTX, operation_id=operation_id, user=None,
353
+ )
354
+ return dump(result)
355
+ except ServiceError as e:
356
+ raise_mcp_error(e)
357
+
358
+ @mcp.tool()
359
+ async def brain_memory_operations_patch(
360
+ operation_id: Annotated[str, Field(min_length=1)],
361
+ approval_state: MemoryOpApprovalState,
362
+ reason: Annotated[str, Field(max_length=500)] | None = None,
363
+ applied_artifact_ref: Annotated[str, Field(max_length=500)] | None = None,
364
+ idempotency_key: str | None = None,
365
+ ) -> dict[str, Any]:
366
+ """Lifecycle action on a memory operation (approve / dismiss / reject).
367
+
368
+ QUANDO USARLO: operator gate — approva (sblocca apply guidance), dismiss (non utile per ora), reject (proposta sbagliata). `applied_artifact_ref` opzionale dopo aver creato artifact via guidance. Idempotency-Key obbligatoria.
369
+ QUANDO NON USARLO: per applicare l'azione concreta — l'apply NON scrive, ritorna SOLO guidance. La scrittura effettiva e' agente-driven, MAI inline qui. Per bulk: brain_memory_operations_bulk_patch.
370
+ RESTITUISCE: MemoryOperation aggiornata con nuovo approval_state."""
371
+ # Node sends `approval_state` as the desired terminal state; the use_case
372
+ # takes it as `action` (the lifecycle verb). idempotency_key is the
373
+ # transport replay header; the use_case forwards it to the service.
374
+ try:
375
+ result = await brain_uc.patch_memory_operation(
376
+ LOCAL_CTX,
377
+ operation_id=operation_id,
378
+ action=approval_state,
379
+ reason=reason,
380
+ applied_artifact_ref=applied_artifact_ref,
381
+ idempotency_key=idempotency_key,
382
+ user=None,
383
+ )
384
+ return dump(result)
385
+ except ServiceError as e:
386
+ raise_mcp_error(e)
387
+
388
+ @mcp.tool()
389
+ async def brain_memory_operations_apply(
390
+ operation_id: Annotated[str, Field(min_length=1)],
391
+ ) -> dict[str, Any]:
392
+ """Get apply GUIDANCE for an approved memory operation (NO write — sub-03 §11.1).
393
+
394
+ QUANDO USARLO: dopo aver `approved` un'operazione (via brain_memory_operations_patch), per ottenere `next_action.tool` (es. mcp__pir__create_task) + args + `must_include_in_tags` (es. `brain_memory_op:abc123`). L'apply NON scrive l'artifact — ritorna istruzioni. L'agente esegue il `next_action.tool` con i tag richiesti per stabilire la chain di audit.
395
+ QUANDO NON USARLO: per scrivere direttamente l'artifact — questo endpoint ritorna SOLO guidance. Pattern: PATCH → apply (guidance) → call next_action.tool con must_include_in_tags. Non usarlo prima di approval_state='approved' (409 precondition).
396
+ RESTITUISCE: {operation_id, next_action:{tool, args, must_include_in_tags}, operation_summary}."""
397
+ # GUIDANCE-only: the use_case calls get_apply_guidance (READ-only precondition
398
+ # check + next-action envelope). It opens no write transaction and mutates
399
+ # nothing — preserved verbatim, no acquire_write_db.
400
+ try:
401
+ result = await brain_uc.apply_memory_operation(
402
+ LOCAL_CTX, operation_id=operation_id, user=None,
403
+ )
404
+ return dump(result)
405
+ except ServiceError as e:
406
+ raise_mcp_error(e)
407
+
408
+ # -----------------------------------------------------------------------
409
+ # Sub-04 — Learn findings
410
+ # -----------------------------------------------------------------------
411
+
412
+ @mcp.tool()
413
+ async def brain_findings(
414
+ cycle_key: str | None = None,
415
+ run_id: str | None = None,
416
+ scope_type: ScopeType | None = None,
417
+ scope_key: str | None = None,
418
+ finding_type: list[str] | None = None,
419
+ severity_min: SeverityMin = "low",
420
+ confidence_min: ConfidenceMin = "low",
421
+ approval_state: list[str] | None = None,
422
+ include_terminal: bool = False,
423
+ recurrence_min: Annotated[int, Field(ge=1)] = 1,
424
+ regression_only: bool = False,
425
+ applied: bool | None = None,
426
+ created_after: str | None = None,
427
+ owner_user_id: str | None = None,
428
+ cursor: str | None = None,
429
+ limit: Annotated[int, Field(ge=1, le=200)] = 50,
430
+ ) -> dict[str, Any]:
431
+ """List Brain Learn findings (sub-04 §11.2) — conclusioni approvabili dal ciclo.
432
+
433
+ QUANDO USARLO: trovare findings aperti (default `approval_state=['open']`) per drain del Triage Queue. Filtri: finding_type, severity_min, confidence_min (low|medium|high — TIER, non float), recurrence_min, regression_only, applied. Default agent: `cycle_key='latest', approval_state=['open'], severity_min='low'`. CE2 recency_factor disponibile read-time (se decay enabled in settings).
434
+ QUANDO NON USARLO: per drift signals — usa brain_drift. Per proposte di azione (intermedio) — usa brain_memory_operations. Per applicare: vedi brain_findings_apply (GUIDANCE-only). NEVER multiplica confidence × severity in score composito (F10/FR1 anti-pattern).
435
+ RESTITUISCE: {items:[Finding|FindingRedacted], cycle_key, run_id, redacted_count, redacted_evidence_count, total_returned, next_cursor}."""
436
+ try:
437
+ result = await brain_uc.list_findings(
438
+ LOCAL_CTX,
439
+ cycle_key=cycle_key,
440
+ run_id=run_id,
441
+ scope_type=scope_type,
442
+ scope_key=scope_key,
443
+ finding_type=list(finding_type) if finding_type else None,
444
+ severity_min=severity_min,
445
+ confidence_min=confidence_min,
446
+ approval_state=list(approval_state) if approval_state else None,
447
+ include_terminal=include_terminal,
448
+ recurrence_min=recurrence_min,
449
+ regression_only=regression_only,
450
+ applied=applied,
451
+ created_after=created_after,
452
+ owner_user_id=owner_user_id,
453
+ cursor=cursor,
454
+ limit=limit,
455
+ user=None,
456
+ )
457
+ return dump(result)
458
+ except ServiceError as e:
459
+ raise_mcp_error(e)
460
+
461
+ @mcp.tool()
462
+ async def brain_findings_get(
463
+ finding_id: Annotated[str, Field(min_length=1)],
464
+ ) -> dict[str, Any]:
465
+ """Fetch a single finding by finding_id.
466
+
467
+ QUANDO USARLO: hai un finding_id (da brain_findings o WS payload) e vuoi vedere proposta completa: title, summary, why_now, evidence, suggested_artifact, owner_hint, closure_condition (drift_signal_clears|memory_op_applied|artifact_exists|manual_attest), regression_of_finding_id. Necessario prima di brain_findings_apply per leggere il guidance contract.
468
+ QUANDO NON USARLO: per ricerca semantica — usa brain_findings con filtri. Per modificare lo stato — usa brain_findings_patch.
469
+ RESTITUISCE: Finding completo (o FindingRedacted se cross-scope con progetto invisibile)."""
470
+ try:
471
+ result = await brain_uc.get_finding(
472
+ LOCAL_CTX, finding_id=finding_id, user=None,
473
+ )
474
+ return dump(result)
475
+ except ServiceError as e:
476
+ raise_mcp_error(e)
477
+
478
+ @mcp.tool()
479
+ async def brain_findings_patch(
480
+ finding_id: Annotated[str, Field(min_length=1)],
481
+ approval_state: FindingApprovalState,
482
+ reason: Annotated[str, Field(max_length=500)] | None = None,
483
+ applied_artifact_ref: Annotated[str, Field(max_length=500)] | None = None,
484
+ idempotency_key: str | None = None,
485
+ ) -> dict[str, Any]:
486
+ """Lifecycle action on a finding (approve / dismiss / resolve).
487
+
488
+ QUANDO USARLO: operator gate per gestire la coda 'Da decidere'. `approve` segna approval_state=approved MA NON crea artifact (sub-04 F1: GUIDANCE-only). `dismiss` = non utile. `resolve` = osservato comportamento atteso (richiede reason se closure_condition.kind=manual_attest). Idempotency-Key obbligatoria.
489
+ QUANDO NON USARLO: per creare artifact dal finding — pattern e' PATCH approve → brain_findings_apply (guidance) → call next_action.tool. Per bulk: brain_findings_bulk_patch.
490
+ RESTITUISCE: Finding aggiornato con nuovo approval_state + approved_by/approved_at o applied_artifact_ref."""
491
+ # Node sends `approval_state` (desired state); the use_case takes it as the
492
+ # lifecycle `action` verb. idempotency_key forwarded to the service.
493
+ try:
494
+ result = await brain_uc.patch_finding(
495
+ LOCAL_CTX,
496
+ finding_id=finding_id,
497
+ action=approval_state,
498
+ reason=reason,
499
+ applied_artifact_ref=applied_artifact_ref,
500
+ idempotency_key=idempotency_key,
501
+ user=None,
502
+ )
503
+ return dump(result)
504
+ except ServiceError as e:
505
+ raise_mcp_error(e)
506
+
507
+ @mcp.tool()
508
+ async def brain_findings_bulk_patch(
509
+ finding_ids: Annotated[list[str], Field(min_length=1, max_length=25)],
510
+ approval_state: FindingApprovalState,
511
+ reason: Annotated[str, Field(max_length=500)] | None = None,
512
+ idempotency_key: str | None = None,
513
+ ) -> dict[str, Any]:
514
+ """Bulk lifecycle patch on findings (cap 25).
515
+
516
+ QUANDO USARLO: drain massivo (es. dismiss una classe di rumore). Max 25 finding_id per request (oltre → 413). Conflict invariant: one bad transition fails the whole batch (409, NO partial commit).
517
+ QUANDO NON USARLO: per >25 — splitta. Per gestione individuale con motivazione — usa brain_findings_patch.
518
+ RESTITUISCE: {results:[{finding_id, status}], applied_count, skipped_count}."""
519
+ try:
520
+ result = await brain_uc.bulk_patch_findings(
521
+ LOCAL_CTX,
522
+ finding_ids=list(finding_ids),
523
+ action=approval_state,
524
+ reason=reason,
525
+ idempotency_key=idempotency_key,
526
+ user=None,
527
+ )
528
+ return dump(result)
529
+ except ServiceError as e:
530
+ raise_mcp_error(e)
531
+
532
+ @mcp.tool()
533
+ async def brain_findings_apply(
534
+ finding_id: Annotated[str, Field(min_length=1)],
535
+ ) -> dict[str, Any]:
536
+ """Get apply GUIDANCE for an approved finding (NO write — sub-04 F1).
537
+
538
+ QUANDO USARLO: dopo aver `approved` una finding, per ottenere `next_action.tool` (es. mcp__pir__create_task con title/description/tags pre-compilato) + `must_include_in_tags=brain_finding:{id}` per audit chain. L'apply NON scrive l'artifact — ritorna istruzioni. L'agente esegue il next_action.tool con i tag richiesti.
539
+ QUANDO NON USARLO: per scrivere artifact direttamente — pattern e' GUIDANCE-only. Non usarlo prima di approval_state='approved' (409). Non chiamarlo in loop 'ricomputa finche' la finding X appare': se non emerge, e' un problema del producer rule — file un learning via mcp__pir__create_learning.
540
+ RESTITUISCE: {operation_id (=finding_id), next_action:{tool, args, must_include_in_tags}, operation_summary}."""
541
+ # GUIDANCE-only: the use_case calls get_apply_guidance (READ-only precondition
542
+ # + next-action envelope). No write transaction, no mutation — preserved.
543
+ try:
544
+ result = await brain_uc.apply_finding(
545
+ LOCAL_CTX, finding_id=finding_id, user=None,
546
+ )
547
+ return dump(result)
548
+ except ServiceError as e:
549
+ raise_mcp_error(e)
550
+
551
+ # -----------------------------------------------------------------------
552
+ # Sub-05 — recompute + capabilities
553
+ # -----------------------------------------------------------------------
554
+
555
+ @mcp.tool()
556
+ async def brain_cycles_recompute(
557
+ cycle_key: Annotated[str, Field(min_length=1)],
558
+ idempotency_key: Annotated[str, Field(min_length=1)],
559
+ sources: list[RecomputeSource] | None = None,
560
+ force: bool = False,
561
+ dry_run: bool = False,
562
+ ) -> dict[str, Any]:
563
+ """Force manual recompute of a Brain cycle (operator+, sub-01 §6.D4).
564
+
565
+ QUANDO USARLO: dopo bug fix in un producer (collector/drift/memory_ops/learn), dopo backfill di dati upstream, o per debug `dry_run=true` (stima eventi senza scrivere). `sources=None` ricomputa tutto; `sources=['drift']` ricomputa solo drift (digest resta, findings citano i nuovi signal_id). Idempotency-Key obbligatoria. Concurrent calls sullo stesso ciclo serializzano via lease: il secondo caller ritorna 202 con il run_id dell'in-flight.
566
+ QUANDO NON USARLO: come scheduler — il batch giornaliero gira da solo dopo brain_cutoff_hour_utc. Per cicli >30 giorni vecchi: il server rifiuta con `cycle_too_old` (force=true sblocca, audit log la marca come override). Mai chiamarlo in loop 'ricomputa finche' X appare'.
567
+ RESTITUISCE: {status, cycle_key, run_id, event_count, journal_count, duration_ms, mode, dry_run}."""
568
+ # The use_case owns its own writer (jobs.recompute_brain_cycle) — no
569
+ # acquire_write_db here. `sources` is accepted on the surface for Node
570
+ # parity; the use_case signature recomputes the full cycle (the sub-phase
571
+ # selector lives below the use_case in jobs and is not threaded through
572
+ # recompute_cycle's args), so it is not forwarded.
573
+ try:
574
+ result = await brain_uc.recompute_cycle(
575
+ LOCAL_CTX,
576
+ cycle_key=cycle_key,
577
+ dry_run=dry_run,
578
+ force=force,
579
+ idempotency_key=idempotency_key,
580
+ triggered_by=LOCAL_CTX.username,
581
+ user=None,
582
+ )
583
+ return dump(result)
584
+ except ServiceError as e:
585
+ raise_mcp_error(e)
586
+
587
+ @mcp.tool()
588
+ async def brain_capabilities() -> dict[str, Any]:
589
+ """Discover Brain schema metadata (enums + glyphs + node kinds) — sub-05 OD-11.
590
+
591
+ QUANDO USARLO: cold-start dell'agente — leggere i Literal enum esposti dal Brain (event_types, signal_types, knowledge_forms, operation_types, finding_types, severities, confidence_tiers, drift_axes, approval_states, signal_states, run_statuses, closure_condition_kinds, knowledge_glyphs). Pattern equivalente di mcp__pir__graph_capabilities. Evita hardcoding constants. Schema_version aumenta quando i Literal cambiano.
592
+ QUANDO NON USARLO: per dati live del ciclo — usa brain_runs/brain_events/etc. Non e' un health check.
593
+ RESTITUISCE: {schema_version, event_types[], source_systems[], signal_types[], knowledge_forms[], operation_types[], finding_types[], severities[], confidence_tiers[], drift_axes[], approval_states[], finding_approval_states[], signal_states[], run_statuses[], run_triggers[], scope_types[], suggested_artifacts[], closure_condition_kinds[], knowledge_glyphs:{form: glyph}}."""
594
+ # Deterministic; no DB. The use_case takes only ctx.
595
+ try:
596
+ result = await brain_uc.get_brain_capabilities(LOCAL_CTX)
597
+ return dump(result)
598
+ except ServiceError as e:
599
+ raise_mcp_error(e)