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,326 @@
1
+ # v1.0.0 - 2026-05-27 - S1 F3.1d: ingest-triage MCP tool group (use_cases-direct, no HTTP)
2
+ """Ingest-triage MCP tools — port of the Node ``*_ingest*`` group, use_cases-direct.
3
+
4
+ Same TEMPLATE as ``tasks.py`` / ``graph.py``: the Node HTTP proxy
5
+ (``get``/``post``/``patch`` -> ``:8100``) is replaced by an in-process
6
+ ``await ingest_uc.<fn>(LOCAL_CTX, db, ...)`` against the read/write pool the
7
+ tool acquires via ``acquire_db()`` / ``acquire_write_db()``. Docstrings are copied
8
+ VERBATIM from ``core/mcp-pir/index.mjs`` (curated QUANDO USARLO / NON USARLO /
9
+ RESTITUISCE blocks).
10
+
11
+ Schema port (Zod -> Pydantic), per S1 F3:
12
+ * ``z.enum([...])`` -> ``Literal[...]``
13
+ * ``z.string().uuid()`` -> ``Annotated[str, Field(pattern=_UUID_PATTERN)]``
14
+ * ``z.string().regex(slug)`` -> ``Annotated[str, Field(pattern=_PROJECT_SLUG_PATTERN)]``
15
+ * ``z.number().int().min().max()`` -> ``Annotated[int, Field(ge=, le=)]``
16
+ * ``z.record(z.string(), z.string())`` -> ``dict[str, str] | None``
17
+ * optional -> ``X | None = None`` (or ``= <default>``)
18
+
19
+ Visibility: the MCP surface is local single-user (no ``UserInfo.teams``), so every
20
+ tool passes ``visible_projects=None`` = unrestricted (the same DECISION 1 the
21
+ use_cases / the other groups take). ``LOCAL_CTX`` is ``operator`` so the
22
+ operator+ ``require_role_ctx`` gates inside the use_cases pass.
23
+
24
+ FIRE-AND-FORGET SCHEDULING is NOT replicated here (DECISION). The use_cases perform
25
+ the durable DB transition (the source of truth) and return; the HTTP adapter then
26
+ schedules ``parse_pending`` / ``execute_saga`` (``asyncio.create_task``) and emits
27
+ the ``broadcast_ingest_changed`` SSE notification. Both the parser/saga workers and
28
+ the SSE channel are server-lifecycle / transport side-channels — the SAME
29
+ per-surface trade-off the tasks template documents for ``mcp_schedule_embed`` (the
30
+ auto-embed worker, S1 F4). Importing ``parse_pending`` / ``execute_saga`` /
31
+ ``broadcast_ingest_changed`` here would ALSO drag fastapi into the MCP import path
32
+ (they transitively import a fastapi module — the use_case keeps them FUNCTION-LOCAL
33
+ for exactly this reason), so they are NOT imported. Net: ``approve`` /
34
+ ``reject`` / ``classify`` / ``reparse`` commit the row transition; the actual
35
+ parse/saga execution is driven by the in-process worker wiring the server lifecycle
36
+ owns (parity with the auto-embed F4 no-op).
37
+
38
+ PATCH transport-boundary replication. ``patch_pending`` (the use_case) owns the
39
+ sha-collision check, containment, atomic move, and the row + change-history writes;
40
+ the HTTP adapter resolves the row + the destination input-root + the slug/path
41
+ guards BEFORE calling it. The MCP ``patch_ingest_pending`` surface takes only
42
+ ``id`` + optional ``project_slug`` (Node parity), so this module replicates the
43
+ ADAPTER pre-work locally, fastapi-free: load the row, no-op short-circuit on an
44
+ unchanged/empty slug, slug-regex validate, and resolve the destination
45
+ ``input_root`` against ``PROJECTS_ROOT`` (the same constant the router uses;
46
+ test-overridable via ``monkeypatch.setattr(ingest_tools, "PROJECTS_ROOT", ...)``).
47
+ The bespoke ``HTTPException`` dict bodies the router raises are a transport contract
48
+ the MCP surface does not carry — slug/path failures raise ``ValidationError`` /
49
+ ``NotFoundError`` (mapped via ``raise_mcp_error``).
50
+
51
+ SKIPPED (no clean fastapi-free use_case):
52
+ * ``upload_ingest`` — there is NO ``upload`` function in
53
+ ``use_cases.ingest_triage``. The upload pipeline (MIME validation +
54
+ ``_save_upload_file`` + ``dispatch_files_batched``) lives ENTIRELY in
55
+ ``routers/ingest_triage.py`` + the ingest services, and ``dispatch_files_batched``
56
+ transitively imports fastapi. Porting would mean re-implementing the
57
+ router/service multipart+dispatch path, not calling a use_case — out of scope
58
+ for the use_cases-direct port (S1 F3 SKIP rule), so it is skipped, not faked.
59
+ * ``write_haiku_frontmatter`` — the HTTP endpoint is a router-only 501 placeholder
60
+ (``write_frontmatter_placeholder``); there is no ``use_cases.ingest_triage``
61
+ function and nothing to call. The Node tool documents "Pre-E5: 501". Skipped
62
+ until E5 extracts a use_case.
63
+ """
64
+ from __future__ import annotations
65
+
66
+ import re
67
+ from pathlib import Path
68
+ from typing import Annotated, Any, Literal
69
+
70
+ from pydantic import Field
71
+
72
+ from core.api.mcp._adapter import (
73
+ LOCAL_CTX,
74
+ acquire_db,
75
+ acquire_write_db,
76
+ dump,
77
+ raise_mcp_error,
78
+ )
79
+ from core.api.use_cases import ingest_triage as ingest_uc
80
+ from core.api.use_cases._errors import NotFoundError, ServiceError, ValidationError
81
+
82
+ # Zod enums -> Literals (mirror the Node tool signatures).
83
+ IngestStatus = Literal[
84
+ "queued", "parsing", "classified", "awaiting_triage", "approved",
85
+ "inserted", "done", "parse_error", "rejected", "all",
86
+ ]
87
+
88
+ # Node Zod patterns mirrored as Pydantic Field patterns.
89
+ _UUID_PATTERN = (
90
+ r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-"
91
+ r"[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
92
+ )
93
+ # index.mjs _PROJECT_SLUG_RE = /^[a-z0-9][a-z0-9_&-]+$/
94
+ _PROJECT_SLUG_PATTERN = r"^[a-z0-9][a-z0-9_&-]+$"
95
+ _PROJECT_SLUG_RE = re.compile(_PROJECT_SLUG_PATTERN)
96
+
97
+ # The projects metadata root the patch file-move resolves against. Same constant
98
+ # the router uses; module-level so a test can override it with
99
+ # ``monkeypatch.setattr(ingest_tools, "PROJECTS_ROOT", tmp_path)``.
100
+ PROJECTS_ROOT = Path("/data/projects").resolve()
101
+
102
+
103
+ def _project_input_root(project_slug: str) -> Path:
104
+ """Resolve ``PROJECTS_ROOT/<slug>/input`` with the router's containment guards.
105
+
106
+ fastapi-free counterpart of the router's ``_project_input_root``: the bespoke
107
+ ``HTTPException`` dict bodies (``INVALID_SLUG_FORMAT`` / ``PROJECT_NOT_FOUND`` /
108
+ ``SLUG_PATH_TRAVERSAL``) are a transport contract the MCP surface does not carry,
109
+ so the same conditions raise ``ValidationError`` / ``NotFoundError`` (mapped via
110
+ ``raise_mcp_error``).
111
+ """
112
+ if not _PROJECT_SLUG_RE.fullmatch(project_slug):
113
+ raise ValidationError(
114
+ code="invalid_slug_format",
115
+ message=f"Project slug '{project_slug}' must match [a-z0-9][a-z0-9_&-]+",
116
+ )
117
+ project_root = (PROJECTS_ROOT / project_slug).resolve()
118
+ if not project_root.is_relative_to(PROJECTS_ROOT):
119
+ raise ValidationError(
120
+ code="slug_path_traversal",
121
+ message=f"Resolved path for '{project_slug}' escapes projects root",
122
+ )
123
+ if not project_root.is_dir():
124
+ raise NotFoundError(
125
+ code="project_not_found",
126
+ message=f"Project '{project_slug}' not found",
127
+ )
128
+ input_root = (project_root / "input").resolve()
129
+ if not input_root.is_relative_to(project_root):
130
+ raise ValidationError(
131
+ code="slug_path_traversal",
132
+ message="Input directory escapes project root",
133
+ )
134
+ return input_root
135
+
136
+
137
+ def register(mcp) -> None:
138
+ """Register the ingest-triage tool group on the shared FastMCP instance."""
139
+
140
+ @mcp.tool()
141
+ async def list_ingest_pending(
142
+ status: IngestStatus | None = None,
143
+ project: Annotated[str, Field(pattern=_PROJECT_SLUG_PATTERN)] | None = None,
144
+ limit: Annotated[int, Field(ge=1, le=250)] = 50,
145
+ ) -> list[dict[str, Any]]:
146
+ """List ingest_pending rows con filtri status/project/limit. Phase 1.5 capability via MCP per agent.
147
+
148
+ QUANDO USARLO: serve la coda triage (queued/parsing/awaiting_triage/parse_error/...) per un progetto o globale (visibility-filtered).
149
+ QUANDO NON USARLO: NOT per dettaglio singolo row -> nessun get_ingest_pending dedicato; usa la list filtrata. NOT per cambiare stato -> approve/reject/patch.
150
+ RESTITUISCE: list of IngestPendingItem (id, file_path, project_slug, status, classification, target_folder, target_filename, ...)."""
151
+ # Node maps status="all" -> no status filter (drops the param). Local sees
152
+ # all projects (visible_projects=None), so project_filter_visible stays True.
153
+ try:
154
+ async with acquire_db() as db:
155
+ result = await ingest_uc.list_pending(
156
+ LOCAL_CTX,
157
+ db,
158
+ status=None if status == "all" else status,
159
+ project_slug=project,
160
+ limit=limit,
161
+ visible_projects=None,
162
+ )
163
+ return dump(result)
164
+ except ServiceError as e:
165
+ raise_mcp_error(e)
166
+
167
+ @mcp.tool()
168
+ async def approve_ingest_pending(
169
+ id: Annotated[str, Field(pattern=_UUID_PATTERN)],
170
+ classification_override: dict[str, str] | None = None,
171
+ ) -> dict[str, Any]:
172
+ """Approve ingest_pending row → triggers saga move da input/ a target_folder + KG insert. Optional classification_override (logged decision_source=agent_override; backend support post-E5).
173
+
174
+ QUANDO USARLO: row in awaiting_triage con target_folder/target_filename validi → approval umano via agent.
175
+ QUANDO NON USARLO: NOT su row in stato diverso da awaiting_triage (409). NOT per cambiare project_slug -> usa patch_ingest_pending.
176
+ RESTITUISCE: {id, status:approved} dopo enqueue saga."""
177
+ # The use_case returns (response, project_slug); the second element feeds the
178
+ # HTTP adapter's SSE broadcast (a transport side-channel not replicated here,
179
+ # see module docstring). classification_override is accepted for Node parity
180
+ # but not forwarded — the use_case signature does not carry it (backend
181
+ # support is post-E5). The durable status='approved' transition commits here;
182
+ # the saga is driven by the server-lifecycle worker.
183
+ try:
184
+ async with acquire_write_db() as db:
185
+ response, _project_slug = await ingest_uc.approve(
186
+ LOCAL_CTX, db, ingest_id=id, visible_projects=None,
187
+ )
188
+ return dump(response)
189
+ except ServiceError as e:
190
+ raise_mcp_error(e)
191
+
192
+ @mcp.tool()
193
+ async def reject_ingest_pending(
194
+ id: Annotated[str, Field(pattern=_UUID_PATTERN)],
195
+ reason: Annotated[str, Field(max_length=500)],
196
+ ) -> dict[str, Any]:
197
+ """Reject ingest_pending row → status=rejected, file resta orphan in input/.
198
+
199
+ QUANDO USARLO: row non vuole essere ingestita (low signal, duplicato, off-topic) → libera la coda triage.
200
+ QUANDO NON USARLO: NOT per cancellare il file fisico (resta orphan in input/). NOT per cambiare project_slug post-rejection -> patch_ingest_pending supporta lo stato rejected.
201
+ RESTITUISCE: {id, status:rejected}."""
202
+ # The use_case `reject` is a soft state transition and does not carry a
203
+ # `reason` arg (the Node `reason` is the HTTP body the legacy router
204
+ # accepted; the durable transition only records `triage_decision_id`).
205
+ # Accepted on the surface for Node parity. Returns (response, project_slug).
206
+ try:
207
+ async with acquire_write_db() as db:
208
+ response, _project_slug = await ingest_uc.reject(
209
+ LOCAL_CTX, db, ingest_id=id, visible_projects=None,
210
+ )
211
+ return dump(response)
212
+ except ServiceError as e:
213
+ raise_mcp_error(e)
214
+
215
+ @mcp.tool()
216
+ async def patch_ingest_pending(
217
+ id: Annotated[str, Field(pattern=_UUID_PATTERN)],
218
+ project_slug: Annotated[str, Field(pattern=_PROJECT_SLUG_PATTERN)] | None = None,
219
+ ) -> dict[str, Any]:
220
+ """Change project_slug post-upload (E4). State machine: SOLO awaiting_triage/parse_error/rejected. Atomic copy-then-rename con path containment.
221
+
222
+ QUANDO USARLO: file finito nel progetto sbagliato in upload → reroute prima dell'approve.
223
+ QUANDO NON USARLO: NOT su row in flight (queued/parsing/approved/inserted/done) -> 422. NOT per cambiare classification (target_folder/filename) -> richiede E4 follow-up non ancora live.
224
+ RESTITUISCE: IngestPendingItem aggiornato."""
225
+ # Replicate the router's transport-boundary pre-work fastapi-free (load row,
226
+ # state guard, no-op short-circuit, slug validate, input_root resolve), then
227
+ # hand the resolved Path objects to the use_case which owns the domain truth
228
+ # (sha-collision, containment, atomic move, row + change-history writes).
229
+ try:
230
+ async with acquire_write_db() as db:
231
+ row = await ingest_uc._load_row(db, id)
232
+
233
+ allowed_states = ("awaiting_triage", "parse_error", "rejected")
234
+ if row["status"] not in allowed_states:
235
+ raise ValidationError(
236
+ code="invalid_state_for_change",
237
+ message=(
238
+ f"Item state '{row['status']}' is not patchable "
239
+ f"(allowed: {', '.join(allowed_states)})"
240
+ ),
241
+ )
242
+
243
+ new_slug = project_slug
244
+ if not new_slug or new_slug == row["project_slug"]:
245
+ # No-op: return the current row so the caller can refresh.
246
+ return dump(ingest_uc._row_to_item(row))
247
+
248
+ new_input_root = _project_input_root(new_slug)
249
+ result = await ingest_uc.patch_pending(
250
+ LOCAL_CTX,
251
+ db,
252
+ row=row,
253
+ new_slug=new_slug,
254
+ new_input_root=new_input_root,
255
+ projects_root=PROJECTS_ROOT,
256
+ changed_by=LOCAL_CTX.username,
257
+ source_ip=None,
258
+ user_agent="mcp-local",
259
+ )
260
+ return dump(result)
261
+ except ServiceError as e:
262
+ raise_mcp_error(e)
263
+
264
+ @mcp.tool()
265
+ async def classify_ingest(
266
+ id: Annotated[str, Field(pattern=_UUID_PATTERN)],
267
+ force: bool = False,
268
+ ) -> dict[str, Any]:
269
+ """Force re-classify ingest_pending row. Phase 1 = deterministic re-parse (parser_router); post P1.5.E5 = trigger Haiku #1 manuale senza cambi MCP/UI.
270
+
271
+ QUANDO USARLO: classification originale era sbagliata e vuoi rilanciare il classifier (es. dopo training data update).
272
+ QUANDO NON USARLO: NOT per parse_error (usa reparse_ingest che e' lo stesso path semanticamente). NOT su done/inserted (409 stato non valido).
273
+ RESTITUISCE: {id, status:parsing}."""
274
+ # `force` is accepted for Node parity; the Node handler ignores it too (it
275
+ # always POSTs classify-force). The durable status='queued' reset commits
276
+ # here; the deterministic re-parse is driven by the server-lifecycle worker.
277
+ try:
278
+ async with acquire_write_db() as db:
279
+ result = await ingest_uc.classify_force(
280
+ LOCAL_CTX, db, ingest_id=id, visible_projects=None,
281
+ )
282
+ return dump(result)
283
+ except ServiceError as e:
284
+ raise_mcp_error(e)
285
+
286
+ @mcp.tool()
287
+ async def reparse_ingest(
288
+ id: Annotated[str, Field(pattern=_UUID_PATTERN)] | None = None,
289
+ status: Literal["parse_error"] | None = None,
290
+ ) -> dict[str, Any]:
291
+ """Re-parse ingest_pending row (single id) o batch (status=parse_error, admin only). Riusa parser_router via API invece di scripts/reparse_failed.py.
292
+
293
+ QUANDO USARLO: row in parse_error → ritenta dopo fix parser/dipendenze. Batch utile per recovery post-deploy.
294
+ QUANDO NON USARLO: NOT con id E status insieme (usa uno o l'altro). NOT per cambiare project_slug -> patch_ingest_pending.
295
+ RESTITUISCE: single → {id, status:parsing}; batch → {queued_count, status}."""
296
+ if id and status:
297
+ raise_mcp_error(
298
+ ValidationError(
299
+ code="reparse_args_conflict",
300
+ message="Provide id OR status, not both",
301
+ )
302
+ )
303
+ try:
304
+ if id:
305
+ async with acquire_write_db() as db:
306
+ result = await ingest_uc.reparse_single(
307
+ LOCAL_CTX, db, ingest_id=id, visible_projects=None,
308
+ )
309
+ return dump(result)
310
+ if status:
311
+ # The batch read selects the matching ids; the use_case returns
312
+ # (response, ids). The per-row parse fan-out is the server-lifecycle
313
+ # worker's job (not replicated here, see module docstring).
314
+ async with acquire_db() as db:
315
+ response, _ids = await ingest_uc.reparse_batch(
316
+ LOCAL_CTX, db, target_status=status, visible_projects=None,
317
+ )
318
+ return dump(response)
319
+ raise_mcp_error(
320
+ ValidationError(
321
+ code="reparse_args_missing",
322
+ message="Either id or status required",
323
+ )
324
+ )
325
+ except ServiceError as e:
326
+ raise_mcp_error(e)
@@ -0,0 +1,144 @@
1
+ # v1.0.0 - 2026-05-27 - S1 F3.0: learnings MCP tool group (use_cases-direct, no HTTP)
2
+ """Learnings MCP tools — port of the Node ``learnings`` group, use_cases-direct.
3
+
4
+ Same template as ``tasks.py``: the Node HTTP proxy is replaced by an in-process
5
+ ``await learnings_uc.<action>(LOCAL_CTX, db, ...)``. Docstrings copied
6
+ VERBATIM from ``core/mcp-pir/index.mjs``.
7
+
8
+ Schema notes:
9
+ * ``check_learnings`` keeps the Node param name ``q`` (the surface contract);
10
+ the use_case parameter is ``query`` — mapped in the body. ``module`` optional.
11
+ * ``create_learning`` mirrors the Node REQUIRED set (title/category/description/
12
+ prevention/severity) and the optional set (module/tags/project). Enums become
13
+ ``Literal[...]``.
14
+
15
+ Return typing: reads return ``dict[str, Any]`` / ``list[dict]``; ``create_learning``
16
+ (mutator) returns the DTO ``.model_dump()`` via ``dump()``. visible_projects=None
17
+ (local single-user, unrestricted).
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from typing import Annotated, Any, Literal
22
+
23
+ from pydantic import Field
24
+
25
+ from core.api.mcp._adapter import LOCAL_CTX, acquire_db, acquire_write_db, dump, raise_mcp_error
26
+ from core.api.use_cases import learnings as learnings_uc
27
+ from core.api.use_cases._errors import ServiceError
28
+
29
+ LearningCategory = Literal[
30
+ "deploy", "migration", "auth", "testing", "architecture", "security", "performance"
31
+ ]
32
+ LearningSeverity = Literal["low", "medium", "high", "critical"]
33
+
34
+
35
+ def register(mcp) -> None:
36
+ """Register the learnings tool group on the shared FastMCP instance."""
37
+
38
+ @mcp.tool()
39
+ async def check_learnings(
40
+ q: Annotated[str, Field(min_length=1, max_length=500)],
41
+ module: str | None = None,
42
+ ) -> dict[str, Any]:
43
+ """Search learnings DB (past post-mortems + prevention rules) for a risky action.
44
+
45
+ QUANDO USARLO: PRIMA di qualunque azione production-affecting (deploy, migration, auth change, push to main, dependency upgrade) — Quality Gate 9.4. BOUNDARY: create_learning vs check_learnings = create scrive nuovo; check cerca rilevante PRE-azione rischiosa. BOUNDARY: check_learnings vs list_learnings = check = semantic match su situazione; list = enumerazione filtrata.
46
+ QUANDO NON USARLO: NOT quando hai gia' un file/module path e vuoi learnings scoped -> usa graph_pattern. NOT per scrivere un nuovo learning -> usa create_learning.
47
+ RESTITUISCE: list of {id, title, prevention, severity, module} ranked per rilevanza."""
48
+ try:
49
+ async with acquire_db() as db:
50
+ # Node param `q` -> use_case param `query`.
51
+ result = await learnings_uc.check_learnings(
52
+ LOCAL_CTX, db, query=q, module=module
53
+ )
54
+ return dump(result)
55
+ except ServiceError as e:
56
+ raise_mcp_error(e)
57
+
58
+ @mcp.tool()
59
+ async def create_learning(
60
+ title: Annotated[str, Field(min_length=1, max_length=200)],
61
+ category: LearningCategory,
62
+ description: Annotated[str, Field(min_length=1)],
63
+ prevention: Annotated[str, Field(min_length=1)],
64
+ severity: LearningSeverity,
65
+ module: str | None = None,
66
+ tags: list[str] | None = None,
67
+ project: str | None = None,
68
+ ) -> dict[str, Any]:
69
+ """Persist a new learning (post-mortem) with prevention rule after an error or incident.
70
+
71
+ QUANDO USARLO: DOPO aver hit un bug che vale la pena ricordare cross-session (es. 'z.record(1-arg) crashes MCP SDK'). BOUNDARY: create_learning vs check_learnings = create scrive nuovo; check cerca rilevante PRE-azione rischiosa.
72
+ QUANDO NON USARLO: NOT per idea forward-looking improvement -> usa create_task kind='idea'. NOT per task operativo -> usa create_task.
73
+ RESTITUISCE: {id, title, category, severity, created_at} — indexed by check_learnings."""
74
+ try:
75
+ async with acquire_write_db(label="mcp.create_learning") as db:
76
+ result = await learnings_uc.create_learning(
77
+ LOCAL_CTX,
78
+ db,
79
+ title=title,
80
+ category=category,
81
+ description=description,
82
+ prevention=prevention,
83
+ severity=severity,
84
+ module=module,
85
+ tags=tags,
86
+ project=project,
87
+ )
88
+ return dump(result)
89
+ except ServiceError as e:
90
+ raise_mcp_error(e)
91
+
92
+ @mcp.tool()
93
+ async def list_learnings(
94
+ project: str | None = None,
95
+ category: LearningCategory | None = None,
96
+ severity: LearningSeverity | None = None,
97
+ tags: str | None = None,
98
+ search: str | None = None,
99
+ limit: Annotated[int, Field(ge=1, le=200)] = 50,
100
+ offset: Annotated[int, Field(ge=0)] = 0,
101
+ ) -> list[dict[str, Any]]:
102
+ """List learnings with optional filters (category, project, severity, tags, free text).
103
+
104
+ QUANDO USARLO: enumerare tutti i learning del progetto o filtrare per category/severity/tags. Utile per review periodica pattern consolidati. BOUNDARY: check_learnings vs list_learnings = check = semantic match su situazione; list = enumerazione filtrata.
105
+ QUANDO NON USARLO: NOT per cercare learning applicabile a situazione rischiosa corrente -> usa check_learnings (semantic). NOT per singolo ID -> usa get_learning.
106
+ RESTITUISCE: list of {id, title, category, severity, module, project, created_at, tags} paginato ordered per frequency DESC."""
107
+ try:
108
+ async with acquire_db() as db:
109
+ result = await learnings_uc.list_learnings(
110
+ LOCAL_CTX,
111
+ db,
112
+ category=category,
113
+ project=project,
114
+ severity=severity,
115
+ tags=tags,
116
+ search=search,
117
+ limit=limit,
118
+ offset=offset,
119
+ visible_projects=None,
120
+ )
121
+ return dump(result)
122
+ except ServiceError as e:
123
+ raise_mcp_error(e)
124
+
125
+ @mcp.tool()
126
+ async def get_learning(
127
+ learning_id: Annotated[str, Field(min_length=1)],
128
+ deep: bool | None = None,
129
+ ) -> dict[str, Any]:
130
+ """Get a single learning by UUID.
131
+
132
+ QUANDO USARLO: hai un learning ID (da list_learnings o check_learnings) e vuoi il body completo (description + prevention + tags). Usa ?deep=true per includere kg_context inline (context chain, related learnings) — risparmia 2-3 tool call aggiuntivi.
133
+ QUANDO NON USARLO: NOT per ricerca semantica pre-azione -> usa check_learnings. NOT per enumerazione filtrata -> usa list_learnings.
134
+ RESTITUISCE: {id, title, category, description, prevention, severity, tags, module, project, session, created_at, updated_at, frequency, last_occurrence} + kg_context se deep=true."""
135
+ try:
136
+ async with acquire_db() as db:
137
+ # DECISION 2: deep KG enrichment is an adapter concern; core fetch
138
+ # here, deep-attach lands in a later F3 increment.
139
+ result = await learnings_uc.get_learning(
140
+ LOCAL_CTX, db, learning_id=learning_id
141
+ )
142
+ return dump(result)
143
+ except ServiceError as e:
144
+ raise_mcp_error(e)
@@ -0,0 +1,99 @@
1
+ # v1.0.0 - 2026-05-27 - S1 F3.1a: projects MCP tool group (use_cases-direct, no HTTP)
2
+ """Projects MCP tools — port of the Node ``projects`` group, use_cases-direct.
3
+
4
+ Same template as ``tasks.py`` / ``learnings.py``: the Node HTTP proxy
5
+ (``get(/api/v1/projects...)``) is replaced by an in-process
6
+ ``await projects_uc.<action>(LOCAL_CTX, db, ...)``. Docstrings copied VERBATIM
7
+ from ``core/mcp-pir/index.mjs``.
8
+
9
+ Name mapping (Node tool name -> use_case function):
10
+ * ``list_projects`` -> ``projects_uc.list_programs`` (the Node tool returns the
11
+ project list grouped by program — that IS ``list_programs`` here).
12
+ * ``get_project`` -> ``projects_uc.get_project``.
13
+ * ``session_brief`` -> ``projects_uc.get_session_brief``.
14
+
15
+ Deep KG enrichment is DEFERRED (same as F3.0): ``get_project`` returns the core
16
+ fetch with ``kg_context=None`` and ignores ``deep`` (adapter concern, later F3
17
+ increment). ``session_brief``'s ``get_session_brief`` assembles kg_context inside
18
+ the use_case as part of the bundle contract — Node always cold-starts deep, so the
19
+ MCP surface passes ``deep=True`` to widen the per-bucket KG limits (Node
20
+ ``effectiveDeep`` default).
21
+
22
+ Return typing: reads return ``dict[str, Any]`` / ``list[dict]`` via ``dump()``
23
+ (DTO lists are normalised element-wise). visible_projects=None (local single-user,
24
+ unrestricted — DECISION 1).
25
+ """
26
+ from __future__ import annotations
27
+
28
+ from typing import Annotated, Any
29
+
30
+ from pydantic import Field
31
+
32
+ from core.api.mcp._adapter import LOCAL_CTX, acquire_db, dump, raise_mcp_error
33
+ from core.api.use_cases import projects as projects_uc
34
+ from core.api.use_cases._errors import ServiceError
35
+
36
+
37
+ def register(mcp) -> None:
38
+ """Register the projects tool group on the shared FastMCP instance."""
39
+
40
+ @mcp.tool()
41
+ async def list_projects() -> list[dict[str, Any]]:
42
+ """List all PiR-tracked projects grouped by program.
43
+
44
+ QUANDO USARLO: serve un inventario (quali progetti ci sono, dammi tutti gli slug) o non conosci lo slug target.
45
+ QUANDO NON USARLO: NOT quando hai gia' uno slug specifico e vuoi i dettagli -> usa get_project. NOT per cold-start di un progetto -> usa session_brief.
46
+ RESTITUISCE: list of {slug, program, lifecycle, language, task_counts} senza body di context.md."""
47
+ try:
48
+ async with acquire_db() as db:
49
+ # Node `list_projects` returns the program-grouped project list =
50
+ # `list_programs` here. visible_projects=None -> local sees all.
51
+ result = await projects_uc.list_programs(
52
+ LOCAL_CTX, db, visible_projects=None
53
+ )
54
+ return dump(result)
55
+ except ServiceError as e:
56
+ raise_mcp_error(e)
57
+
58
+ @mcp.tool()
59
+ async def get_project(
60
+ slug: Annotated[str, Field(min_length=1, max_length=50)],
61
+ deep: bool | None = None,
62
+ ) -> dict[str, Any]:
63
+ """Get full project detail (metadata + context.md body + handoff index + docs).
64
+
65
+ QUANDO USARLO: hai uno slug noto e ti serve lo stato raw del progetto (es. leggere context.md body o elenco handoff completo). Usa ?deep=true per includere kg_context inline (neighbors, context_chain, applicable_learnings) — risparmia 2-3 tool call aggiuntivi.
66
+ QUANDO NON USARLO: NOT per agent cold-start -> usa session_brief (aggrega project + open tasks + top learnings + salience docs in una call). NOT se non hai lo slug -> usa list_projects.
67
+ RESTITUISCE: {slug, metadata, context_md, handoffs[], docs[], deploy_info} + kg_context se deep=true."""
68
+ try:
69
+ async with acquire_db() as db:
70
+ # DECISION 2: `deep` KG enrichment is an adapter concern; the
71
+ # use_case returns kg_context=None (core fetch). The deep-attach
72
+ # is a later F3 increment, same as get_task/get_learning in F3.0.
73
+ result = await projects_uc.get_project(
74
+ LOCAL_CTX, db, slug=slug, visible_projects=None
75
+ )
76
+ return dump(result)
77
+ except ServiceError as e:
78
+ raise_mcp_error(e)
79
+
80
+ @mcp.tool()
81
+ async def session_brief(
82
+ slug: Annotated[str, Field(min_length=1, max_length=50)],
83
+ ) -> dict[str, Any]:
84
+ """Cold-start aggregated context bundle for agent session resume.
85
+
86
+ QUANDO USARLO: inizio sessione su un progetto (sostituisce la sequenza get_project + list_tasks + search_handoffs + Read context.md + check_learnings). BOUNDARY: session_brief = cold-start aggregato; list_tasks+list_handoffs = query mirate.
87
+ QUANDO NON USARLO: NOT se ti serve solo il body grezzo di context.md o l'elenco completo handoff -> usa get_project. NOT per query filtrate puntuali -> usa list_tasks/list_handoffs.
88
+ RESTITUISCE: {project, open_tasks[], latest_handoff, recent_learnings[], top_salience_docs[]} tuned per context window LLM."""
89
+ try:
90
+ async with acquire_db() as db:
91
+ # The bundle's kg_context is part of the contract (assembled inside
92
+ # the use_case, not deferred). Node cold-starts deep by default
93
+ # (`effectiveDeep` -> true), so widen the per-bucket KG limits.
94
+ result = await projects_uc.get_session_brief(
95
+ LOCAL_CTX, db, slug=slug, deep=True, visible_projects=None
96
+ )
97
+ return dump(result)
98
+ except ServiceError as e:
99
+ raise_mcp_error(e)