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,440 @@
|
|
|
1
|
+
# v1.3.0 - 2026-04-24 - Project.degree = SUM(weight) outgoing aggregate (was COUNT)
|
|
2
|
+
# v1.2.0 - 2026-04-24 - Q2 estesa con sat_items CTE: top-12 SatelliteItem clickable
|
|
3
|
+
# v1.1.0 - 2026-04-24 - SatelliteSummary: Q2 ritorna count + latest_at
|
|
4
|
+
# v1.0.0 - 2026-04-24 - Cosmo canvas data aggregator
|
|
5
|
+
"""Service layer per GET /api/v1/graph/cosmo.
|
|
6
|
+
|
|
7
|
+
3 query SQL (projects / satellites windowed / aggregated edges) dentro
|
|
8
|
+
una single transaction BEGIN DEFERRED → COMMIT per snapshot isolation vs
|
|
9
|
+
populator che gira in parallelo (M-BE-10 piano).
|
|
10
|
+
|
|
11
|
+
`program` NON e' nel metadata KG (populate_project_nodes.py non lo scrive):
|
|
12
|
+
lo joiniamo live da `programs.yaml` tramite _load_programs_map() con cache
|
|
13
|
+
mtime-based (M-BE-03 opzione A).
|
|
14
|
+
|
|
15
|
+
v1.2.0: Q2 estesa con CTE `sat_items` che ritorna top-12 artifact per
|
|
16
|
+
(project, kind) ordinati per latest_at DESC. Ogni item porta id/title/path/
|
|
17
|
+
latest_at/importance per file-dot cliccabili FE. Performance: subquery
|
|
18
|
+
`importance` O(N_items × edge_lookup) — assume `idx_graph_edges_target`
|
|
19
|
+
su `graph_edges.target_id` (verificato in produzione 2026-04-24). Cap teorico
|
|
20
|
+
payload: 71 project × 8 kind × 12 item ≈ 6816 row × ~150 byte ≈ ~1MB.
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import logging
|
|
26
|
+
import re
|
|
27
|
+
import threading
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
import aiosqlite
|
|
31
|
+
import yaml
|
|
32
|
+
|
|
33
|
+
from core.api.models import UserInfo
|
|
34
|
+
from core.api.models.graph_cosmo import (
|
|
35
|
+
Edge,
|
|
36
|
+
GraphCosmoOut,
|
|
37
|
+
Project,
|
|
38
|
+
SatelliteItem,
|
|
39
|
+
SatelliteSummary,
|
|
40
|
+
)
|
|
41
|
+
from core.api.visibility import get_visible_projects
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
# Keep in sync con scripts/populate_project_nodes.py v1.3.0: qualsiasi char
|
|
46
|
+
# fuori da [a-zA-Z0-9_\-.] viene sostituito con `_` nel node_id del progetto.
|
|
47
|
+
# Serve per matchare `graph_nodes.project_id` (che puo' contenere `&`, es.
|
|
48
|
+
# cartelle `c&i-tool`) al `project:artifact:<slug>` super-nodo reale (`c_i-tool`).
|
|
49
|
+
_SLUG_SAFE_RE = re.compile(r"[^a-zA-Z0-9_\-.]")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _safe_project_slug(raw: str) -> str:
|
|
53
|
+
"""Sanitize project slug per costruire node_id stabili (c&i-tool → c_i-tool)."""
|
|
54
|
+
return _SLUG_SAFE_RE.sub("_", raw)
|
|
55
|
+
|
|
56
|
+
# Kind whitelist per satellites: derivato dal prefix del node_id (H-11 piano).
|
|
57
|
+
# Schema CHECK di `graph_nodes.type` esclude plan/brainstorm/audit/research,
|
|
58
|
+
# quindi la classificazione vera sta nel prefix `{kind}:artifact:...`.
|
|
59
|
+
ALLOWED_KINDS: tuple[str, ...] = (
|
|
60
|
+
"plan",
|
|
61
|
+
"brainstorm",
|
|
62
|
+
"solution",
|
|
63
|
+
"audit",
|
|
64
|
+
"research",
|
|
65
|
+
"handoff",
|
|
66
|
+
"task",
|
|
67
|
+
"learning",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Generic program baseline shipped in core (tenant-agnostic). Tenant/customer
|
|
71
|
+
# program names are NOT hardcoded here: they come from programs.yaml (local,
|
|
72
|
+
# not mirrored) and are unioned into the valid set by _valid_programs().
|
|
73
|
+
_KNOWN_PROGRAMS_BASELINE: frozenset[str] = frozenset({"marvis", "personal"})
|
|
74
|
+
|
|
75
|
+
# --- programs.yaml cache (mtime-based, thread-safe) ---
|
|
76
|
+
|
|
77
|
+
_programs_map_cache: dict[str, str] | None = None
|
|
78
|
+
_programs_map_mtime: float = 0.0
|
|
79
|
+
_programs_map_lock = threading.Lock()
|
|
80
|
+
_PROGRAMS_YAML_PATH = Path.home() / "workspace" / "programs.yaml"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _load_programs_map() -> dict[str, str]:
|
|
84
|
+
"""Carica programs.yaml → {project_slug: program_name} con cache mtime.
|
|
85
|
+
|
|
86
|
+
Formato programs.yaml:
|
|
87
|
+
<program_name>:
|
|
88
|
+
projects:
|
|
89
|
+
- <slug1>
|
|
90
|
+
- <slug2>
|
|
91
|
+
|
|
92
|
+
Ritorna dict che mappa ogni slug al program di appartenenza. Slug non
|
|
93
|
+
listati in programs.yaml → non presenti nel dict (default 'personal'
|
|
94
|
+
applicato dal caller tramite _normalize_program).
|
|
95
|
+
"""
|
|
96
|
+
global _programs_map_cache, _programs_map_mtime
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
current_mtime = _PROGRAMS_YAML_PATH.stat().st_mtime
|
|
100
|
+
except FileNotFoundError:
|
|
101
|
+
logger.warning("cosmo: programs.yaml non trovato a %s", _PROGRAMS_YAML_PATH)
|
|
102
|
+
return {}
|
|
103
|
+
|
|
104
|
+
with _programs_map_lock:
|
|
105
|
+
if _programs_map_cache is not None and current_mtime == _programs_map_mtime:
|
|
106
|
+
return _programs_map_cache
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
raw = yaml.safe_load(_PROGRAMS_YAML_PATH.read_text(encoding="utf-8")) or {}
|
|
110
|
+
except Exception: # noqa: BLE001 — yaml errori vari
|
|
111
|
+
logger.exception("cosmo: parse programs.yaml fallito")
|
|
112
|
+
return _programs_map_cache or {}
|
|
113
|
+
|
|
114
|
+
result: dict[str, str] = {}
|
|
115
|
+
for prog_name, prog_data in raw.items():
|
|
116
|
+
if not isinstance(prog_data, dict):
|
|
117
|
+
continue
|
|
118
|
+
projects = prog_data.get("projects") or []
|
|
119
|
+
for slug in projects:
|
|
120
|
+
if isinstance(slug, str):
|
|
121
|
+
result[slug] = prog_name
|
|
122
|
+
|
|
123
|
+
_programs_map_cache = result
|
|
124
|
+
_programs_map_mtime = current_mtime
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _valid_programs() -> frozenset[str]:
|
|
129
|
+
"""Insieme dei program validi: baseline generico + nomi da programs.yaml.
|
|
130
|
+
|
|
131
|
+
Il baseline (`marvis`, `personal`) e' tenant-agnostico e vive in core. Ogni
|
|
132
|
+
program definito in programs.yaml (locale, non mirrorato) e' considerato
|
|
133
|
+
valido — cosi' nessun nome tenant/cliente e' hardcoded in core ma il
|
|
134
|
+
comportamento live resta identico. La punteggiatura `&` viene normalizzata
|
|
135
|
+
(`c&i` → `ci`) come per _normalize_program.
|
|
136
|
+
"""
|
|
137
|
+
extra = {_strip_program_punctuation(name) for name in _load_programs_map().values()}
|
|
138
|
+
return _KNOWN_PROGRAMS_BASELINE | extra
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _strip_program_punctuation(raw: str) -> str:
|
|
142
|
+
"""Normalizza il nome program (punteggiatura): `c&i` → `ci`."""
|
|
143
|
+
return raw.replace("&", "")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _normalize_program(raw: str | None) -> str:
|
|
147
|
+
"""Normalizza program name a un valore valido (baseline + programs.yaml).
|
|
148
|
+
|
|
149
|
+
Mappa esplicita:
|
|
150
|
+
- 'c&i' → 'ci' (rimozione punteggiatura, coerente con i nomi mirror FE)
|
|
151
|
+
- None / non riconosciuto → 'personal' (con warning)
|
|
152
|
+
"""
|
|
153
|
+
if not raw:
|
|
154
|
+
logger.warning("cosmo: project missing program, defaulting to 'personal'")
|
|
155
|
+
return "personal"
|
|
156
|
+
normalized = _strip_program_punctuation(raw)
|
|
157
|
+
if normalized not in _valid_programs():
|
|
158
|
+
logger.warning("cosmo: unknown program %r, defaulting to 'personal'", raw)
|
|
159
|
+
return "personal"
|
|
160
|
+
return normalized
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def fetch_cosmo_graph(
|
|
164
|
+
db: aiosqlite.Connection, user: UserInfo
|
|
165
|
+
) -> GraphCosmoOut:
|
|
166
|
+
"""Ritorna il bundle {projects, edges} per il canvas cosmo.
|
|
167
|
+
|
|
168
|
+
Snapshot isolation via BEGIN DEFERRED — evita che populator in corso
|
|
169
|
+
causi conteggi disallineati tra le 3 query.
|
|
170
|
+
|
|
171
|
+
RBAC: projects filtrati via get_visible_projects (None = tutti, set =
|
|
172
|
+
whitelist). Edges scartati se un endpoint non e' nel whitelist (post-filter
|
|
173
|
+
Python).
|
|
174
|
+
"""
|
|
175
|
+
# Dedup row_factory: il caller (get_db) lo setta gia' ma per sicurezza.
|
|
176
|
+
db.row_factory = aiosqlite.Row
|
|
177
|
+
|
|
178
|
+
# Transazione DEFERRED: nessun lock prima del primo SELECT. Rilascia in
|
|
179
|
+
# finally per garantire COMMIT/ROLLBACK anche su eccezioni query.
|
|
180
|
+
await db.execute("BEGIN DEFERRED")
|
|
181
|
+
try:
|
|
182
|
+
# --- Query 1: project super-nodi + degree ---
|
|
183
|
+
# v1.4.0: degree = SUM(weight) INCOMING aggregate (chi e' citato/dipeso
|
|
184
|
+
# da molti = importante = bubble grande). v1.3.0 usava OUTGOING ma
|
|
185
|
+
# premiava i citatori (small project con depends_on forti emergono al
|
|
186
|
+
# top, marvisx scompare perche' la sua weight outgoing e' normalizzata
|
|
187
|
+
# per sqrt(2700 artifact)). v1.4.0 corregge: marvisx riceve mention da
|
|
188
|
+
# tutti -> incoming sum alto -> bubble grande. Tool-bolletta cita
|
|
189
|
+
# marvisx ma non e' citato da nessuno -> piccolo.
|
|
190
|
+
q_projects = """
|
|
191
|
+
SELECT gn.id AS node_id,
|
|
192
|
+
gn.name AS slug,
|
|
193
|
+
(SELECT COALESCE(SUM(CAST(json_extract(ge.metadata, '$.weight') AS REAL)), 0.0)
|
|
194
|
+
FROM graph_edges ge
|
|
195
|
+
WHERE ge.target_id = gn.id
|
|
196
|
+
AND ge.relation IN (
|
|
197
|
+
'mentions','depends_on','refers_to','shares_tag','similar_to'
|
|
198
|
+
)
|
|
199
|
+
AND json_extract(ge.metadata, '$.aggregated') = 1
|
|
200
|
+
AND ge.valid_until IS NULL
|
|
201
|
+
) AS degree
|
|
202
|
+
FROM graph_nodes gn
|
|
203
|
+
WHERE gn.id LIKE 'project:artifact:%'
|
|
204
|
+
AND gn.type = 'project'
|
|
205
|
+
AND gn.deprecated_at IS NULL
|
|
206
|
+
ORDER BY degree DESC, gn.id ASC
|
|
207
|
+
"""
|
|
208
|
+
async with db.execute(q_projects) as cur:
|
|
209
|
+
project_rows: list[aiosqlite.Row] = await cur.fetchall()
|
|
210
|
+
|
|
211
|
+
# --- Query 2: satellites top-8 per project via graph_nodes.project_id ---
|
|
212
|
+
# Gli edge work-chain (produces/contains/describes/documents) non sono
|
|
213
|
+
# popolati in produzione (0 righe). I nodi artifact pero' hanno
|
|
214
|
+
# `project_id` valorizzato dal populator (migration 073 + ingest path),
|
|
215
|
+
# quindi deriviamo i satellites direttamente dalla colonna — senza
|
|
216
|
+
# passare per `graph_edges`. Una riga per (project_id, kind) con:
|
|
217
|
+
# - count = COUNT(*) artifact totali del kind nel project (per file-dot)
|
|
218
|
+
# - latest_at = MAX(last_seen_at, created_at) ISO recency
|
|
219
|
+
# ROW_NUMBER ordinato per latest_at DESC, kind ASC tiebreak. Cap a 8
|
|
220
|
+
# kind distinti per project. Il service ricostruira' il node_id
|
|
221
|
+
# `project:artifact:<slug>` in fase di aggregazione.
|
|
222
|
+
#
|
|
223
|
+
# v1.2.0: aggiunta CTE `sat_items` con top-12 artifact per (project,
|
|
224
|
+
# kind). Performance critica: la subquery `importance` (incoming edges)
|
|
225
|
+
# gira ~6800 volte. Mitigazione: indice `idx_graph_edges_target`
|
|
226
|
+
# esistente su `graph_edges.target_id`. Stima dev DB ~500ms; produzione
|
|
227
|
+
# da monitorare con SQLITE_TIMER se si osserva degrado.
|
|
228
|
+
kinds_placeholders = ",".join("?" * len(ALLOWED_KINDS))
|
|
229
|
+
q_satellites = f"""
|
|
230
|
+
WITH sat_counts AS (
|
|
231
|
+
SELECT gn.project_id AS project_slug,
|
|
232
|
+
gn.type AS kind,
|
|
233
|
+
COUNT(*) AS count,
|
|
234
|
+
MAX(COALESCE(gn.last_seen_at, gn.created_at)) AS latest_at
|
|
235
|
+
FROM graph_nodes gn
|
|
236
|
+
WHERE gn.project_id IS NOT NULL
|
|
237
|
+
AND gn.deprecated_at IS NULL
|
|
238
|
+
AND gn.type IN ({kinds_placeholders})
|
|
239
|
+
GROUP BY gn.project_id, gn.type
|
|
240
|
+
),
|
|
241
|
+
sat_ranked AS (
|
|
242
|
+
SELECT project_slug, kind, count, latest_at,
|
|
243
|
+
ROW_NUMBER() OVER (
|
|
244
|
+
PARTITION BY project_slug
|
|
245
|
+
ORDER BY latest_at DESC, kind ASC
|
|
246
|
+
) AS rn
|
|
247
|
+
FROM sat_counts
|
|
248
|
+
),
|
|
249
|
+
sat_items AS (
|
|
250
|
+
SELECT gn.project_id AS project_slug,
|
|
251
|
+
gn.type AS kind,
|
|
252
|
+
gn.id AS item_id,
|
|
253
|
+
gn.name AS title,
|
|
254
|
+
gn.file_path AS path,
|
|
255
|
+
COALESCE(gn.last_seen_at, gn.created_at) AS latest_at,
|
|
256
|
+
(SELECT COUNT(*) FROM graph_edges ge
|
|
257
|
+
WHERE ge.target_id = gn.id
|
|
258
|
+
AND ge.relation IN (
|
|
259
|
+
'cites','mentions','refers_to',
|
|
260
|
+
'applies_to','similar_to'
|
|
261
|
+
)
|
|
262
|
+
AND ge.valid_until IS NULL
|
|
263
|
+
) AS importance,
|
|
264
|
+
ROW_NUMBER() OVER (
|
|
265
|
+
PARTITION BY gn.project_id, gn.type
|
|
266
|
+
ORDER BY COALESCE(gn.last_seen_at, gn.created_at) DESC,
|
|
267
|
+
gn.id ASC
|
|
268
|
+
) AS rn
|
|
269
|
+
FROM graph_nodes gn
|
|
270
|
+
WHERE gn.project_id IS NOT NULL
|
|
271
|
+
AND gn.deprecated_at IS NULL
|
|
272
|
+
AND gn.type IN ({kinds_placeholders})
|
|
273
|
+
)
|
|
274
|
+
SELECT sr.project_slug, sr.kind, sr.count, sr.latest_at,
|
|
275
|
+
(
|
|
276
|
+
SELECT json_group_array(json_object(
|
|
277
|
+
'id', si.item_id,
|
|
278
|
+
'title', si.title,
|
|
279
|
+
'path', si.path,
|
|
280
|
+
'latest_at', si.latest_at,
|
|
281
|
+
'importance', si.importance
|
|
282
|
+
) ORDER BY si.rn ASC)
|
|
283
|
+
FROM sat_items si
|
|
284
|
+
WHERE si.project_slug = sr.project_slug
|
|
285
|
+
AND si.kind = sr.kind
|
|
286
|
+
AND si.rn <= 12
|
|
287
|
+
) AS items_json
|
|
288
|
+
FROM sat_ranked sr
|
|
289
|
+
WHERE sr.rn <= 8
|
|
290
|
+
"""
|
|
291
|
+
# Doppio param-set: il placeholder appare 2 volte (sat_counts + sat_items)
|
|
292
|
+
sat_query_params: list[str] = list(ALLOWED_KINDS) + list(ALLOWED_KINDS)
|
|
293
|
+
async with db.execute(q_satellites, sat_query_params) as cur:
|
|
294
|
+
sat_rows: list[aiosqlite.Row] = await cur.fetchall()
|
|
295
|
+
|
|
296
|
+
# --- Query 3: edges cross-project aggregated ---
|
|
297
|
+
# JOIN graph_nodes per leggere `name` (raw slug, es. `c&i-master`) invece
|
|
298
|
+
# di `substr(source_id, 18)` che restituirebbe il safe slug del node_id
|
|
299
|
+
# (`c_i-master`). Q1 emette `Project.slug = gn.name` (raw): se gli edge
|
|
300
|
+
# usassero substr (safe), su FE `activeSlug = "c&i-master"` non
|
|
301
|
+
# matcherebbe mai `Edge.source = "c_i-master"` → no edge highlight su
|
|
302
|
+
# hover (bug Cosmo canvas, sessione 162). weight e' dentro metadata JSON.
|
|
303
|
+
# aggregated flag letto via json_extract (robust vs varianti di
|
|
304
|
+
# spacing nel JSON seriale: "aggregated":true vs "aggregated": true).
|
|
305
|
+
# weight: REAL (float) post v1.4 populator. Storicamente era int (count
|
|
306
|
+
# primitive), ora e' un float continuo (decay+normalize). Cast esplicito
|
|
307
|
+
# a REAL per evitare di perdere edge con peso < 1.0 (CAST AS INTEGER
|
|
308
|
+
# tronca 0.4 -> 0 -> filtrato dal `> 0`). FE Zod schema rilassato
|
|
309
|
+
# a `z.number().nonnegative()` di pari passo (consumer accettano float).
|
|
310
|
+
q_edges = """
|
|
311
|
+
SELECT src.name AS slug_a,
|
|
312
|
+
tgt.name AS slug_b,
|
|
313
|
+
ge.relation AS relation,
|
|
314
|
+
CAST(json_extract(ge.metadata, '$.weight') AS REAL) AS weight
|
|
315
|
+
FROM graph_edges ge
|
|
316
|
+
JOIN graph_nodes src ON src.id = ge.source_id
|
|
317
|
+
JOIN graph_nodes tgt ON tgt.id = ge.target_id
|
|
318
|
+
WHERE ge.source_id LIKE 'project:artifact:%'
|
|
319
|
+
AND ge.target_id LIKE 'project:artifact:%'
|
|
320
|
+
AND ge.relation IN ('mentions','depends_on')
|
|
321
|
+
AND json_extract(ge.metadata, '$.aggregated') = 1
|
|
322
|
+
AND ge.valid_until IS NULL
|
|
323
|
+
AND CAST(json_extract(ge.metadata, '$.weight') AS REAL) > 0.0
|
|
324
|
+
"""
|
|
325
|
+
async with db.execute(q_edges) as cur:
|
|
326
|
+
edge_rows: list[aiosqlite.Row] = await cur.fetchall()
|
|
327
|
+
|
|
328
|
+
await db.commit()
|
|
329
|
+
except Exception:
|
|
330
|
+
await db.rollback()
|
|
331
|
+
raise
|
|
332
|
+
|
|
333
|
+
# --- Aggregazione satellites per project_id ricostruito ---
|
|
334
|
+
# Q2 restituisce `project_slug` (valore della colonna `gn.project_id`, es.
|
|
335
|
+
# 'marvisx'). Ricostruiamo la chiave completa `project:artifact:<slug>` per
|
|
336
|
+
# allinearla al `node_id` dei project super-nodi di Q1. Applichiamo la
|
|
337
|
+
# stessa `_safe_project_slug` del populator: altrimenti slug con `&` (es.
|
|
338
|
+
# `c&i-tool`) non matchano mai i super-nodi (`project:artifact:c_i-tool`)
|
|
339
|
+
# e i satelliti/edges dei progetti c&i-* restano orfani.
|
|
340
|
+
#
|
|
341
|
+
# v1.1.0: Q2 ritorna anche `count` (artifact totali del kind, per
|
|
342
|
+
# file-dot semantici N=min(count,6)) + `latest_at` (recency ISO, per
|
|
343
|
+
# accent burning su primo dot solo se < 7 giorni).
|
|
344
|
+
# v1.2.0: Q2 ritorna anche `items_json` (lista top-12 SatelliteItem
|
|
345
|
+
# serializzata via json_group_array). Deserializza con json.loads e
|
|
346
|
+
# popola SatelliteSummary.items.
|
|
347
|
+
satellites_by_pid: dict[str, list[SatelliteSummary]] = {}
|
|
348
|
+
for r in sat_rows:
|
|
349
|
+
slug_raw = r["project_slug"]
|
|
350
|
+
kind = r["kind"]
|
|
351
|
+
if kind not in ALLOWED_KINDS: # defense-in-depth vs schema drift
|
|
352
|
+
continue
|
|
353
|
+
pid = f"project:artifact:{_safe_project_slug(slug_raw)}"
|
|
354
|
+
items_raw = r["items_json"] or "[]"
|
|
355
|
+
try:
|
|
356
|
+
items_decoded = json.loads(items_raw)
|
|
357
|
+
except (TypeError, ValueError):
|
|
358
|
+
logger.warning("cosmo: items_json parse failed for pid=%s kind=%s", pid, kind)
|
|
359
|
+
items_decoded = []
|
|
360
|
+
items_out: list[SatelliteItem] = []
|
|
361
|
+
for it in items_decoded:
|
|
362
|
+
if not isinstance(it, dict):
|
|
363
|
+
continue
|
|
364
|
+
item_id = it.get("id")
|
|
365
|
+
title = it.get("title")
|
|
366
|
+
latest_at_item = it.get("latest_at")
|
|
367
|
+
if not isinstance(item_id, str) or not isinstance(title, str):
|
|
368
|
+
continue
|
|
369
|
+
if not isinstance(latest_at_item, str):
|
|
370
|
+
continue
|
|
371
|
+
path_val = it.get("path")
|
|
372
|
+
items_out.append(
|
|
373
|
+
SatelliteItem(
|
|
374
|
+
id=item_id,
|
|
375
|
+
title=title,
|
|
376
|
+
latest_at=latest_at_item,
|
|
377
|
+
importance=int(it.get("importance") or 0),
|
|
378
|
+
path=path_val if isinstance(path_val, str) else None,
|
|
379
|
+
)
|
|
380
|
+
)
|
|
381
|
+
satellites_by_pid.setdefault(pid, []).append(
|
|
382
|
+
SatelliteSummary(
|
|
383
|
+
kind=kind, # type: ignore[arg-type] # filtered above
|
|
384
|
+
count=int(r["count"] or 0),
|
|
385
|
+
latest_at=r["latest_at"],
|
|
386
|
+
items=items_out,
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# --- Program join via programs.yaml ---
|
|
391
|
+
programs_map = _load_programs_map()
|
|
392
|
+
|
|
393
|
+
# --- Visibility filter ---
|
|
394
|
+
visible = await get_visible_projects(db, user)
|
|
395
|
+
|
|
396
|
+
projects_out: list[Project] = []
|
|
397
|
+
for r in project_rows:
|
|
398
|
+
slug: str = r["slug"]
|
|
399
|
+
if visible is not None and slug not in visible:
|
|
400
|
+
continue
|
|
401
|
+
program = _normalize_program(programs_map.get(slug))
|
|
402
|
+
sats = satellites_by_pid.get(r["node_id"], [])
|
|
403
|
+
projects_out.append(
|
|
404
|
+
Project(
|
|
405
|
+
slug=slug,
|
|
406
|
+
program=program, # type: ignore[arg-type] # Literal runtime-checked
|
|
407
|
+
degree=float(r["degree"] or 0.0),
|
|
408
|
+
satellites=sats,
|
|
409
|
+
)
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
# --- Edge filter + shape ---
|
|
413
|
+
visible_slugs = {p.slug for p in projects_out} if visible is not None else None
|
|
414
|
+
edges_out: list[Edge] = []
|
|
415
|
+
for r in edge_rows:
|
|
416
|
+
a: str = r["slug_a"]
|
|
417
|
+
b: str = r["slug_b"]
|
|
418
|
+
if visible_slugs is not None and (a not in visible_slugs or b not in visible_slugs):
|
|
419
|
+
continue
|
|
420
|
+
edges_out.append(
|
|
421
|
+
Edge(
|
|
422
|
+
source=a,
|
|
423
|
+
target=b,
|
|
424
|
+
relation=r["relation"], # type: ignore[arg-type] # CHECK in SQL
|
|
425
|
+
weight=float(r["weight"] or 0.0),
|
|
426
|
+
)
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
return GraphCosmoOut(projects=projects_out, edges=edges_out)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
# --- Test helpers (invalidazione cache) ---
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _reset_programs_cache_for_tests() -> None:
|
|
436
|
+
"""Reset programs.yaml cache — solo per unit test."""
|
|
437
|
+
global _programs_map_cache, _programs_map_mtime
|
|
438
|
+
with _programs_map_lock:
|
|
439
|
+
_programs_map_cache = None
|
|
440
|
+
_programs_map_mtime = 0.0
|