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,380 @@
1
+ # v1.0.0 - 2026-05-27 - S1 F3.1b: graph KG MCP tool group (use_cases-direct, no HTTP)
2
+ """Graph (Knowledge-Graph) MCP tools — port of the Node ``graph_*`` group, use_cases-direct.
3
+
4
+ Same template as ``tasks.py`` / ``learnings.py``: the Node HTTP proxy
5
+ (``get``/``post``/``del`` -> ``:8100``) is replaced by an in-process
6
+ ``await graph_uc.<fn>(LOCAL_CTX, db, ...)``. Docstrings are copied VERBATIM from
7
+ ``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().max(N)`` -> ``Annotated[str, Field(max_length=N)]``
13
+ * ``z.number().int().min().max()`` -> ``Annotated[int, Field(ge=, le=)]``
14
+ * optional -> ``X | None = None`` (or ``= <default>``)
15
+ * ``z.array(edgeTypeEnum)`` -> ``list[EdgeType] | None``
16
+
17
+ Return typing (S1 F3): reads return ``dict[str, Any]`` / ``list[dict]``; the pin
18
+ mutators (``pin_graph_node`` / ``unpin_graph_node``) return the ``PinOut``/``dict``
19
+ via ``dump()``. ``graph_capabilities`` returns the ``GraphCapabilities`` DTO dump.
20
+
21
+ Visibility: the MCP surface is local single-user (no ``UserInfo.teams``), so every
22
+ tool passes ``visible_projects=None`` = unrestricted (the same DECISION 1 + DECISION B
23
+ collapse the four-eyes / PR gates take on the local surface, S1 §AUTH). The
24
+ project-scoped 403 / oracle-avoidance 404 enforcement that ``routers/graph.py``
25
+ layers on top (DECISION B) is a multi-tenant transport concern that has no meaning
26
+ for a single operator — the pure use_case data work is what the MCP surface needs.
27
+
28
+ fastapi-free invariant (the collapse must stay honest — zero fastapi in the MCP
29
+ import path): ``use_cases.graph`` itself is already fastapi-free (it depends only
30
+ on the fastapi-free ``graph_service`` / ``graph_ranker``), so it is a module-top
31
+ import. The ONLY fastapi-importing dependency in this family is
32
+ ``api.visibility.filter_visible_edges`` (the macro-overview RBAC edge filter,
33
+ DECISION A). It is NEVER imported here; instead ``graph_overview`` injects a
34
+ LOCAL no-op edge filter (single-user sees all edges, hidden_cross_project_count=0),
35
+ which both keeps fastapi out of the MCP path AND matches the local "unrestricted"
36
+ stance.
37
+
38
+ SKIPPED (no clean use_case — adapter/fastapi-bound, ported in a later increment):
39
+ * ``share_function`` / ``graph_function_share`` — the use_case only carries the
40
+ PURE pieces (validate_qualified_name / lookup_function_node /
41
+ build_share_function_blocks). Their headline value (the signed share_url + the
42
+ on-disk source preview) lives in the router and pulls fastapi via
43
+ ``services.share_links`` (``validate_repo_path`` / ``create_shared_link_record``)
44
+ + the router-local ``_read_file_preview``. Porting without the URL would be a
45
+ degraded, misleading tool, so it is skipped (S1 F3 SKIP rule), not faked.
46
+ * ``graph_cosmo`` — adapter-only (``graph_cosmo_service`` imports fastapi,
47
+ DECISION A); no ``use_cases.graph`` entry.
48
+ * ``graph_pr_impact`` / ``graph_branches`` / ``graph_conflicts`` /
49
+ ``graph_semantic_modules`` — PR-Impact/Codex-lens family; logic was not
50
+ extracted into ``use_cases.graph`` (still router/service-bound).
51
+ * ``kg_reindex_path`` / ``kg_rebuild`` / ``kg_watcher_control`` — KG control
52
+ plane; no use_case (systemd / sentinel side effects in the router).
53
+ """
54
+ from __future__ import annotations
55
+
56
+ from typing import Annotated, Any, Literal
57
+
58
+ from pydantic import Field
59
+
60
+ from core.api.mcp._adapter import (
61
+ LOCAL_CTX,
62
+ acquire_db,
63
+ acquire_write_db,
64
+ dump,
65
+ raise_mcp_error,
66
+ )
67
+ from core.api.use_cases import graph as graph_uc
68
+ from core.api.use_cases._errors import ServiceError
69
+
70
+ # Zod enums -> Literals (mirror the Node tool signatures).
71
+ NeighborRelation = Literal["calls", "imports", "defines"]
72
+ NeighborDirection = Literal["incoming", "outgoing", "both"]
73
+ NeighborRank = Literal["none", "suspect_write"]
74
+ HotspotWindow = Literal["7d", "30d", "total"]
75
+ HotspotTypeFilter = Literal["function", "file", "all"]
76
+ OverviewLevel = Literal["macro", "module"]
77
+ # edgeTypeEnum (index.mjs) — the 16 relation types valid as edge_types filters.
78
+ EdgeType = Literal[
79
+ "calls", "imports", "defines",
80
+ "produces", "contains",
81
+ "describes", "documents", "cites", "applies_to",
82
+ "depends_on", "mentions", "refers_to", "shares_tag", "similar_to",
83
+ "resolves_to",
84
+ "modifies",
85
+ ]
86
+
87
+ # Node ID + qualified-name patterns (mirror the Node Zod regexes).
88
+ _NODE_ID_PATTERN = (
89
+ r"^(py|ts|task|pr|commit|handoff|solution|learning|audit|spike|analysis|"
90
+ r"research|rubric|guide|mockup|project|file|hook|skill|command|plugin|plan|"
91
+ r"brainstorm|inbox|xlsx|policy|contract|transcript|record|report):"
92
+ r"(function|file|module|artifact|sheet):[a-zA-Z0-9_\-.]+$"
93
+ )
94
+ _PIN_NODE_ID_PATTERN = r"^[a-z]+:[a-z]+:.+$"
95
+ _AS_OF_PATTERN = r"^\d{4}-\d{2}-\d{2}([ T]\d{2}:\d{2}:\d{2}(\.\d+)?)?Z?$"
96
+ _PROJECT_PATTERN = r"^[a-z0-9][a-z0-9&\-]+$"
97
+ _ORPHAN_SCOPE_PATTERN = r"^(project|module):artifact:.+$"
98
+
99
+
100
+ async def _local_no_op_edge_filter(
101
+ _db: Any, _user: Any, edges: list[dict]
102
+ ) -> tuple[list[dict], int]:
103
+ """Local single-user edge filter for ``graph_overview`` macro (unrestricted).
104
+
105
+ The HTTP surface injects ``api.visibility.filter_visible_edges`` (which imports
106
+ fastapi, DECISION A). The MCP surface MUST NOT pull fastapi into its import
107
+ path, and the single operator sees every project anyway, so the local injection
108
+ is this no-op: all edges kept, ``hidden_cross_project_count = 0``.
109
+ """
110
+ return edges, 0
111
+
112
+
113
+ def register(mcp) -> None:
114
+ """Register the graph (KG) tool group on the shared FastMCP instance."""
115
+
116
+ @mcp.tool()
117
+ async def graph_neighbors(
118
+ node_id: Annotated[str, Field(pattern=_NODE_ID_PATTERN, max_length=256)],
119
+ relation: NeighborRelation | None = None,
120
+ edge_types: list[EdgeType] | None = None,
121
+ project: Annotated[str, Field(max_length=50, pattern=_PROJECT_PATTERN)] | None = None,
122
+ direction: NeighborDirection = "both",
123
+ rank: NeighborRank = "none",
124
+ as_of: Annotated[str, Field(pattern=_AS_OF_PATTERN, max_length=32)] | None = None,
125
+ ) -> dict[str, Any]:
126
+ """[Power-user] Surgical 1-hop neighbour query for a KG node (calls/imports/defines/mentions/...) — per context completo su un nodo usa get_task/get_project/get_handoff/get_learning(deep=true). Usa questo tool solo quando hai bisogno di ispezionare il vicinato topologico diretto senza il bundle completo.
127
+ QUANDO USARLO: refactor impact assessment su una singola function; verificare se un nodo ha caller inattesi; time-travel as_of su commit specifico; rank=suspect_write per bug write-through-read.
128
+ QUANDO NON USARLO: NOT per context di un task/handoff/learning -> usa get_*(deep=true). NOT per BFS transitivo -> usa graph_impact.
129
+ RESTITUISCE: list of {node_id, relation, direction, rank_score?} cap 200."""
130
+ try:
131
+ async with acquire_db() as db:
132
+ # Node proxy hardcodes limit=200 (the surface exposes no limit param).
133
+ result = await graph_uc.graph_neighbors(
134
+ LOCAL_CTX,
135
+ db,
136
+ node_id=node_id,
137
+ relation=relation,
138
+ edge_types=list(edge_types) if edge_types else None,
139
+ project=project,
140
+ direction=direction,
141
+ limit=200,
142
+ rank=rank or "none",
143
+ as_of=as_of,
144
+ )
145
+ return dump(result)
146
+ except ServiceError as e:
147
+ raise_mcp_error(e)
148
+
149
+ @mcp.tool()
150
+ async def graph_hotspots(
151
+ window: HotspotWindow = "30d",
152
+ limit: Annotated[int, Field(ge=1, le=100)] = 20,
153
+ type_filter: HotspotTypeFilter = "file",
154
+ ) -> dict[str, Any]:
155
+ """[Power-user] DORA-style churn ranking: files/functions sorted by touch_count 7d/30d with bus-factor warning — per context completo su un progetto usa get_project(deep=true). Usa questo tool per analisi architetturale di rischio o pianificazione refactor.
156
+ QUANDO USARLO: identificare moduli ad alto rischio prima di una release; trovare ownership gaps (bus_factor=1); pianificare tech debt.
157
+ QUANDO NON USARLO: NOT per context di un progetto specifico -> usa get_project(deep=true). NOT per BFS da un nodo -> usa graph_impact.
158
+ RESTITUISCE: list of {node_id, touch_count_7d, touch_count_30d, authors[], bus_factor} top N."""
159
+ try:
160
+ async with acquire_db() as db:
161
+ result = await graph_uc.graph_hotspots(
162
+ LOCAL_CTX,
163
+ db,
164
+ window=window,
165
+ limit=limit,
166
+ type_filter=type_filter,
167
+ project=None,
168
+ )
169
+ return dump(result)
170
+ except ServiceError as e:
171
+ raise_mcp_error(e)
172
+
173
+ @mcp.tool()
174
+ async def graph_impact(
175
+ node_id: Annotated[str, Field(pattern=_NODE_ID_PATTERN, max_length=256)],
176
+ depth: Annotated[int, Field(ge=1, le=5)] = 2,
177
+ limit: Annotated[int, Field(ge=1, le=200)] = 50,
178
+ edge_types: list[EdgeType] | None = None,
179
+ project: Annotated[str, Field(max_length=50, pattern=_PROJECT_PATTERN)] | None = None,
180
+ ) -> dict[str, Any]:
181
+ """[Power-user] BFS impact analysis: 'cosa si rompe se cambio X?' con ranker su direct callers + transitive list — per context completo usa get_task(deep=true). Usa questo tool per delete/rename risk assessment o pre-refactor sweep.
182
+ QUANDO USARLO: prima di eliminare/rinominare una function critica; misurare blast radius di una migration; dependency cascade analysis.
183
+ QUANDO NON USARLO: NOT per context standard -> usa get_*(deep=true). NOT per 1-hop only -> usa graph_neighbors.
184
+ RESTITUISCE: {direct_callers[], transitive_list[], rank_score} depth configurabile."""
185
+ try:
186
+ async with acquire_db() as db:
187
+ result = await graph_uc.graph_impact(
188
+ LOCAL_CTX,
189
+ db,
190
+ node_id=node_id,
191
+ depth=depth,
192
+ limit=limit,
193
+ edge_types=list(edge_types) if edge_types else None,
194
+ project=project,
195
+ )
196
+ return dump(result)
197
+ except ServiceError as e:
198
+ raise_mcp_error(e)
199
+
200
+ @mcp.tool()
201
+ async def graph_context(
202
+ node_id: Annotated[str, Field(pattern=_NODE_ID_PATTERN, max_length=256)],
203
+ per_category_limit: Annotated[int, Field(ge=1, le=20)] = 5,
204
+ project: Annotated[str, Field(max_length=50, pattern=_PROJECT_PATTERN)] | None = None,
205
+ ) -> dict[str, Any]:
206
+ """[Power-user] Rationale chain: function→commit→PR→task→handoff→learning in 1 call — per context su un singolo artefatto usa get_task/get_handoff/get_learning(deep=true). Usa questo tool quando hai un node_id e vuoi la storia completa del PERCHE'.
207
+ QUANDO USARLO: archeologia di codice legacy ('perche esiste questa function?'); audit trail pre-refactor; linkare codice a decision record.
208
+ QUANDO NON USARLO: NOT per workflow standard su task/handoff -> usa get_*(deep=true). NOT per vicinato topologico -> usa graph_neighbors.
209
+ RESTITUISCE: chain {commits[], PR?, task?, handoffs[], learnings[]} per_category_limit configurabile."""
210
+ try:
211
+ async with acquire_db() as db:
212
+ result = await graph_uc.graph_context(
213
+ LOCAL_CTX,
214
+ db,
215
+ node_id=node_id,
216
+ per_category_limit=per_category_limit,
217
+ project=project,
218
+ )
219
+ return dump(result)
220
+ except ServiceError as e:
221
+ raise_mcp_error(e)
222
+
223
+ @mcp.tool()
224
+ async def graph_pattern(
225
+ scope: Annotated[str, Field(max_length=256)],
226
+ limit: Annotated[int, Field(ge=1, le=100)] = 20,
227
+ ) -> dict[str, Any]:
228
+ """[Power-user] Learnings scoped a un modulo/path specifico via KG — per learnings generici usa check_learnings. Usa questo tool per learnings chirurgici su un file/modulo specifico prima di toccare codice in quella area.
229
+ QUANDO USARLO: 'quali learnings esistono per api.db?' prima di toccare il DB module; scoping pre-deployment di un servizio specifico.
230
+ QUANDO NON USARLO: NOT per ricerca semantica generica pre-azione -> usa check_learnings. NOT per context di un nodo -> usa graph_context.
231
+ RESTITUISCE: list of {learning_id, title, prevention, severity, scope_match_score}."""
232
+ try:
233
+ async with acquire_db() as db:
234
+ result = await graph_uc.graph_pattern(
235
+ LOCAL_CTX, db, scope=scope, limit=limit, project=None,
236
+ )
237
+ return dump(result)
238
+ except ServiceError as e:
239
+ raise_mcp_error(e)
240
+
241
+ @mcp.tool()
242
+ async def graph_capabilities() -> dict[str, Any]:
243
+ """[Power-user] KG schema metadata per agent discovery (edge_types + node_kinds + prefixes + versions).
244
+ QUANDO USARLO: prima di costruire query graph_* o validare node_id pattern; cold-start agent discovery.
245
+ QUANDO NON USARLO: per query topologiche -> usa graph_neighbors/impact/context.
246
+ RESTITUISCE: {edge_types[], node_kinds[], node_prefixes[], schema_version}.
247
+ FALLBACK: se 5xx, usa lista statica edge_types noti 2026-04 (vedi kb/knowledge-graph.md)."""
248
+ try:
249
+ async with acquire_db() as db:
250
+ result = await graph_uc.get_capabilities(LOCAL_CTX, db)
251
+ return dump(result)
252
+ except ServiceError as e:
253
+ raise_mcp_error(e)
254
+
255
+ @mcp.tool()
256
+ async def pin_graph_node(
257
+ node_id: Annotated[
258
+ str, Field(min_length=6, max_length=256, pattern=_PIN_NODE_ID_PATTERN)
259
+ ],
260
+ note: Annotated[str, Field(max_length=500)] | None = None,
261
+ ) -> dict[str, Any]:
262
+ """Save a KG node as a personal bookmark (pin). Idempotent — pinning the same node twice updates the note. Use to bookmark frequently visited nodes (functions, files, tasks). Appears in graph_landing() saved_nodes slice."""
263
+ try:
264
+ async with acquire_write_db(label="mcp.pin_graph_node") as db:
265
+ result = await graph_uc.create_graph_pin(
266
+ LOCAL_CTX,
267
+ db,
268
+ workspace_id=LOCAL_CTX.workspace_id,
269
+ user_id=LOCAL_CTX.user_id,
270
+ node_id=node_id,
271
+ note=note,
272
+ )
273
+ return dump(result)
274
+ except ServiceError as e:
275
+ raise_mcp_error(e)
276
+
277
+ @mcp.tool()
278
+ async def unpin_graph_node(
279
+ node_id: Annotated[
280
+ str, Field(min_length=6, max_length=256, pattern=_PIN_NODE_ID_PATTERN)
281
+ ],
282
+ ) -> dict[str, Any]:
283
+ """Remove a personal bookmark (pin) for a KG node. Returns 404 if the pin does not exist for the current user."""
284
+ try:
285
+ async with acquire_write_db(label="mcp.unpin_graph_node") as db:
286
+ result = await graph_uc.delete_graph_pin(
287
+ LOCAL_CTX, db, user_id=LOCAL_CTX.user_id, node_id=node_id,
288
+ )
289
+ return dump(result)
290
+ except ServiceError as e:
291
+ raise_mcp_error(e)
292
+
293
+ @mcp.tool()
294
+ async def list_graph_pins() -> list[dict[str, Any]]:
295
+ """List all personal KG bookmarks (pins) for the current user, ordered by most recently pinned. Pins on soft-deleted nodes are excluded."""
296
+ try:
297
+ async with acquire_db() as db:
298
+ result = await graph_uc.list_graph_pins(
299
+ LOCAL_CTX, db, user_id=LOCAL_CTX.user_id,
300
+ )
301
+ return dump(result)
302
+ except ServiceError as e:
303
+ raise_mcp_error(e)
304
+
305
+ @mcp.tool()
306
+ async def graph_resolve(
307
+ path: Annotated[str, Field(min_length=1, max_length=1024)],
308
+ ) -> dict[str, Any]:
309
+ """Resolve a file path to its KG node_id. Useful when you have a file path (e.g. 'api/db.py') and need the graph_nodes id for neighbors/impact/context queries. Returns 404 if the file is not indexed or not visible."""
310
+ try:
311
+ async with acquire_db() as db:
312
+ # visible_projects=None -> local single-user sees all (DECISION 1).
313
+ result = await graph_uc.graph_resolve(
314
+ LOCAL_CTX, db, path=path, visible_projects=None,
315
+ )
316
+ return dump(result)
317
+ except ServiceError as e:
318
+ raise_mcp_error(e)
319
+
320
+ @mcp.tool()
321
+ async def graph_landing() -> dict[str, Any]:
322
+ """Get the KG landing bundle: top-10 hotspots (30d), last-20 recent artifacts (commits/PRs/tasks/handoffs), and your saved pins. Cached 60s per workspace. Use as the first call when opening the KG explorer or needing a quick project overview."""
323
+ try:
324
+ async with acquire_db() as db:
325
+ # The 60s TTLCache is a transport concern living in the HTTP adapter;
326
+ # the MCP surface computes the slices fresh each call (pass no cache).
327
+ bundle, _hotspots, _recent, _saved = await graph_uc.graph_landing(
328
+ LOCAL_CTX,
329
+ db,
330
+ workspace_id=LOCAL_CTX.workspace_id,
331
+ user_id=LOCAL_CTX.user_id,
332
+ )
333
+ return dump(bundle)
334
+ except ServiceError as e:
335
+ raise_mcp_error(e)
336
+
337
+ @mcp.tool()
338
+ async def graph_overview(
339
+ level: OverviewLevel,
340
+ scope: Annotated[str, Field(max_length=256)] | None = None,
341
+ cross_project: bool = True,
342
+ ) -> dict[str, Any]:
343
+ """Get a LOD (level-of-detail) overview of the knowledge graph at macro or module level. macro = project hub nodes + aggregated cross-project edges. module = module/folder nodes within a scope. RBAC-filtered — cross-project edges to invisible projects are omitted with hidden_cross_project_count."""
344
+ try:
345
+ async with acquire_db() as db:
346
+ # visible_projects=None + a LOCAL no-op edge filter (single-user sees
347
+ # all). filter_visible_edges (api.visibility) imports fastapi and must
348
+ # NOT enter the MCP import path; the local injection keeps it out.
349
+ # limit mirrors the HTTP Query default (300).
350
+ result = await graph_uc.graph_overview(
351
+ LOCAL_CTX,
352
+ db,
353
+ level=level,
354
+ scope=scope,
355
+ cross_project=cross_project,
356
+ limit=300,
357
+ user=LOCAL_CTX,
358
+ visible_projects=None,
359
+ filter_visible_edges=_local_no_op_edge_filter,
360
+ )
361
+ return dump(result)
362
+ except ServiceError as e:
363
+ raise_mcp_error(e)
364
+
365
+ @mcp.tool()
366
+ async def graph_orphans(
367
+ scope: Annotated[
368
+ str, Field(min_length=1, max_length=256, pattern=_ORPHAN_SCOPE_PATTERN)
369
+ ],
370
+ ) -> dict[str, Any]:
371
+ """Find file nodes with no edges (orphans) within a project or module scope. Orphans are grouped by folder with deterministic colors. Useful for identifying dead code, stale docs, or unlinked files. Each sub-cluster is capped at 30 files (overflow_count reflects the rest)."""
372
+ try:
373
+ async with acquire_db() as db:
374
+ # visible_projects=None -> local single-user sees all (DECISION 1).
375
+ result = await graph_uc.graph_orphans(
376
+ LOCAL_CTX, db, scope=scope, visible_projects=None,
377
+ )
378
+ return dump(result)
379
+ except ServiceError as e:
380
+ raise_mcp_error(e)
@@ -0,0 +1,112 @@
1
+ # v1.0.0 - 2026-05-27 - S1 F3.1a: handoffs MCP tool group (use_cases-direct, no HTTP)
2
+ """Handoffs MCP tools — port of the Node ``handoffs`` group, use_cases-direct.
3
+
4
+ Same template as ``tasks.py`` / ``learnings.py``: the Node HTTP proxy is replaced
5
+ by an in-process ``await <uc>.<action>(LOCAL_CTX, db, ...)``. Docstrings copied
6
+ VERBATIM from ``core/mcp-pir/index.mjs``.
7
+
8
+ Name mapping (Node tool name -> use_case function):
9
+ * ``list_handoffs`` -> ``projects_uc.get_handoffs`` (lives in the projects
10
+ use_case — handoff listing is a project-index read, mirroring the Node route
11
+ ``/api/v1/projects/<slug>/handoffs``).
12
+ * ``search_handoffs`` -> ``handoffs_uc.search_handoffs`` (needs BOTH a read ``db``
13
+ and a ``vec_db``; the HTTP router injects ``vec_db`` via ``Depends(get_vec_db)``,
14
+ so the MCP surface wraps the same ``get_vec_db`` generator as a context manager
15
+ FUNCTION-LOCALLY — no fastapi, just the sqlite-vec connection).
16
+ * ``get_handoff`` -> ``handoffs_uc.get_handoff`` (read ``db`` only).
17
+
18
+ Deep KG enrichment is DEFERRED (same as F3.0): ``get_handoff`` returns the core
19
+ fetch with ``kg_context=None`` and ignores ``deep`` (adapter concern, later F3
20
+ increment).
21
+
22
+ Return typing: reads return ``dict[str, Any]`` / ``list[dict]`` via ``dump()``
23
+ (DTO lists 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 handoffs as handoffs_uc
34
+ from core.api.use_cases import projects as projects_uc
35
+ from core.api.use_cases._errors import ServiceError
36
+
37
+
38
+ def register(mcp) -> None:
39
+ """Register the handoffs tool group on the shared FastMCP instance."""
40
+
41
+ @mcp.tool()
42
+ async def list_handoffs(
43
+ slug: Annotated[str, Field(min_length=1, max_length=50)],
44
+ ) -> list[dict[str, Any]]:
45
+ """List handoff files for one project (frontmatter metadata only, no body).
46
+
47
+ QUANDO USARLO: browse chronological session history per progetto noto (es. quali sessioni su marvisx ultima settimana). BOUNDARY: session_brief vs list_tasks+list_handoffs = session_brief aggrega tutto in una call.
48
+ QUANDO NON USARLO: NOT per keyword search cross-project -> usa search_handoffs. NOT per cold-start -> usa session_brief.
49
+ RESTITUISCE: list of {session_id, branch, tags, date, path} ordered chronological."""
50
+ try:
51
+ async with acquire_db() as db:
52
+ # Node route is /projects/<slug>/handoffs -> projects_uc.get_handoffs.
53
+ result = await projects_uc.get_handoffs(
54
+ LOCAL_CTX, db, slug=slug, visible_projects=None
55
+ )
56
+ return dump(result)
57
+ except ServiceError as e:
58
+ raise_mcp_error(e)
59
+
60
+ @mcp.tool()
61
+ async def search_handoffs(
62
+ q: Annotated[str, Field(min_length=1, max_length=500)],
63
+ ) -> list[dict[str, Any]]:
64
+ """Full-text keyword search across every handoff body in every project.
65
+
66
+ QUANDO USARLO: ricordi un termine ma non il progetto/data (es. 'find handoffs mentioning migration 025'). Exact keyword match, NOT semantic.
67
+ QUANDO NON USARLO: NOT per semantic discovery multi-type -> usa search. NOT se conosci il progetto -> usa list_handoffs.
68
+ RESTITUISCE: list of {project, session_id, path, snippet, date} con match literal."""
69
+ # search_handoffs needs both a read `db` and a `vec_db`. The HTTP router
70
+ # injects vec_db via Depends(get_vec_db); on the MCP surface we wrap the
71
+ # SAME get_vec_db async-generator as a context manager FUNCTION-LOCALLY
72
+ # (fastapi-free: get_vec_db is a plain aiosqlite connection helper).
73
+ from contextlib import asynccontextmanager
74
+
75
+ from core.api.db import get_vec_db
76
+
77
+ acquire_vec_db = asynccontextmanager(get_vec_db)
78
+ try:
79
+ async with acquire_db() as db, acquire_vec_db() as vec_db:
80
+ result = await handoffs_uc.search_handoffs(
81
+ LOCAL_CTX, db, vec_db, q=q, visible_projects=None
82
+ )
83
+ return dump(result)
84
+ except ServiceError as e:
85
+ raise_mcp_error(e)
86
+
87
+ @mcp.tool()
88
+ async def get_handoff(
89
+ project_slug: Annotated[str, Field(min_length=1, max_length=63)],
90
+ filename: Annotated[str, Field(min_length=1, max_length=200)],
91
+ deep: bool | None = None,
92
+ ) -> dict[str, Any]:
93
+ """Get a single handoff file by project and filename.
94
+
95
+ QUANDO USARLO: hai project_slug + filename da list_handoffs/search_handoffs e vuoi il body completo + frontmatter. Usa ?deep=true per kg_context inline (references, mentions, context chain).
96
+ QUANDO NON USARLO: NOT per ricerca testuale -> usa search_handoffs. NOT per lista handoff di progetto -> usa list_handoffs.
97
+ RESTITUISCE: {project, file, frontmatter, body, kg_context?}."""
98
+ try:
99
+ async with acquire_db() as db:
100
+ # DECISION 2: `deep` KG enrichment is an adapter concern; the
101
+ # use_case returns kg_context=None (core fetch). Deep-attach lands
102
+ # in a later F3 increment, same as get_task/get_project in F3.0.
103
+ result = await handoffs_uc.get_handoff(
104
+ LOCAL_CTX,
105
+ db,
106
+ project_slug=project_slug,
107
+ filename=filename,
108
+ visible_projects=None,
109
+ )
110
+ return dump(result)
111
+ except ServiceError as e:
112
+ raise_mcp_error(e)