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,87 @@
|
|
|
1
|
+
"""Governance configuration loader for MarvisX docs."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from core.api.paths import repo_path
|
|
11
|
+
|
|
12
|
+
DEFAULT_CONFIG_PATH = repo_path(__file__, "docs", ".governance.yml")
|
|
13
|
+
|
|
14
|
+
ALLOWED_LAYERS = frozenset(
|
|
15
|
+
{
|
|
16
|
+
"api",
|
|
17
|
+
"mcp",
|
|
18
|
+
"llm-gateway",
|
|
19
|
+
"kg",
|
|
20
|
+
"code-examples",
|
|
21
|
+
"narrative",
|
|
22
|
+
"concept",
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GovernanceConfigError(ValueError):
|
|
28
|
+
"""Raised when docs governance config is missing or malformed."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@lru_cache(maxsize=8)
|
|
32
|
+
def load_governance_config(path: str | Path | None = None) -> dict[str, Any]:
|
|
33
|
+
"""Load the single root governance config and validate its shape."""
|
|
34
|
+
config_path = Path(path) if path else DEFAULT_CONFIG_PATH
|
|
35
|
+
try:
|
|
36
|
+
raw = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
|
37
|
+
except FileNotFoundError as exc:
|
|
38
|
+
raise GovernanceConfigError(f"governance config not found: {config_path}") from exc
|
|
39
|
+
except yaml.YAMLError as exc:
|
|
40
|
+
raise GovernanceConfigError(f"governance config YAML error: {exc}") from exc
|
|
41
|
+
|
|
42
|
+
if not isinstance(raw, dict):
|
|
43
|
+
raise GovernanceConfigError("governance config must be a YAML mapping")
|
|
44
|
+
|
|
45
|
+
layers = raw.get("layers")
|
|
46
|
+
if not isinstance(layers, dict) or not layers:
|
|
47
|
+
raise GovernanceConfigError("governance config must define non-empty layers map")
|
|
48
|
+
if "auto_merge_on" in raw:
|
|
49
|
+
raise GovernanceConfigError("auto_merge_on is not supported; use auto_pr_label_on")
|
|
50
|
+
|
|
51
|
+
unknown = sorted(set(layers) - ALLOWED_LAYERS)
|
|
52
|
+
if unknown:
|
|
53
|
+
raise GovernanceConfigError(f"unknown governance layer(s): {unknown}")
|
|
54
|
+
|
|
55
|
+
for layer, layer_config in layers.items():
|
|
56
|
+
if not isinstance(layer_config, dict):
|
|
57
|
+
raise GovernanceConfigError(f"layer {layer!r} config must be a mapping")
|
|
58
|
+
if "auto_merge_on" in layer_config:
|
|
59
|
+
raise GovernanceConfigError(f"layer {layer!r} uses removed auto_merge_on key")
|
|
60
|
+
threshold = layer_config.get("threshold", raw.get("default_threshold", 1.0))
|
|
61
|
+
try:
|
|
62
|
+
threshold_value = float(threshold)
|
|
63
|
+
except (TypeError, ValueError) as exc:
|
|
64
|
+
raise GovernanceConfigError(f"layer {layer!r} threshold must be numeric") from exc
|
|
65
|
+
if threshold_value < 0.0 or threshold_value > 1.0:
|
|
66
|
+
raise GovernanceConfigError(f"layer {layer!r} threshold must be between 0 and 1")
|
|
67
|
+
if threshold_value != 1.0:
|
|
68
|
+
raise GovernanceConfigError(f"layer {layer!r} threshold must be 1.0")
|
|
69
|
+
|
|
70
|
+
return raw
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def layer_config(layer: str, config: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
74
|
+
"""Return merged defaults for one layer."""
|
|
75
|
+
active = config or load_governance_config()
|
|
76
|
+
layers = active.get("layers") or {}
|
|
77
|
+
if layer not in layers:
|
|
78
|
+
raise GovernanceConfigError(f"unknown governance layer: {layer}")
|
|
79
|
+
merged = {
|
|
80
|
+
"threshold": active.get("default_threshold", 1.0),
|
|
81
|
+
"suggest_label_min_score": active.get("suggest_label_min_score", 0.85),
|
|
82
|
+
"hard_gates": [],
|
|
83
|
+
"auto_pr_label_on": active.get("auto_pr_label_on", {}),
|
|
84
|
+
"human_required_on": ["any"],
|
|
85
|
+
}
|
|
86
|
+
merged.update(layers[layer])
|
|
87
|
+
return merged
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Markdown enrichment for human docs governance review."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Mapping
|
|
6
|
+
|
|
7
|
+
from core.api.services.docs_governance.confidence import DocsTriageDecision
|
|
8
|
+
|
|
9
|
+
DEFAULT_TEMPLATE = """# Docs Governance Review: {layer}
|
|
10
|
+
|
|
11
|
+
Score: {score}
|
|
12
|
+
PR label: {pr_label}
|
|
13
|
+
Reason: {label_reason}
|
|
14
|
+
|
|
15
|
+
## Signals
|
|
16
|
+
|
|
17
|
+
- Syntax validity: {syntax_validity}
|
|
18
|
+
- KG consistency: {kg_consistency}
|
|
19
|
+
- LLM confidence: {llm_confidence}
|
|
20
|
+
- Penalties: {penalties}
|
|
21
|
+
|
|
22
|
+
## Risk
|
|
23
|
+
|
|
24
|
+
{risk_summary}
|
|
25
|
+
|
|
26
|
+
## Evidence
|
|
27
|
+
|
|
28
|
+
{evidence}
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_enrichment_markdown(
|
|
33
|
+
*,
|
|
34
|
+
decision: DocsTriageDecision,
|
|
35
|
+
evidence: Mapping[str, Any] | None = None,
|
|
36
|
+
template_path: str | Path | None = None,
|
|
37
|
+
) -> str:
|
|
38
|
+
"""Render a compact human-review note for PR comments."""
|
|
39
|
+
evidence = evidence or {}
|
|
40
|
+
template = DEFAULT_TEMPLATE
|
|
41
|
+
if template_path:
|
|
42
|
+
path = Path(template_path)
|
|
43
|
+
if path.exists():
|
|
44
|
+
template = path.read_text(encoding="utf-8")
|
|
45
|
+
|
|
46
|
+
return template.format(
|
|
47
|
+
layer=decision.layer,
|
|
48
|
+
score=f"{decision.score:.2f}",
|
|
49
|
+
pr_label=decision.pr_label,
|
|
50
|
+
label_reason=decision.label_reason,
|
|
51
|
+
syntax_validity=f"{decision.syntax_validity:.2f}",
|
|
52
|
+
kg_consistency=f"{decision.kg_consistency:.2f}",
|
|
53
|
+
llm_confidence=f"{decision.llm_confidence:.2f}",
|
|
54
|
+
penalties=", ".join(decision.penalties) or "none",
|
|
55
|
+
risk_summary=str(evidence.get("risk_summary") or "No extra risk summary provided."),
|
|
56
|
+
evidence=_format_evidence(evidence),
|
|
57
|
+
).rstrip() + "\n"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _format_evidence(evidence: Mapping[str, Any]) -> str:
|
|
61
|
+
lines: list[str] = []
|
|
62
|
+
for key in ("test_results", "caller_graph", "kg_context", "links"):
|
|
63
|
+
if key in evidence:
|
|
64
|
+
lines.append(f"- {key}: {evidence[key]}")
|
|
65
|
+
return "\n".join(lines) if lines else "No evidence attached."
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Validate docs MDX frontmatter used by KG coverage tracking."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from core.scripts._frontmatter import parse_frontmatter
|
|
9
|
+
|
|
10
|
+
AUDIENCES = frozenset({"integrator", "agent", "operator", "internal"})
|
|
11
|
+
VISIBILITIES = frozenset({"external", "internal", "draft"})
|
|
12
|
+
LAYERS = frozenset(
|
|
13
|
+
{"api", "mcp", "llm-gateway", "kg", "narrative", "concept", "code-examples"}
|
|
14
|
+
)
|
|
15
|
+
NODE_LAYERS_REQUIRING_DOCUMENTS = frozenset({"api", "mcp", "kg"})
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class FrontmatterValidation:
|
|
20
|
+
valid: bool
|
|
21
|
+
errors: list[str] = field(default_factory=list)
|
|
22
|
+
frontmatter: dict[str, Any] = field(default_factory=dict)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def validate_docs_frontmatter(path: str | Path) -> FrontmatterValidation:
|
|
26
|
+
"""Validate one MDX/Markdown docs page.
|
|
27
|
+
|
|
28
|
+
`documents_nodes` is deliberately opt-in for narrative/concept layers, but
|
|
29
|
+
required for API/MCP/KG pages where coverage can be machine-derived.
|
|
30
|
+
"""
|
|
31
|
+
data, _body = parse_frontmatter(path)
|
|
32
|
+
errors: list[str] = []
|
|
33
|
+
if data is None:
|
|
34
|
+
return FrontmatterValidation(False, ["missing_or_invalid_frontmatter"], {})
|
|
35
|
+
|
|
36
|
+
for required in ("title", "audience", "visibility", "layer"):
|
|
37
|
+
if required not in data:
|
|
38
|
+
errors.append(f"missing_{required}")
|
|
39
|
+
|
|
40
|
+
layer = str(data.get("layer") or "")
|
|
41
|
+
if layer and layer not in LAYERS:
|
|
42
|
+
errors.append(f"invalid_layer:{layer}")
|
|
43
|
+
|
|
44
|
+
visibility = str(data.get("visibility") or "")
|
|
45
|
+
if visibility and visibility not in VISIBILITIES:
|
|
46
|
+
errors.append(f"invalid_visibility:{visibility}")
|
|
47
|
+
|
|
48
|
+
audience = data.get("audience")
|
|
49
|
+
if not isinstance(audience, list) or not audience:
|
|
50
|
+
errors.append("audience_must_be_non_empty_list")
|
|
51
|
+
else:
|
|
52
|
+
invalid_audience = sorted(str(item) for item in audience if str(item) not in AUDIENCES)
|
|
53
|
+
if invalid_audience:
|
|
54
|
+
errors.append(f"invalid_audience:{','.join(invalid_audience)}")
|
|
55
|
+
|
|
56
|
+
documents_nodes = data.get("documents_nodes")
|
|
57
|
+
if documents_nodes is None:
|
|
58
|
+
if layer in NODE_LAYERS_REQUIRING_DOCUMENTS:
|
|
59
|
+
errors.append("missing_documents_nodes")
|
|
60
|
+
elif not isinstance(documents_nodes, list):
|
|
61
|
+
errors.append("documents_nodes_must_be_list")
|
|
62
|
+
else:
|
|
63
|
+
for node_id in documents_nodes:
|
|
64
|
+
if not isinstance(node_id, str) or node_id.count(":") < 2:
|
|
65
|
+
errors.append(f"invalid_documents_node:{node_id!r}")
|
|
66
|
+
|
|
67
|
+
return FrontmatterValidation(not errors, errors, data)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def validate_docs_tree(root: str | Path) -> list[dict[str, Any]]:
|
|
71
|
+
"""Validate all docs content pages under a root directory."""
|
|
72
|
+
root_path = Path(root)
|
|
73
|
+
results: list[dict[str, Any]] = []
|
|
74
|
+
for path in sorted([*root_path.rglob("*.md"), *root_path.rglob("*.mdx")]):
|
|
75
|
+
validation = validate_docs_frontmatter(path)
|
|
76
|
+
results.append(
|
|
77
|
+
{
|
|
78
|
+
"path": str(path),
|
|
79
|
+
"valid": validation.valid,
|
|
80
|
+
"errors": validation.errors,
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
return results
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""Deterministic docs governance hard gates.
|
|
2
|
+
|
|
3
|
+
These gates are opt-in per layer through `docs/.governance.yml`. Missing tools
|
|
4
|
+
or unknown gate names fail the gate explicitly instead of silently approving.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Iterable, Mapping
|
|
14
|
+
|
|
15
|
+
from core.api.services.docs_governance.config import layer_config, load_governance_config
|
|
16
|
+
from core.api.services.docs_governance.frontmatter_validator import validate_docs_frontmatter
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class GateResult:
|
|
21
|
+
name: str
|
|
22
|
+
passed: bool
|
|
23
|
+
detail: str = ""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class GateReport:
|
|
28
|
+
passed: bool
|
|
29
|
+
results: list[GateResult] = field(default_factory=list)
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def failed(self) -> list[GateResult]:
|
|
33
|
+
return [result for result in self.results if not result.passed]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_INTERNAL_URL_PATTERN = r"https?://(?:100\.\d+\.\d+\.\d+|localhost|127\.0\.0\.1)(?::\d+)?"
|
|
37
|
+
_PERSONAL_DOMAIN_PATTERN = os.environ.get(
|
|
38
|
+
"DOCS_GATE_PERSONAL_DOMAIN_REGEX", r"\bllm\.example\.invalid\b"
|
|
39
|
+
)
|
|
40
|
+
_INTERNAL_PATH_PATTERN = r"(?:/data/projects/|/home/[^/]+/|/data/pir/)"
|
|
41
|
+
_INTERNAL_CODENAME_PATTERN = (
|
|
42
|
+
r"\b(marvis-brain|marvisx-core|wedge\s+W\d+|criterion\s+§\d+|R\d{2,})\b"
|
|
43
|
+
)
|
|
44
|
+
_AGENT_TRANSPARENCY_PATTERN = r"\b(subagent|\d+/\d+\s+agent|multi-agent\s+transparency)\b"
|
|
45
|
+
_BARE_TAILSCALE_PATTERN = r"\b100\.(?:6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.\d+\.\d+\b"
|
|
46
|
+
|
|
47
|
+
SECURITY_LEAK_PATTERN_SOURCES: tuple[tuple[str, str, int], ...] = (
|
|
48
|
+
("internal_url", _INTERNAL_URL_PATTERN, 0),
|
|
49
|
+
("personal_gateway_domain", _PERSONAL_DOMAIN_PATTERN, re.IGNORECASE),
|
|
50
|
+
("internal_path", _INTERNAL_PATH_PATTERN, 0),
|
|
51
|
+
("internal_codename", _INTERNAL_CODENAME_PATTERN, re.IGNORECASE),
|
|
52
|
+
("agent_transparency", _AGENT_TRANSPARENCY_PATTERN, re.IGNORECASE),
|
|
53
|
+
("bare_tailscale_ip", _BARE_TAILSCALE_PATTERN, 0),
|
|
54
|
+
)
|
|
55
|
+
SECURITY_LEAK_PATTERNS: tuple[tuple[str, re.Pattern[str]], ...] = tuple(
|
|
56
|
+
(name, re.compile(pattern, flags)) for name, pattern, flags in SECURITY_LEAK_PATTERN_SOURCES
|
|
57
|
+
)
|
|
58
|
+
SECURITY_LEAK_GREP_PATTERN = "|".join(
|
|
59
|
+
f"(?:{pattern})" for _name, pattern, _flags in SECURITY_LEAK_PATTERN_SOURCES
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def check_frontmatter_valid(path: str | Path) -> GateResult:
|
|
64
|
+
validation = validate_docs_frontmatter(path)
|
|
65
|
+
return GateResult(
|
|
66
|
+
"frontmatter_valid",
|
|
67
|
+
validation.valid,
|
|
68
|
+
",".join(validation.errors),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def check_no_security_leaks(text: str) -> GateResult:
|
|
73
|
+
"""Block internal endpoints, paths, codenames, and implementation traces."""
|
|
74
|
+
matches = [
|
|
75
|
+
name
|
|
76
|
+
for name, pattern in SECURITY_LEAK_PATTERNS
|
|
77
|
+
if pattern.search(text)
|
|
78
|
+
]
|
|
79
|
+
return GateResult("no_security_leaks", not matches, ",".join(matches))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def check_schema_valid(payload: Mapping[str, Any]) -> GateResult:
|
|
83
|
+
"""Validate minimal schema-ish payloads from generated references."""
|
|
84
|
+
ok = bool(payload) and not payload.get("errors")
|
|
85
|
+
return GateResult("schema_valid", ok, str(payload.get("errors") or ""))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def check_no_breaking_removal(change_type: str) -> GateResult:
|
|
89
|
+
return GateResult(
|
|
90
|
+
"no_breaking_removal",
|
|
91
|
+
change_type not in {"breaking_removal", "signature_change", "tool_removed"},
|
|
92
|
+
change_type,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def check_doc_detective_green(result: Mapping[str, Any]) -> GateResult:
|
|
97
|
+
status = str(result.get("status") or "").upper()
|
|
98
|
+
return GateResult("doc_detective_green", status == "PASS", status or "missing")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def check_vale_pass(result: Mapping[str, Any]) -> GateResult:
|
|
102
|
+
errors = int(result.get("errors") or 0)
|
|
103
|
+
return GateResult("vale_pass", errors == 0, f"errors={errors}")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def check_lychee_pass(result: Mapping[str, Any]) -> GateResult:
|
|
107
|
+
failures = int(result.get("failures") or result.get("failed") or 0)
|
|
108
|
+
return GateResult("lychee_pass", failures == 0, f"failures={failures}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def check_kg_xref_valid(xrefs: Iterable[str], known_nodes: set[str]) -> GateResult:
|
|
112
|
+
missing = sorted(str(node_id) for node_id in xrefs if str(node_id) not in known_nodes)
|
|
113
|
+
return GateResult("kg_xref_valid", not missing, ",".join(missing[:10]))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def check_adr_consistency(result: Mapping[str, Any]) -> GateResult:
|
|
117
|
+
conflicts = result.get("conflicts") or []
|
|
118
|
+
return GateResult("adr_consistency", not conflicts, json.dumps(conflicts[:5]))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def check_coverage_no_regression(
|
|
122
|
+
*,
|
|
123
|
+
removed_nodes: Iterable[str],
|
|
124
|
+
replacement_nodes: Iterable[str],
|
|
125
|
+
baseline_documented_nodes: set[str],
|
|
126
|
+
) -> GateResult:
|
|
127
|
+
"""Fail if a PR removes the only documented coverage for a baseline node."""
|
|
128
|
+
replacements = set(replacement_nodes)
|
|
129
|
+
regressions = sorted(
|
|
130
|
+
str(node_id)
|
|
131
|
+
for node_id in removed_nodes
|
|
132
|
+
if str(node_id) in baseline_documented_nodes and str(node_id) not in replacements
|
|
133
|
+
)
|
|
134
|
+
return GateResult(
|
|
135
|
+
"documented_coverage_no_regression",
|
|
136
|
+
not regressions,
|
|
137
|
+
",".join(regressions[:10]),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def check_no_undocumented_new_function(
|
|
142
|
+
*,
|
|
143
|
+
new_function_qualified_names: set[str],
|
|
144
|
+
documented_qualified_names: set[str],
|
|
145
|
+
) -> GateResult:
|
|
146
|
+
"""Fail if a newly added public function has no docs frontmatter claim."""
|
|
147
|
+
missing = sorted(new_function_qualified_names - documented_qualified_names)
|
|
148
|
+
return GateResult(
|
|
149
|
+
"no_undocumented_new_function",
|
|
150
|
+
not missing,
|
|
151
|
+
",".join(missing[:10]),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def run_configured_hard_gates(
|
|
156
|
+
*,
|
|
157
|
+
layer: str,
|
|
158
|
+
context: Mapping[str, Any],
|
|
159
|
+
config_path: str | Path | None = None,
|
|
160
|
+
) -> GateReport:
|
|
161
|
+
"""Run only gates configured for a layer.
|
|
162
|
+
|
|
163
|
+
Context keys intentionally mirror CI output names so callers can wire this
|
|
164
|
+
without creating a synthetic object graph.
|
|
165
|
+
"""
|
|
166
|
+
config = load_governance_config(config_path)
|
|
167
|
+
gates = layer_config(layer, config).get("hard_gates") or []
|
|
168
|
+
results: list[GateResult] = []
|
|
169
|
+
text = str(context.get("text") or "")
|
|
170
|
+
|
|
171
|
+
for gate in gates:
|
|
172
|
+
if gate == "frontmatter_valid":
|
|
173
|
+
path = context.get("path")
|
|
174
|
+
if path:
|
|
175
|
+
results.append(check_frontmatter_valid(path))
|
|
176
|
+
else:
|
|
177
|
+
results.append(GateResult("frontmatter_valid", False, "missing_path"))
|
|
178
|
+
elif gate == "no_security_leaks":
|
|
179
|
+
results.append(check_no_security_leaks(text))
|
|
180
|
+
elif gate == "schema_valid":
|
|
181
|
+
results.append(check_schema_valid(context.get("schema") or {}))
|
|
182
|
+
elif gate == "no_breaking_removal":
|
|
183
|
+
results.append(check_no_breaking_removal(str(context.get("change_type") or "")))
|
|
184
|
+
elif gate == "doc_detective_green":
|
|
185
|
+
results.append(check_doc_detective_green(context.get("doc_detective") or {}))
|
|
186
|
+
elif gate == "vale_pass":
|
|
187
|
+
results.append(check_vale_pass(context.get("vale") or {}))
|
|
188
|
+
elif gate == "lychee_pass":
|
|
189
|
+
results.append(check_lychee_pass(context.get("lychee") or {}))
|
|
190
|
+
elif gate == "kg_xref_valid":
|
|
191
|
+
results.append(
|
|
192
|
+
check_kg_xref_valid(
|
|
193
|
+
context.get("xrefs") or [],
|
|
194
|
+
set(context.get("known_nodes") or []),
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
elif gate == "adr_consistency":
|
|
198
|
+
results.append(check_adr_consistency(context.get("adr") or {}))
|
|
199
|
+
elif gate == "documented_coverage_no_regression":
|
|
200
|
+
results.append(
|
|
201
|
+
check_coverage_no_regression(
|
|
202
|
+
removed_nodes=context.get("removed_nodes") or [],
|
|
203
|
+
replacement_nodes=context.get("replacement_nodes") or [],
|
|
204
|
+
baseline_documented_nodes=set(context.get("baseline_documented_nodes") or []),
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
elif gate == "no_undocumented_new_function":
|
|
208
|
+
results.append(
|
|
209
|
+
check_no_undocumented_new_function(
|
|
210
|
+
new_function_qualified_names=set(
|
|
211
|
+
context.get("new_function_qualified_names") or []
|
|
212
|
+
),
|
|
213
|
+
documented_qualified_names=set(
|
|
214
|
+
context.get("documented_qualified_names") or []
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
else:
|
|
219
|
+
results.append(GateResult(str(gate), False, "unknown_gate"))
|
|
220
|
+
|
|
221
|
+
return GateReport(all(result.passed for result in results), results)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Slim docs governance orchestrator.
|
|
2
|
+
|
|
3
|
+
Plan 0 deliberately uses a small deterministic orchestration layer. The P0
|
|
4
|
+
review deferred LangGraph until the docs pipeline needs multiple conditional
|
|
5
|
+
LLM nodes; for now this function is the durable decision boundary.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import asdict, dataclass
|
|
10
|
+
from typing import Any, Mapping
|
|
11
|
+
|
|
12
|
+
from core.api.services.docs_governance.confidence import (
|
|
13
|
+
compute_docs_confidence,
|
|
14
|
+
estimate_syntax_validity,
|
|
15
|
+
)
|
|
16
|
+
from core.api.services.docs_governance.config import layer_config, load_governance_config
|
|
17
|
+
from core.api.services.docs_governance.enrichment import build_enrichment_markdown
|
|
18
|
+
from core.api.services.docs_governance.hard_gates import run_configured_hard_gates
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class DocsGovernanceTriage:
|
|
23
|
+
decision: str
|
|
24
|
+
layer: str
|
|
25
|
+
score: float
|
|
26
|
+
pr_label: str
|
|
27
|
+
opens_pr_draft: bool
|
|
28
|
+
confidence: dict[str, Any]
|
|
29
|
+
hard_gates: list[dict[str, Any]]
|
|
30
|
+
enrichment_markdown: str
|
|
31
|
+
|
|
32
|
+
def as_json(self) -> dict[str, Any]:
|
|
33
|
+
return asdict(self)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def triage_docs_change(
|
|
37
|
+
*,
|
|
38
|
+
layer: str,
|
|
39
|
+
change_type: str,
|
|
40
|
+
diff_text: str,
|
|
41
|
+
context: Mapping[str, Any],
|
|
42
|
+
llm_confidence: float = 0.0,
|
|
43
|
+
kg_consistency: float = 1.0,
|
|
44
|
+
kg_stale: bool = False,
|
|
45
|
+
corpus_state: str = "ready",
|
|
46
|
+
config_path: str | None = None,
|
|
47
|
+
) -> DocsGovernanceTriage:
|
|
48
|
+
"""Run configured deterministic gates and compute a PR draft label.
|
|
49
|
+
|
|
50
|
+
This function never merges. Callers use the result to open a draft PR for
|
|
51
|
+
Console Triage and attach `pr_label` as the governance suggestion.
|
|
52
|
+
"""
|
|
53
|
+
gate_report = run_configured_hard_gates(
|
|
54
|
+
layer=layer,
|
|
55
|
+
context=context,
|
|
56
|
+
config_path=config_path,
|
|
57
|
+
)
|
|
58
|
+
cfg = layer_config(layer, load_governance_config(config_path))
|
|
59
|
+
syntax = estimate_syntax_validity(
|
|
60
|
+
frontmatter_valid=not any(result.name == "frontmatter_valid" and not result.passed for result in gate_report.results),
|
|
61
|
+
vale_errors=int((context.get("vale") or {}).get("errors") or 0),
|
|
62
|
+
link_failures=int((context.get("lychee") or {}).get("failures") or 0),
|
|
63
|
+
doc_detective_status=(context.get("doc_detective") or {}).get("status"),
|
|
64
|
+
)
|
|
65
|
+
confidence = compute_docs_confidence(
|
|
66
|
+
layer=layer,
|
|
67
|
+
change_type=change_type,
|
|
68
|
+
diff_text=diff_text,
|
|
69
|
+
syntax_validity=syntax,
|
|
70
|
+
kg_consistency=kg_consistency,
|
|
71
|
+
llm_confidence=llm_confidence,
|
|
72
|
+
suggest_label_min_score=float(cfg.get("suggest_label_min_score") or 0.85),
|
|
73
|
+
kg_stale=kg_stale,
|
|
74
|
+
corpus_state=corpus_state,
|
|
75
|
+
hard_gate_failures=[result.name for result in gate_report.failed],
|
|
76
|
+
pr_labels=cfg.get("auto_pr_label_on") or {},
|
|
77
|
+
human_required_change_types=set(cfg.get("human_required_on") or []),
|
|
78
|
+
)
|
|
79
|
+
enrichment = build_enrichment_markdown(
|
|
80
|
+
decision=confidence,
|
|
81
|
+
evidence={
|
|
82
|
+
"risk_summary": "Deterministic docs governance triage.",
|
|
83
|
+
"test_results": context.get("test_results", "not attached"),
|
|
84
|
+
"caller_graph": context.get("caller_graph", "not attached"),
|
|
85
|
+
"kg_context": context.get("kg_context", "not attached"),
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return DocsGovernanceTriage(
|
|
90
|
+
decision="draft_pr",
|
|
91
|
+
layer=layer,
|
|
92
|
+
score=confidence.score,
|
|
93
|
+
pr_label=confidence.pr_label,
|
|
94
|
+
opens_pr_draft=True,
|
|
95
|
+
confidence=confidence.as_json(),
|
|
96
|
+
hard_gates=[asdict(result) for result in gate_report.results],
|
|
97
|
+
enrichment_markdown=enrichment,
|
|
98
|
+
)
|