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.
- core/api/__init__.py +0 -0
- core/api/agents/__init__.py +0 -0
- core/api/agents/session_health.py +59 -0
- core/api/agents/session_manager.py +206 -0
- core/api/bin/marvisx-state-hook.py +182 -0
- core/api/config.py +533 -0
- core/api/db.py +1516 -0
- core/api/dependencies/__init__.py +0 -0
- core/api/dependencies/tenant.py +34 -0
- core/api/main.py +1641 -0
- core/api/mcp/__init__.py +8 -0
- core/api/mcp/_adapter.py +184 -0
- core/api/mcp/server.py +58 -0
- core/api/mcp/tools/__init__.py +59 -0
- core/api/mcp/tools/brain.py +599 -0
- core/api/mcp/tools/graph.py +380 -0
- core/api/mcp/tools/handoffs.py +112 -0
- core/api/mcp/tools/ingest.py +326 -0
- core/api/mcp/tools/learnings.py +144 -0
- core/api/mcp/tools/projects.py +99 -0
- core/api/mcp/tools/pull_requests.py +173 -0
- core/api/mcp/tools/safety.py +111 -0
- core/api/mcp/tools/search.py +79 -0
- core/api/mcp/tools/tasks.py +258 -0
- core/api/middleware/__init__.py +0 -0
- core/api/middleware/tool_call_audit.py +111 -0
- core/api/models/__init__.py +346 -0
- core/api/models/auth.py +48 -0
- core/api/models/brain.py +1006 -0
- core/api/models/common.py +76 -0
- core/api/models/costs.py +91 -0
- core/api/models/graph.py +66 -0
- core/api/models/graph_cosmo.py +125 -0
- core/api/models/graph_pr_impact.py +257 -0
- core/api/models/graph_ux.py +141 -0
- core/api/models/inbox.py +230 -0
- core/api/models/ingest_keys.py +108 -0
- core/api/models/kg.py +41 -0
- core/api/models/llm_config.py +56 -0
- core/api/models/monitoring.py +234 -0
- core/api/models/projects.py +161 -0
- core/api/models/search.py +42 -0
- core/api/models/sessions.py +322 -0
- core/api/models/tasks.py +184 -0
- core/api/models/teams.py +63 -0
- core/api/models/users.py +184 -0
- core/api/observability/__init__.py +0 -0
- core/api/observability/tracing.py +92 -0
- core/api/paths.py +26 -0
- core/api/rate_limit.py +24 -0
- core/api/rbac.py +112 -0
- core/api/routers/__init__.py +0 -0
- core/api/routers/_adapter.py +24 -0
- core/api/routers/admin_pr_impact.py +230 -0
- core/api/routers/admin_settings.py +147 -0
- core/api/routers/agent.py +1079 -0
- core/api/routers/agent_tokens.py +276 -0
- core/api/routers/app_settings.py +112 -0
- core/api/routers/audit.py +89 -0
- core/api/routers/auth.py +586 -0
- core/api/routers/bench.py +161 -0
- core/api/routers/brain.py +881 -0
- core/api/routers/brain_directions.py +527 -0
- core/api/routers/ci_checks.py +140 -0
- core/api/routers/comments.py +273 -0
- core/api/routers/costs.py +148 -0
- core/api/routers/docs_coverage.py +217 -0
- core/api/routers/docs_governance.py +63 -0
- core/api/routers/documents.py +318 -0
- core/api/routers/files.py +163 -0
- core/api/routers/finder.py +987 -0
- core/api/routers/graph.py +836 -0
- core/api/routers/handoffs.py +156 -0
- core/api/routers/inbox.py +496 -0
- core/api/routers/ingest_api_keys.py +205 -0
- core/api/routers/ingest_triage.py +1227 -0
- core/api/routers/judge.py +306 -0
- core/api/routers/kg.py +336 -0
- core/api/routers/learnings.py +253 -0
- core/api/routers/llm_config.py +130 -0
- core/api/routers/monitoring.py +347 -0
- core/api/routers/notifications.py +125 -0
- core/api/routers/pr_impact.py +315 -0
- core/api/routers/projects.py +1061 -0
- core/api/routers/pull_requests.py +312 -0
- core/api/routers/push.py +67 -0
- core/api/routers/raci.py +228 -0
- core/api/routers/search.py +125 -0
- core/api/routers/sessions.py +3100 -0
- core/api/routers/settings.py +90 -0
- core/api/routers/share_repo.py +68 -0
- core/api/routers/status_updates.py +96 -0
- core/api/routers/tags.py +45 -0
- core/api/routers/tasks.py +526 -0
- core/api/routers/teams.py +425 -0
- core/api/routers/terminal.py +105 -0
- core/api/routers/users.py +331 -0
- core/api/routers/webhooks.py +330 -0
- core/api/runtime_settings.py +84 -0
- core/api/security.py +652 -0
- core/api/services/__init__.py +0 -0
- core/api/services/audit.py +58 -0
- core/api/services/auto_approval.py +82 -0
- core/api/services/brain/__init__.py +52 -0
- core/api/services/brain/baseline.py +230 -0
- core/api/services/brain/capabilities.py +75 -0
- core/api/services/brain/cascade_rollup.py +388 -0
- core/api/services/brain/compound_bridge.py +215 -0
- core/api/services/brain/cycle.py +1242 -0
- core/api/services/brain/cycle_snapshot.py +371 -0
- core/api/services/brain/digest_collector.py +147 -0
- core/api/services/brain/direction.py +421 -0
- core/api/services/brain/drift.py +356 -0
- core/api/services/brain/drift_router.py +409 -0
- core/api/services/brain/edge_metrics.py +79 -0
- core/api/services/brain/events_reader.py +222 -0
- core/api/services/brain/findings.py +1379 -0
- core/api/services/brain/findings_reader.py +1006 -0
- core/api/services/brain/jobs.py +733 -0
- core/api/services/brain/journal.py +206 -0
- core/api/services/brain/knowledge_forms.py +92 -0
- core/api/services/brain/llm/__init__.py +37 -0
- core/api/services/brain/llm/_runner.py +62 -0
- core/api/services/brain/llm/base.py +70 -0
- core/api/services/brain/llm/cache.py +99 -0
- core/api/services/brain/llm/constants.py +46 -0
- core/api/services/brain/llm/direction_alignment.py +289 -0
- core/api/services/brain/llm/factory.py +132 -0
- core/api/services/brain/llm/finding_reasoning.py +98 -0
- core/api/services/brain/llm/finding_summary.py +92 -0
- core/api/services/brain/llm/grounding.py +46 -0
- core/api/services/brain/llm/journal_polish.py +96 -0
- core/api/services/brain/llm/local_gateway.py +426 -0
- core/api/services/brain/llm/parsers.py +71 -0
- core/api/services/brain/llm/router_glue.py +422 -0
- core/api/services/brain/memory_ops.py +1677 -0
- core/api/services/brain/models.py +140 -0
- core/api/services/brain/owner_hint.py +211 -0
- core/api/services/brain/recap.py +307 -0
- core/api/services/brain/rules/__init__.py +65 -0
- core/api/services/brain/rules/_signals.py +205 -0
- core/api/services/brain/rules/dr1_activity_without_status.py +108 -0
- core/api/services/brain/rules/dr2_decision_without_adr.py +110 -0
- core/api/services/brain/rules/dr3_stale_open_loop.py +127 -0
- core/api/services/brain/rules/dr4_docs_governance_drift.py +79 -0
- core/api/services/brain/rules/dr5_playbook_changed.py +99 -0
- core/api/services/brain/rules/dr6_external_update_unpropagated.py +100 -0
- core/api/services/brain/rules/dr7_claimed_decision_gap.py +123 -0
- core/api/services/brain/rules/dr8_direction_misalignment.py +230 -0
- core/api/services/brain/runs_reader.py +485 -0
- core/api/services/brain/scope.py +79 -0
- core/api/services/brain/sources/__init__.py +46 -0
- core/api/services/brain/sources/base.py +86 -0
- core/api/services/brain/sources/git_kg.py +393 -0
- core/api/services/brain/sources/handoffs.py +157 -0
- core/api/services/brain/sources/ingestor.py +130 -0
- core/api/services/brain/sources/learnings.py +121 -0
- core/api/services/brain/sources/pir_tasks.py +245 -0
- core/api/services/brain/watermarks.py +147 -0
- core/api/services/brain/ws_emitter.py +170 -0
- core/api/services/cc_tasks_reader.py +76 -0
- core/api/services/ci_service.py +263 -0
- core/api/services/claude_metrics.py +796 -0
- core/api/services/codex_metrics.py +364 -0
- core/api/services/conversation_reader.py +102 -0
- core/api/services/cost_service.py +243 -0
- core/api/services/crypto.py +147 -0
- core/api/services/docs_governance/__init__.py +1 -0
- core/api/services/docs_governance/confidence.py +230 -0
- core/api/services/docs_governance/config.py +87 -0
- core/api/services/docs_governance/enrichment.py +65 -0
- core/api/services/docs_governance/frontmatter_validator.py +83 -0
- core/api/services/docs_governance/hard_gates.py +221 -0
- core/api/services/docs_governance/triage_orchestrator.py +98 -0
- core/api/services/embedding_internal.py +395 -0
- core/api/services/embedding_service.py +832 -0
- core/api/services/event_dispatcher.py +167 -0
- core/api/services/events.py +70 -0
- core/api/services/git_ops.py +621 -0
- core/api/services/graph_cosmo_service.py +440 -0
- core/api/services/graph_ranker.py +306 -0
- core/api/services/graph_service.py +1589 -0
- core/api/services/inbox.py +800 -0
- core/api/services/inbox_digest.py +221 -0
- core/api/services/inbox_digest_deep_research.py +80 -0
- core/api/services/inbox_digest_jobs.py +595 -0
- core/api/services/inbox_gmail_sync.py +167 -0
- core/api/services/inbox_llm_classifier.py +906 -0
- core/api/services/inbox_source_identity.py +116 -0
- core/api/services/inbox_sources.py +456 -0
- core/api/services/inbox_taxonomy.py +195 -0
- core/api/services/inbox_tldr.py +1079 -0
- core/api/services/inbox_triage.py +899 -0
- core/api/services/ingest/__init__.py +13 -0
- core/api/services/ingest/api_key_auth.py +136 -0
- core/api/services/ingest/auto_approve.py +120 -0
- core/api/services/ingest/classifier.py +138 -0
- core/api/services/ingest/confidence.py +173 -0
- core/api/services/ingest/dispatch.py +88 -0
- core/api/services/ingest/embedding_router.py +272 -0
- core/api/services/ingest/events.py +33 -0
- core/api/services/ingest/ignore_patterns.py +79 -0
- core/api/services/ingest/image_probe.py +218 -0
- core/api/services/ingest/ingress.py +263 -0
- core/api/services/ingest/insert_saga.py +793 -0
- core/api/services/ingest/llm/__init__.py +13 -0
- core/api/services/ingest/llm/anthropic_haiku.py +23 -0
- core/api/services/ingest/llm/base.py +59 -0
- core/api/services/ingest/llm/byok_provider.py +130 -0
- core/api/services/ingest/llm/classification_context.py +301 -0
- core/api/services/ingest/llm/config_store.py +246 -0
- core/api/services/ingest/llm/factory.py +24 -0
- core/api/services/ingest/llm/kg_enricher.py +306 -0
- core/api/services/ingest/llm/local_gateway.py +821 -0
- core/api/services/ingest/llm/local_vllm.py +23 -0
- core/api/services/ingest/llm/openai_nano.py +349 -0
- core/api/services/ingest/lock_advisory.py +57 -0
- core/api/services/ingest/parser_router.py +1756 -0
- core/api/services/ingest/parsers/__init__.py +1 -0
- core/api/services/ingest/parsers/docling_parser.py +142 -0
- core/api/services/ingest/parsers/docparse_gateway.py +178 -0
- core/api/services/ingest/parsers/docx_parser.py +127 -0
- core/api/services/ingest/parsers/folder_unpacker.py +85 -0
- core/api/services/ingest/parsers/gateway_aux.py +147 -0
- core/api/services/ingest/parsers/image_parser.py +251 -0
- core/api/services/ingest/parsers/internal_markdown.py +89 -0
- core/api/services/ingest/parsers/ocr_gateway.py +117 -0
- core/api/services/ingest/parsers/ocr_pdf_parser.py +112 -0
- core/api/services/ingest/parsers/pdf_types.py +13 -0
- core/api/services/ingest/parsers/transcript_parser.py +445 -0
- core/api/services/ingest/parsers/vision_gateway.py +186 -0
- core/api/services/ingest/parsers/xlsx_parser.py +91 -0
- core/api/services/ingest/parsers/zip_unpacker.py +126 -0
- core/api/services/ingest/preflight.py +393 -0
- core/api/services/ingest/retry_voyage.py +88 -0
- core/api/services/ingest/routing_policy.py +307 -0
- core/api/services/ingest/serializers/__init__.py +1 -0
- core/api/services/ingest/serializers/xlsx_to_markdown.py +80 -0
- core/api/services/ingest/skip_log.py +74 -0
- core/api/services/ingest/watcher.py +637 -0
- core/api/services/kg/__init__.py +0 -0
- core/api/services/kg/audit.py +49 -0
- core/api/services/kg/hybrid_search.py +691 -0
- core/api/services/kg/lens.py +339 -0
- core/api/services/kg/pr_impact.py +770 -0
- core/api/services/kg/queries.py +152 -0
- core/api/services/kg/ranking.py +89 -0
- core/api/services/kg/rrf.py +143 -0
- core/api/services/kg_watcher_control.py +161 -0
- core/api/services/local_llm/__init__.py +19 -0
- core/api/services/local_llm/async_client.py +385 -0
- core/api/services/local_llm/client.py +173 -0
- core/api/services/local_llm/url_validator.py +44 -0
- core/api/services/metrics_collector.py +646 -0
- core/api/services/metrics_providers.py +65 -0
- core/api/services/model_registry.py +266 -0
- core/api/services/model_router.py +137 -0
- core/api/services/n8n_client.py +77 -0
- core/api/services/newsletter_llm_gateway.py +66 -0
- core/api/services/notification_service.py +134 -0
- core/api/services/openai_responses.py +55 -0
- core/api/services/opencode_metrics.py +375 -0
- core/api/services/opencode_sessions.py +173 -0
- core/api/services/pii_redactor.py +138 -0
- core/api/services/pr_impact_pipeline/__init__.py +21 -0
- core/api/services/pr_impact_pipeline/differ.py +421 -0
- core/api/services/pr_impact_pipeline/dispatcher.py +415 -0
- core/api/services/pr_impact_pipeline/gc.py +93 -0
- core/api/services/pr_impact_pipeline/languages.py +192 -0
- core/api/services/pr_impact_pipeline/parser.py +178 -0
- core/api/services/pr_impact_pipeline/writer.py +394 -0
- core/api/services/pr_service.py +1393 -0
- core/api/services/project_paths.py +70 -0
- core/api/services/project_status_updates.py +265 -0
- core/api/services/providers.py +276 -0
- core/api/services/push_service.py +170 -0
- core/api/services/reminder_service.py +89 -0
- core/api/services/runas.py +41 -0
- core/api/services/salience_service.py +69 -0
- core/api/services/security_collector.py +281 -0
- core/api/services/session_catalog.py +385 -0
- core/api/services/session_metrics_service.py +301 -0
- core/api/services/session_ops.py +272 -0
- core/api/services/session_state.py +173 -0
- core/api/services/share_links.py +222 -0
- core/api/services/task_transitions.py +146 -0
- core/api/services/terminal_metrics.py +462 -0
- core/api/services/terminal_metrics_dump.py +203 -0
- core/api/services/tmux.py +1205 -0
- core/api/services/webhook_service.py +422 -0
- core/api/services/workspace_sync.py +164 -0
- core/api/templates/__init__.py +1 -0
- core/api/templates/markdown_share.py +164 -0
- core/api/terminal.py +1031 -0
- core/api/tests/__init__.py +0 -0
- core/api/tests/test_agent_facing_auth_dependencies.py +132 -0
- core/api/tests/test_audit_permissions.py +133 -0
- core/api/tests/test_backfill_session_conversations.py +90 -0
- core/api/tests/test_backfill_working_seconds_msg.py +129 -0
- core/api/tests/test_claude_metrics.py +326 -0
- core/api/tests/test_codex_metrics.py +189 -0
- core/api/tests/test_finder_paths.py +74 -0
- core/api/tests/test_git_ops_merge.py +155 -0
- core/api/tests/test_learnings_check_search.py +81 -0
- core/api/tests/test_metrics_providers.py +133 -0
- core/api/tests/test_migration_087.py +164 -0
- core/api/tests/test_migration_088.py +94 -0
- core/api/tests/test_migration_089.py +116 -0
- core/api/tests/test_openai_responses.py +24 -0
- core/api/tests/test_opencode_metrics.py +740 -0
- core/api/tests/test_opencode_sessions.py +321 -0
- core/api/tests/test_pr_workflow_e2e.py +457 -0
- core/api/tests/test_projects_handoffs.py +31 -0
- core/api/tests/test_providers.py +138 -0
- core/api/tests/test_safety_bridge.py +347 -0
- core/api/tests/test_session_catalog.py +142 -0
- core/api/tests/test_session_conversations.py +512 -0
- core/api/tests/test_session_metrics_service.py +270 -0
- core/api/tests/test_session_resume_paths.py +548 -0
- core/api/tests/test_session_theme_mode_migration.py +56 -0
- core/api/tests/test_sessions_rbac.py +131 -0
- core/api/tests/test_share_edit.py +398 -0
- core/api/tests/test_share_repo.py +200 -0
- core/api/tests/test_terminal_session_manager.py +98 -0
- core/api/tests/test_terminal_upload.py +34 -0
- core/api/tests/test_tmux.py +272 -0
- core/api/tests/test_workspace_sync.py +186 -0
- core/api/tests/test_ws_ticket_in_memory.py +73 -0
- core/api/use_cases/__init__.py +11 -0
- core/api/use_cases/_context.py +89 -0
- core/api/use_cases/_errors.py +62 -0
- core/api/use_cases/_roles.py +16 -0
- core/api/use_cases/audit.py +171 -0
- core/api/use_cases/brain.py +1232 -0
- core/api/use_cases/costs.py +249 -0
- core/api/use_cases/graph.py +1153 -0
- core/api/use_cases/handoffs.py +506 -0
- core/api/use_cases/ingest_triage.py +1229 -0
- core/api/use_cases/learnings.py +538 -0
- core/api/use_cases/projects.py +705 -0
- core/api/use_cases/pull_requests.py +415 -0
- core/api/use_cases/search.py +926 -0
- core/api/use_cases/tasks.py +1495 -0
- core/api/visibility.py +141 -0
- core/cli/__init__.py +5 -0
- core/cli/_index_source.py +632 -0
- core/cli/_runtime_ctx.py +160 -0
- core/cli/_transmute.py +241 -0
- core/cli/marvis_doctor.py +704 -0
- core/cli/marvis_feedback.py +396 -0
- core/cli/marvis_governance.py +315 -0
- core/cli/marvis_hooks.py +515 -0
- core/cli/marvis_init.py +757 -0
- core/cli/marvis_mcp.py +401 -0
- core/cli/marvis_runtime.py +855 -0
- core/cli/marvis_telemetry.py +228 -0
- core/scripts/_drift_check.py +716 -0
- core/scripts/_frontmatter.py +66 -0
- core/scripts/_graph_writer.py +189 -0
- core/scripts/ast_parser.py +1553 -0
- core/scripts/install_hooks/__init__.py +1 -0
- core/scripts/install_hooks/_config.sh +109 -0
- core/scripts/install_hooks/block-dangerous-bash.sh +23 -0
- core/scripts/install_hooks/block-db-direct-write.sh +23 -0
- core/scripts/install_hooks/block-push-no-task.sh +23 -0
- core/scripts/install_hooks/block-staging-to-prod.sh +23 -0
- core/scripts/install_hooks/block-subtree-push.sh +23 -0
- core/scripts/install_hooks/config.json +53 -0
- core/scripts/install_hooks/enforce-no-merge-main.sh +23 -0
- core/scripts/install_hooks/enforce-worktree.sh +23 -0
- core/scripts/install_hooks/quality-gate.sh +170 -0
- core/scripts/install_hooks/safety_bridge.py +968 -0
- core/scripts/install_hooks/secret-scan.sh +23 -0
- core/scripts/migrate_spike_node_ids.py +122 -0
- core/scripts/populate_artifacts.py +2198 -0
- core/scripts/populate_cross_project.py +2457 -0
- core/scripts/populate_inbox_nodes.py +357 -0
- core/scripts/populate_pr_impact.py +267 -0
- core/scripts/populate_project_nodes.py +603 -0
- core/scripts/populate_touch_counter.py +337 -0
- core/scripts/reparse_failed.py +57 -0
- core/scripts/safety_bridge.py +968 -0
- core/telemetry/__init__.py +9 -0
- core/telemetry/client.py +405 -0
- core/telemetry/schema.py +122 -0
- core/wizard/__init__.py +65 -0
- core/wizard/byok_vault.py +147 -0
- core/wizard/defaults.py +58 -0
- core/wizard/state.py +117 -0
- core/wizard/steps.py +70 -0
- core/wizard/validation.py +136 -0
- marvisx_cli-0.1.0.dist-info/METADATA +201 -0
- marvisx_cli-0.1.0.dist-info/RECORD +587 -0
- marvisx_cli-0.1.0.dist-info/WHEEL +5 -0
- marvisx_cli-0.1.0.dist-info/entry_points.txt +3 -0
- marvisx_cli-0.1.0.dist-info/licenses/LICENSE +98 -0
- marvisx_cli-0.1.0.dist-info/top_level.txt +3 -0
- migrations/001_initial.sql +33 -0
- migrations/002_tasks.sql +30 -0
- migrations/003_session_management.sql +7 -0
- migrations/004_projects_comments.sql +65 -0
- migrations/005_session_intelligence.sql +15 -0
- migrations/006_settings.sql +12 -0
- migrations/007_task_scoring.sql +12 -0
- migrations/008_cost_tracking.sql +31 -0
- migrations/009_session_card_metrics.sql +3 -0
- migrations/010_monitoring.sql +55 -0
- migrations/012_agent_api.sql +21 -0
- migrations/013_session_complete.sql +8 -0
- migrations/015_pull_requests.sql +43 -0
- migrations/015_pull_requests_down.sql +5 -0
- migrations/016_users_raci.sql +116 -0
- migrations/017_task_cost_entries.sql +87 -0
- migrations/018_agents.sql +73 -0
- migrations/018_agents_down.sql +13 -0
- migrations/019_review_feedback.sql +18 -0
- migrations/020_pr_commit_sha.sql +4 -0
- migrations/021_webhook_events.sql +18 -0
- migrations/022_devx_agent_managed.sql +11 -0
- migrations/022_devx_agent_managed_down.sql +6 -0
- migrations/023_devx_p1_gate.sql +7 -0
- migrations/023_devx_p1_gate_down.sql +3 -0
- migrations/024_chat_messages.sql +16 -0
- migrations/024_pr_conversation_id.sql +8 -0
- migrations/024_task_indexes.sql +21 -0
- migrations/024_task_indexes_down.sql +7 -0
- migrations/025_audit_log.sql +17 -0
- migrations/026_agent_tokens.sql +20 -0
- migrations/027_teams_auth_phase_b.sql +35 -0
- migrations/028_learnings.sql +23 -0
- migrations/029_team_roles.sql +14 -0
- migrations/030_finder_pins.sql +10 -0
- migrations/031_pr_deploy_status.sql +9 -0
- migrations/032_task_reminders.sql +7 -0
- migrations/033_events_retry_count.sql +6 -0
- migrations/033_session_owner.sql +9 -0
- migrations/034_notifications.sql +38 -0
- migrations/035_shared_links.sql +15 -0
- migrations/036_session_index_upgrade.sql +29 -0
- migrations/037_pr_approval.sql +15 -0
- migrations/038_pr_submitted_by.sql +6 -0
- migrations/039_push_subscriptions.sql +17 -0
- migrations/040_semantic_search.sql +16 -0
- migrations/041_workspaces.sql +63 -0
- migrations/042_oidc_providers.sql +24 -0
- migrations/043_ci_checks.sql +31 -0
- migrations/044_agent_metrics.sql +30 -0
- migrations/045_documents_doc_type.sql +5 -0
- migrations/046_salience.sql +13 -0
- migrations/047_seed_missing_agents.sql +6 -0
- migrations/048_fix_agent_paths_roles.sql +5 -0
- migrations/049_agent_role_and_learnings_schema.sql +3 -0
- migrations/050_session_provider.sql +2 -0
- migrations/051_session_launch_profile.sql +4 -0
- migrations/052_session_theme_mode.sql +2 -0
- migrations/052_task_kind.sql +4 -0
- migrations/053_inbox_items.sql +31 -0
- migrations/054_inbox_triage_contract.sql +30 -0
- migrations/055_inbox_topic_treatment.sql +12 -0
- migrations/056_inbox_treatment_read_save.sql +57 -0
- migrations/057_session_theme_mode_backfill.sql +4 -0
- migrations/058_inbox_item_status_lifecycle.sql +13 -0
- migrations/059_inbox_tldr_and_source_scores.sql +18 -0
- migrations/060_newsletter.sql +16 -0
- migrations/061_inbox_redesign.sql +69 -0
- migrations/062_fix_inbox_sources_backfill.sql +37 -0
- migrations/063_task_completion_mode.sql +23 -0
- migrations/064_judge_mode_setting.sql +4 -0
- migrations/065_knowledge_graph_spike.sql +40 -0
- migrations/066_digest_ranking_inputs.sql +10 -0
- migrations/066_kg_artifact_nodes.sql +129 -0
- migrations/067_inbox_digest_selections.sql +28 -0
- migrations/067_kg_temporal.sql +53 -0
- migrations/068_inbox_digest_app_settings.sql +9 -0
- migrations/068_kg_touch_counter.sql +52 -0
- migrations/069_kg_doc_types.sql +117 -0
- migrations/070_digest_ranking_inputs_recovery.sql +3 -0
- migrations/071_inbox_digest_selections_recovery.sql +3 -0
- migrations/072_inbox_digest_app_settings_recovery.sql +3 -0
- migrations/073_kg_cross_project.sql +216 -0
- migrations/073_kg_cross_project_down.sql +77 -0
- migrations/074_kg_infra_types.sql +208 -0
- migrations/074_kg_infra_types_down.sql +80 -0
- migrations/075_kg_file_state_recovery.sql +35 -0
- migrations/075_kg_file_state_recovery_down.sql +5 -0
- migrations/076_kg_watcher_state.sql +33 -0
- migrations/076_kg_watcher_state_down.sql +3 -0
- migrations/077_kg_doc_types_extend.sql +226 -0
- migrations/077_kg_doc_types_extend_down.sql +80 -0
- migrations/078_kg_fts5.sql +102 -0
- migrations/078_kg_fts5_down.sql +14 -0
- migrations/079_kg_missing_indexes.sql +31 -0
- migrations/079_kg_missing_indexes_down.sql +10 -0
- migrations/080_kg_fts5_extended.sql +232 -0
- migrations/080_kg_fts5_extended_down.sql +25 -0
- migrations/081_kg_lens_indexes.sql +9 -0
- migrations/081_kg_lens_indexes_down.sql +3 -0
- migrations/082_kg_pins.sql +26 -0
- migrations/082_kg_pins_down.sql +14 -0
- migrations/083_kg_graph_nodes_degree.sql +20 -0
- migrations/083_kg_graph_nodes_degree_down.sql +15 -0
- migrations/084_drop_legacy_scheduler_tables.sql +58 -0
- migrations/084_drop_legacy_scheduler_tables_down.sql +112 -0
- migrations/085_kg_edge_resolves_to.sql +142 -0
- migrations/085_kg_edge_resolves_to_down.sql +66 -0
- migrations/086_project_status_updates_feed.sql +20 -0
- migrations/086_project_status_updates_feed_down.sql +36 -0
- migrations/087_session_metrics_dual.sql +50 -0
- migrations/087_session_metrics_dual_down.sql +21 -0
- migrations/088_rename_context_pct_legacy.sql +23 -0
- migrations/088_rename_context_pct_legacy_down.sql +8 -0
- migrations/089_session_metrics_equivalent_cost.sql +26 -0
- migrations/089_session_metrics_equivalent_cost_down.sql +11 -0
- migrations/090_kg_inbox_node_type.sql +26 -0
- migrations/090_kg_inbox_node_type_down.sql +20 -0
- migrations/091_kg_inbox_node_type_check.sql +265 -0
- migrations/091_kg_inbox_node_type_check_down.sql +129 -0
- migrations/092_sessions_activity_state_ts.sql +29 -0
- migrations/092_sessions_activity_state_ts_down.sql +14 -0
- migrations/093_sessions_activity_state_column.sql +29 -0
- migrations/093_sessions_activity_state_column_down.sql +10 -0
- migrations/094_ingest_pending.sql +55 -0
- migrations/094_ingest_pending_down.sql +15 -0
- migrations/095_kg_intent_first.sql +77 -0
- migrations/095_kg_intent_first_down.sql +25 -0
- migrations/096_kg_xlsx_artifact_prefix.sql +17 -0
- migrations/096_kg_xlsx_artifact_prefix_down.sql +11 -0
- migrations/097_ingest_change_history.sql +37 -0
- migrations/097_ingest_change_history_down.sql +13 -0
- migrations/098_kg_node_type_business.sql +254 -0
- migrations/098_kg_node_type_business_down.sql +195 -0
- migrations/099_kg_edges_restore_weight.sql +58 -0
- migrations/099_kg_edges_restore_weight_down.sql +12 -0
- migrations/100_kg_enriched_at.sql +25 -0
- migrations/100_kg_enriched_at_down.sql +12 -0
- migrations/101_local_llm_shadow_comparisons.sql +66 -0
- migrations/101_local_llm_shadow_comparisons_down.sql +15 -0
- migrations/102_promote_llm_costs.sql +69 -0
- migrations/102_promote_llm_costs_down.sql +19 -0
- migrations/103_ingest_skipped_log.sql +46 -0
- migrations/103_ingest_skipped_log_down.sql +15 -0
- migrations/120_docs_governance.sql +50 -0
- migrations/120_docs_governance_down.sql +11 -0
- migrations/121_notification_event_fk_cleanup.sql +21 -0
- migrations/121_notification_event_fk_cleanup_down.sql +10 -0
- migrations/122_docs_drift_history.sql +34 -0
- migrations/122_docs_drift_history_down.sql +15 -0
- migrations/123_ingest_parser_waiting_status.sql +69 -0
- migrations/123_ingest_parser_waiting_status_down.sql +69 -0
- migrations/124_heypocket_recordings.sql +63 -0
- migrations/124_heypocket_recordings_down.sql +13 -0
- migrations/125_kg_node_type_record.sql +219 -0
- migrations/125_kg_node_type_record_down.sql +205 -0
- migrations/126_ingest_terminal_upload_source_kind.sql +69 -0
- migrations/126_ingest_terminal_upload_source_kind_down.sql +69 -0
- migrations/127_brain_v1_substrate.sql +200 -0
- migrations/127_brain_v1_substrate_down.sql +32 -0
- migrations/128_brain_drift_signals.sql +157 -0
- migrations/128_brain_drift_signals_down.sql +23 -0
- migrations/129_brain_memory_operations.sql +232 -0
- migrations/129_brain_memory_operations_down.sql +27 -0
- migrations/130_brain_findings.sql +258 -0
- migrations/130_brain_findings_down.sql +29 -0
- migrations/132_kg_pr_modifies.sql +242 -0
- migrations/132_kg_pr_modifies_down.sql +99 -0
- migrations/133_brain_v1_2_direction_schema.sql +476 -0
- migrations/133_brain_v1_2_direction_schema_down.sql +273 -0
- migrations/134_brain_journal_narrative_polished.sql +8 -0
- migrations/134_brain_journal_narrative_polished_down.sql +6 -0
- migrations/135_kg_edges_provider.sql +21 -0
- migrations/136_documents_fts.sql +56 -0
- migrations/137_promote_llm_costs.sql +59 -0
- migrations/137_promote_llm_costs_down.sql +19 -0
- migrations/138_ingest_api_keys.sql +39 -0
- migrations/138_ingest_api_keys_down.sql +8 -0
- migrations/139_ingest_pending_ingress.sql +91 -0
- migrations/139_ingest_pending_ingress_down.sql +73 -0
- migrations/140_ingest_idempotency_quota.sql +45 -0
- migrations/140_ingest_idempotency_quota_down.sql +9 -0
- migrations/141_ingest_pending_metadata.sql +16 -0
- migrations/141_ingest_pending_metadata_down.sql +7 -0
- migrations/142_llm_function_config.sql +36 -0
- migrations/142_llm_function_config_down.sql +8 -0
- migrations/143_kg_code_embeddings.sql +25 -0
- migrations/143_kg_code_embeddings_down.sql +5 -0
- migrations/__init__.py +4 -0
- 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)
|