@ngocsangairvds/vsaf 3.2.13 → 3.2.15
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.
- package/bin/vsaf.js +13 -0
- package/package.json +1 -1
- package/src/config.js +167 -0
- package/src/global.js +34 -51
- package/src/utils.js +44 -1
- package/tools/vds-scripts/Makefile +9 -31
- package/tools/vds-scripts/docker/docker-compose.cli.yml +1 -117
- package/tools/vds-scripts/docker/docker-compose.services.yml +1 -40
- package/tools/vds-scripts/docker/infrastructure/init-schemas.sql +0 -34
- package/tools/vds-scripts/docker/infrastructure/pgbouncer/pgbouncer.ini +2 -6
- package/tools/vds-scripts/pyproject.toml +1 -33
- package/tools/vds-scripts/uv.lock +80 -1651
- package/tools/vds-scripts/vds_cli/pyproject.toml +3 -0
- package/tools/vds-scripts/vds_cli/src/vds_cli/cli.py +1 -127
- package/tools/vds-scripts/vds_cli/src/vds_cli/commands/lint_cli.py +1 -20
- package/tools/vds-scripts/vds_cli/src/vds_cli/router.py +0 -100
- package/tools/vds-scripts/vds_cli/tests/conftest.py +0 -2
- package/tools/vds-scripts/vds_cli/tests/unit/test_cli.py +0 -25
- package/tools/vds-scripts/vds_cli/tests/unit/test_lint_cli.py +2 -2
- package/tools/vds-scripts/vds_cli/tests/unit/test_router.py +0 -2
- package/tools/vds-scripts/CLOSURE.md +0 -340
- package/tools/vds-scripts/ECOSYSTEM-CHANGELOG.md +0 -52
- package/tools/vds-scripts/ECOSYSTEM-DOCS.md +0 -602
- package/tools/vds-scripts/ECOSYSTEM_ALIGNMENT.md +0 -133
- package/tools/vds-scripts/ENV-HYGIENE-OPS-NOTE.md +0 -65
- package/tools/vds-scripts/INVESTIGATION-cloud-401.md +0 -103
- package/tools/vds-scripts/MEM0_2.0_API_REFERENCE.md +0 -238
- package/tools/vds-scripts/PACKAGE_P125B_IMPLEMENTATION_SUMMARY.md +0 -131
- package/tools/vds-scripts/PHASE-MERGE-SUMMARY.md +0 -121
- package/tools/vds-scripts/PHASES-3-ARCHIVE.md +0 -59
- package/tools/vds-scripts/PROJECT_COMPLETION_SUMMARY.md +0 -45
- package/tools/vds-scripts/SEARCH-CRASH-REPRO.md +0 -51
- package/tools/vds-scripts/analyze_hexagonal.py +0 -217
- package/tools/vds-scripts/analyze_profiles.py +0 -60
- package/tools/vds-scripts/audit-checklist.xlsx +0 -0
- package/tools/vds-scripts/audit_orchestrator/.audit_approvals/approvals_index.json +0 -1
- package/tools/vds-scripts/audit_orchestrator/.env.example +0 -85
- package/tools/vds-scripts/audit_orchestrator/.github/workflows/audit.yml +0 -47
- package/tools/vds-scripts/audit_orchestrator/Dockerfile +0 -92
- package/tools/vds-scripts/audit_orchestrator/GOOGLE_SHEETS_IMPLEMENTATION_SUMMARY.md +0 -218
- package/tools/vds-scripts/audit_orchestrator/PHASE3_INTEGRATION_SUMMARY.md +0 -268
- package/tools/vds-scripts/audit_orchestrator/PHASE7-MERGE-SUMMARY.md +0 -174
- package/tools/vds-scripts/audit_orchestrator/README.md +0 -1573
- package/tools/vds-scripts/audit_orchestrator/TSK-168-IMPLEMENTATION-SUMMARY.md +0 -191
- package/tools/vds-scripts/audit_orchestrator/TSK-196-IMPLEMENTATION-SUMMARY.md +0 -201
- package/tools/vds-scripts/audit_orchestrator/alembic/env.py +0 -37
- package/tools/vds-scripts/audit_orchestrator/alembic/script.py.mako +0 -28
- package/tools/vds-scripts/audit_orchestrator/alembic/versions/0001_initial_audit_state_schema.py +0 -1260
- package/tools/vds-scripts/audit_orchestrator/alembic.ini +0 -68
- package/tools/vds-scripts/audit_orchestrator/config/category-mapping.json +0 -81
- package/tools/vds-scripts/audit_orchestrator/config/profile-timeouts.yaml +0 -17
- package/tools/vds-scripts/audit_orchestrator/create_sample.py +0 -55
- package/tools/vds-scripts/audit_orchestrator/data/corpus_accuracy_report.json +0 -17
- package/tools/vds-scripts/audit_orchestrator/data/exemplar_quality_report.json +0 -1606
- package/tools/vds-scripts/audit_orchestrator/data/instruction_plan_fixtures.json +0 -163
- package/tools/vds-scripts/audit_orchestrator/data/requirement_exemplars.json +0 -3443
- package/tools/vds-scripts/audit_orchestrator/data/requirement_scope_fixtures.json +0 -172
- package/tools/vds-scripts/audit_orchestrator/debug_rg.py +0 -46
- package/tools/vds-scripts/audit_orchestrator/demo_code_pack.py +0 -127
- package/tools/vds-scripts/audit_orchestrator/docs/AGENT_SDK_SELECTION_SPEC.md +0 -720
- package/tools/vds-scripts/audit_orchestrator/docs/API.md +0 -804
- package/tools/vds-scripts/audit_orchestrator/docs/CONTENT_ANALYSIS_APPROACH.md +0 -1041
- package/tools/vds-scripts/audit_orchestrator/docs/CONTENT_SCORING_EVOLUTION_SPEC.md +0 -868
- package/tools/vds-scripts/audit_orchestrator/docs/DEPLOYMENT.md +0 -778
- package/tools/vds-scripts/audit_orchestrator/docs/LLM_AGENT_AUDIT_SPEC.md +0 -721
- package/tools/vds-scripts/audit_orchestrator/docs/LLM_CONTENT_ANALYSIS_SPEC.md +0 -1143
- package/tools/vds-scripts/audit_orchestrator/docs/LSP_SETUP_GUIDE.md +0 -221
- package/tools/vds-scripts/audit_orchestrator/docs/MULTI_REPO_AUDIT_SPEC.md +0 -951
- package/tools/vds-scripts/audit_orchestrator/docs/OLLAMA_EMBEDDINGS_SETUP.md +0 -119
- package/tools/vds-scripts/audit_orchestrator/docs/PHASE32_REAL_BENCHMARK_2026-02-08.md +0 -66
- package/tools/vds-scripts/audit_orchestrator/docs/PHASE_64_TO_92_HISTORICAL_SPEC.md +0 -1772
- package/tools/vds-scripts/audit_orchestrator/docs/TSK-193-flow-trace.md +0 -201
- package/tools/vds-scripts/audit_orchestrator/docs/TSK-193-verification.md +0 -124
- package/tools/vds-scripts/audit_orchestrator/docs/phase152-hierarchical-query-surface.md +0 -46
- package/tools/vds-scripts/audit_orchestrator/examples/bitbucket_metadata_example.json +0 -50
- package/tools/vds-scripts/audit_orchestrator/legacy/migrations/README.md +0 -68
- package/tools/vds-scripts/audit_orchestrator/legacy/migrations/phase117_phase118_shared_state.sql +0 -64
- package/tools/vds-scripts/audit_orchestrator/legacy/migrations/phase154_published_pages.sql +0 -28
- package/tools/vds-scripts/audit_orchestrator/legacy/migrations/phase157_dispatch_tables.sql +0 -94
- package/tools/vds-scripts/audit_orchestrator/legacy/migrations/phase157_events.sql +0 -91
- package/tools/vds-scripts/audit_orchestrator/legacy/migrations/phase157_scope_snapshots.sql +0 -24
- package/tools/vds-scripts/audit_orchestrator/legacy/migrations/phase157_status_view.sql +0 -22
- package/tools/vds-scripts/audit_orchestrator/legacy/migrations/phase169_dispatch_observability.sql +0 -55
- package/tools/vds-scripts/audit_orchestrator/legacy/migrations/state_repair_hardening.sql +0 -24
- package/tools/vds-scripts/audit_orchestrator/pyproject.toml +0 -211
- package/tools/vds-scripts/audit_orchestrator/pyrightconfig.json +0 -51
- package/tools/vds-scripts/audit_orchestrator/pytest.ini +0 -37
- package/tools/vds-scripts/audit_orchestrator/reproduce_scanner.py +0 -40
- package/tools/vds-scripts/audit_orchestrator/scripts/README.md +0 -116
- package/tools/vds-scripts/audit_orchestrator/scripts/benchmark_crawl_modes.py +0 -455
- package/tools/vds-scripts/audit_orchestrator/scripts/benchmark_dspy.py +0 -513
- package/tools/vds-scripts/audit_orchestrator/scripts/benchmark_nlp_accuracy.py +0 -138
- package/tools/vds-scripts/audit_orchestrator/scripts/benchmark_retrieval_modes.py +0 -176
- package/tools/vds-scripts/audit_orchestrator/scripts/benchmark_upload_update_mode.py +0 -167
- package/tools/vds-scripts/audit_orchestrator/scripts/build_check.py +0 -76
- package/tools/vds-scripts/audit_orchestrator/scripts/check_live_progress.py +0 -61
- package/tools/vds-scripts/audit_orchestrator/scripts/cli_integration_test.py +0 -400
- package/tools/vds-scripts/audit_orchestrator/scripts/index_workspace.py +0 -178
- package/tools/vds-scripts/audit_orchestrator/scripts/inspect_route_conformance.py +0 -196
- package/tools/vds-scripts/audit_orchestrator/scripts/monitor_postgres.py +0 -145
- package/tools/vds-scripts/audit_orchestrator/scripts/optimize_audit.py +0 -462
- package/tools/vds-scripts/audit_orchestrator/scripts/verify.py +0 -673
- package/tools/vds-scripts/audit_orchestrator/scripts/verify_phase111_requirement_analysis.py +0 -375
- package/tools/vds-scripts/audit_orchestrator/scripts/verify_phase117_cross_repo_evidence.py +0 -77
- package/tools/vds-scripts/audit_orchestrator/scripts/verify_phase121_short_circuit.py +0 -680
- package/tools/vds-scripts/audit_orchestrator/scripts/verify_phase122_instruction_handling.py +0 -478
- package/tools/vds-scripts/audit_orchestrator/scripts/verify_phase125_skill_integration.py +0 -832
- package/tools/vds-scripts/audit_orchestrator/scripts/verify_phase_36.py +0 -394
- package/tools/vds-scripts/audit_orchestrator/scripts/verify_phase_37.py +0 -58
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/__init__.py +0 -17
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/__init__.py +0 -29
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/_langchain_warnings.py +0 -17
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/agentic_investigator.py +0 -4130
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/approval.py +0 -490
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/audit_loop_hooks.py +0 -107
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/audit_state.py +0 -50
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/base.py +0 -4035
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/code_agent.py +0 -667
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/code_analysis_helpers.py +0 -236
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/code_analysis_prompts.py +0 -146
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/docs_agent.py +0 -1234
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/langgraph_workflow.py +0 -2002
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/pydantic_base.py +0 -1227
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/requirement_analysis_agent.py +0 -593
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/security_agent.py +0 -1829
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/security_scanner.py +0 -686
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/skill_tools.py +0 -204
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/synthesis_agent.py +0 -1463
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/tool_efficiency_guard.py +0 -609
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/tool_registry.py +0 -3822
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/toolsets/__init__.py +0 -52
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/toolsets/evidence_corpus.py +0 -385
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/toolsets/filesystem.py +0 -1134
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/toolsets/lsp.py +0 -458
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/toolsets/mcp_toolset.py +0 -491
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/toolsets/skills_toolset.py +0 -997
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/toolsets/vector_evidence.py +0 -842
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/usage_tracker.py +0 -682
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/agents/visualization.py +0 -303
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/analyze_cmds.py +0 -892
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checklist_query/__init__.py +0 -15
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checklist_query/service.py +0 -171
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/__init__.py +0 -20
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/base.py +0 -60
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/bitbucket/__init__.py +0 -6
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/bitbucket/checks.py +0 -257
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/confluence/__init__.py +0 -10
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/confluence/checks.py +0 -78
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/git/__init__.py +0 -6
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/git/file_checks.py +0 -133
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/llm_checks/__init__.py +0 -17
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/llm_checks/api_docs_check.py +0 -80
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/llm_checks/readme_check.py +0 -76
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/llm_checks/security_docs_check.py +0 -78
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/registry.py +0 -402
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/sonarqube/__init__.py +0 -10
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/checks/sonarqube/checks.py +0 -276
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/cli.py +0 -12
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/cli_common.py +0 -128
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/cli_impl.py +0 -9826
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/clients/bitbucket_cli_client.py +0 -187
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/clients/confluence_cli_client.py +0 -977
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/clients/sonarqube_cli_client.py +0 -28
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/__init__.py +0 -21
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/base.py +0 -25
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/bitbucket_downloader.py +0 -644
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/bitbucket_metadata.py +0 -133
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/checklist_parser.py +0 -180
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/completeness/__init__.py +0 -31
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/completeness/bitbucket_probe.py +0 -443
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/completeness/confluence_probe.py +0 -365
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/completeness/freshness_evaluator.py +0 -330
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/completeness/material_completeness_service.py +0 -1079
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/confluence_collector.py +0 -259
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/diagram_extractor.py +0 -280
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/enrichment_extractor.py +0 -200
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/evidence_cache.py +0 -35
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/git_collector.py +0 -148
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/graphify_collector.py +0 -171
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/image_extractor.py +0 -359
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/linked_page_tracker.py +0 -120
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/markdown_converter.py +0 -344
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/material_cache.py +0 -1252
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/material_downloader.py +0 -1165
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/orchestrator.py +0 -168
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/registry_parser.py +0 -3063
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/requirements.py +0 -70
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/runner.py +0 -119
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/collectors/sonarqube_collector.py +0 -113
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/config.py +0 -1943
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/config_resolution/__init__.py +0 -23
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/config_resolution/discovery.py +0 -90
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/config_resolution/environment_resolver.py +0 -56
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/config_resolution/evidence.py +0 -78
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/config_resolution/models.py +0 -73
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/config_resolution/precedence.py +0 -10
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/config_resolution/redaction.py +0 -20
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/confluence_connectivity.py +0 -140
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/corpus_cmds.py +0 -278
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/db/__init__.py +0 -7
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/db/alembic_filters.py +0 -57
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/docs/__init__.py +0 -29
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/docs/diataxis_validator.py +0 -687
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/doctor_cmds.py +0 -3295
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/dspy_modules/__init__.py +0 -5
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/dspy_modules/evaluation.py +0 -301
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/dspy_modules/modules.py +0 -172
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/dspy_modules/runtime.py +0 -836
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/dspy_modules/signatures.py +0 -406
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/__init__.py +0 -192
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/ad_hoc_analyzer.py +0 -399
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/aggregator.py +0 -220
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/auditor.py +0 -504
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/batch_evidence_cache.py +0 -111
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/batch_processor.py +0 -4776
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/calibration.py +0 -217
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/checklist_generator.py +0 -1201
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/checklist_projection.py +0 -192
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/checklist_scoping.py +0 -221
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/checkpoint.py +0 -159
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/cl003_shared_lib_guard.py +0 -194
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/companion_context_service.py +0 -445
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/confluence_checklist_contract.py +0 -7425
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/cross_check_rules.py +0 -213
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/deterministic_evaluator.py +0 -237
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/drift_detector.py +0 -157
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/dspy_requirement_classifier.py +0 -640
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/evidence_assembler.py +0 -407
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/evidence_collector.py +0 -119
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/evidence_diversity.py +0 -101
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/gap_analyzer.py +0 -549
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/graduated.py +0 -185
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/grounding_validator.py +0 -287
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/instruction_analyzer.py +0 -882
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/instruction_compliance.py +0 -172
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/llm_row_evaluator.py +0 -9270
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/loader.py +0 -1070
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/manual_check_config.py +0 -136
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/mapping.py +0 -269
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/multi_judge.py +0 -65
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/phase120_checklist_update.py +0 -416
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/profile_scorer.py +0 -427
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/project_evidence_context.py +0 -449
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/project_knowledge_query_service.py +0 -155
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/project_knowledge_store.py +0 -383
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/project_topology.py +0 -1920
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/provider_failure_classifier.py +0 -778
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/readiness_cli_helpers.py +0 -341
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/readiness_extractor.py +0 -303
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/readiness_synthesizer.py +0 -730
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/regression_guard.py +0 -138
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/repo_type_classifier.py +0 -297
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/requirement_analysis.py +0 -1433
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/requirement_classification.py +0 -1725
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/result_merger.py +0 -814
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/route_matrix.py +0 -267
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/row_evaluator.py +0 -9437
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/row_evaluator_runtime.py +0 -1270
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/row_evaluator_types.py +0 -2102
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/rubric.py +0 -592
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/scorer.py +0 -1239
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/section_packs.py +0 -645
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/skill_recommendation.py +0 -1183
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/stability_harness.py +0 -207
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/target_selector.py +0 -841
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/telemetry.py +0 -347
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/template_analyzer.py +0 -469
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/token_tracker.py +0 -111
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/tool_first_planner.py +0 -7905
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/topology_query_service.py +0 -80
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/validator.py +0 -449
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/engine/weight_policy.py +0 -464
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/errors.py +0 -430
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/extract_cmds.py +0 -4887
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/identity.py +0 -146
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/incremental/__init__.py +0 -52
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/incremental/baseline.py +0 -378
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/incremental/change_analyzer.py +0 -407
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/incremental/delta_report.py +0 -189
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/incremental/diff_detector.py +0 -301
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/integrations/__init__.py +0 -3
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/__init__.py +0 -50
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/audit_schemas.py +0 -459
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/codex_oauth.py +0 -340
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/cost_tracker.py +0 -288
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/engine.py +0 -751
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/evaluator.py +0 -245
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/prompts/__init__.py +0 -32
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/prompts/api_docs_evaluation.py +0 -25
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/prompts/gap_analysis.py +0 -31
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/prompts/instruction_templates.py +0 -634
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/prompts/readme_evaluation.py +0 -25
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/prompts/row_evaluation.py +0 -247
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/prompts/security_docs_evaluation.py +0 -25
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/prompts/template_analysis.py +0 -25
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/prompts.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/llm/provider.py +0 -626
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/logging_config.py +0 -577
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/mappings/__init__.py +0 -58
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/mappings/default_checklist_mapping.json +0 -18
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/mappings/vietnamese_checklist_mapping.json +0 -38
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/misc_cmds.py +0 -4689
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/__init__.py +0 -153
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/calibration.py +0 -98
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/checklist.py +0 -921
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/completeness.py +0 -309
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/enrichment.py +0 -58
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/enums.py +0 -97
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/evidence.py +0 -351
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/findings.py +0 -381
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/gaps.py +0 -299
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/graph.py +0 -42
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/multi_judge.py +0 -50
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/readiness.py +0 -309
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/registry.py +0 -386
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/reporting.py +0 -32
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/task.py +0 -549
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/models/template.py +0 -477
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/observability/__init__.py +0 -31
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/observability/metrics.py +0 -404
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/parse_cmds.py +0 -608
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/pdf_cmds.py +0 -208
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/performance_gates.py +0 -224
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/phase151_projection.py +0 -84
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/profiles/__init__.py +0 -65
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/profiles/detection.py +0 -842
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/profiles/models.py +0 -474
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/__init__.py +0 -1
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/_confluence_macros.py +0 -145
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/_field_sanitizer.py +0 -25
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/_table_builder.py +0 -63
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/_vietnamese_templates.py +0 -103
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/bitbucket_link_resolver.py +0 -34
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/checklist_renderer.py +0 -483
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/confluence_publisher.py +0 -3048
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/hierarchy_publisher.py +0 -213
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/live_data_injector.py +0 -152
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/macro_builder.py +0 -101
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/markdown_converter.py +0 -154
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/priority_renderer.py +0 -133
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/project_aggregate_renderer.py +0 -423
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/readiness_renderer.py +0 -186
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/system_doc_hierarchy_renderer.py +0 -382
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/publishers/system_doc_renderer.py +0 -683
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/report_cmds.py +0 -788
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/__init__.py +0 -13
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/aggregation_report.py +0 -86
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/checklist_generator.py +0 -425
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/excel_generator.py +0 -599
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/gap_report.py +0 -131
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/json_generator.py +0 -188
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/markdown_generator.py +0 -595
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/pdf/__init__.py +0 -154
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/pdf/collector.py +0 -61
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/pdf/department_builder.py +0 -77
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/pdf/errors.py +0 -9
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/pdf/md_renderer.py +0 -386
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/pdf/pdf_models.py +0 -95
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/pdf/pdf_writer.py +0 -27
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/pdf/repo_project_builders.py +0 -274
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/readiness_report.py +0 -447
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/reporting.py +0 -94
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/reports/sarif_generator.py +0 -519
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/runtime_profiles.py +0 -98
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/seed/__init__.py +0 -29
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/seed/seed_loader.py +0 -561
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/skills/__init__.py +0 -5
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/skills/skill_routing.py +0 -312
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/sources/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/sources/base.py +0 -110
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/sources/bitbucket.py +0 -129
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/sources/git_url.py +0 -60
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/sources/github.py +0 -75
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/sources/local.py +0 -58
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/spec_sync_validator.py +0 -15
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/state/__init__.py +0 -6285
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/state/readiness_helpers.py +0 -74
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/state/skill_readiness.py +0 -487
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/state/store.py +0 -12927
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/state_cmds.py +0 -1868
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/sync/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/sync/repo_sync.py +0 -409
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/sync_cmds.py +0 -1247
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/utils/__init__.py +0 -3
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/utils/debug_bundle.py +0 -214
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/validators/checklist_validator.py +0 -342
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/workflow_cmds.py +0 -19147
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/workflows/__init__.py +0 -9
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/workflows/_test_audit_daily_batch.py +0 -192
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/workflows/audit_daily_batch.py +0 -308
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/workflows/audit_deep_monthly.py +0 -193
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/workflows/audit_drift_scan.py +0 -178
- package/tools/vds-scripts/audit_orchestrator/src/vds_audit_orchestrator/workflows/audit_security_daily.py +0 -183
- package/tools/vds-scripts/audit_orchestrator/templates/sample_audit_template.xlsx +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/_helpers.py +0 -32
- package/tools/vds-scripts/audit_orchestrator/tests/collectors/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/collectors/completeness/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/collectors/completeness/test_bitbucket_probe.py +0 -403
- package/tools/vds-scripts/audit_orchestrator/tests/collectors/completeness/test_confluence_probe.py +0 -423
- package/tools/vds-scripts/audit_orchestrator/tests/collectors/test_bitbucket_downloader.py +0 -289
- package/tools/vds-scripts/audit_orchestrator/tests/collectors/test_image_extractor.py +0 -260
- package/tools/vds-scripts/audit_orchestrator/tests/collectors/test_markdown_converter.py +0 -57
- package/tools/vds-scripts/audit_orchestrator/tests/collectors/test_material_cache.py +0 -197
- package/tools/vds-scripts/audit_orchestrator/tests/collectors/test_material_downloader.py +0 -550
- package/tools/vds-scripts/audit_orchestrator/tests/collectors/test_registry_parser.py +0 -3514
- package/tools/vds-scripts/audit_orchestrator/tests/collectors/test_registry_parser_department_entry.py +0 -214
- package/tools/vds-scripts/audit_orchestrator/tests/collectors/test_registry_parser_flow.py +0 -200
- package/tools/vds-scripts/audit_orchestrator/tests/conftest.py +0 -988
- package/tools/vds-scripts/audit_orchestrator/tests/engine/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/engine/test_calibration.py +0 -48
- package/tools/vds-scripts/audit_orchestrator/tests/engine/test_confluence_checklist_phase22_helpers.py +0 -6065
- package/tools/vds-scripts/audit_orchestrator/tests/engine/test_multi_judge.py +0 -62
- package/tools/vds-scripts/audit_orchestrator/tests/engine/test_stability_harness.py +0 -61
- package/tools/vds-scripts/audit_orchestrator/tests/engine/test_structured_metadata.py +0 -419
- package/tools/vds-scripts/audit_orchestrator/tests/factories/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/factories/models.py +0 -534
- package/tools/vds-scripts/audit_orchestrator/tests/factories/templates.py +0 -241
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/diagrams/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/diagrams/compressed.drawio +0 -2
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/diagrams/mockup.bmpr +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/diagrams/simple.drawio +0 -26
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/bitbucket/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/bitbucket/branch_permissions_cli.json +0 -26
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/bitbucket/branch_permissions_direct.json +0 -24
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/bitbucket/repo_conditions_cli.json +0 -14
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/bitbucket/repo_conditions_direct.json +0 -12
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/confluence/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/confluence/page_cli.json +0 -7
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/confluence/page_direct.json +0 -7
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/confluence/search_cli.json +0 -11
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/confluence/search_direct.json +0 -7
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/sonarqube/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/sonarqube/quality_gate_cli.json +0 -12
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/golden/sonarqube/quality_gate_direct.json +0 -12
- package/tools/vds-scripts/audit_orchestrator/tests/fixtures/requirement_strategy_phase115.json +0 -118
- package/tools/vds-scripts/audit_orchestrator/tests/integration/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/integration/conftest.py +0 -107
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/expected_outcomes.md +0 -50
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_audit_repo/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_audit_repo/auth.py +0 -27
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_audit_repo/config.py +0 -16
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_audit_repo/db.py +0 -24
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_audit_repo/main.py +0 -18
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_audit_repo/src/__init__.py +0 -1
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_audit_repo/src/utils.py +0 -22
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_checklist_template.json +0 -110
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_evidence/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_evidence/code_evidence_pack.json +0 -40
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_evidence/manifest.json +0 -49
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_evidence/projects/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_evidence/projects/mock-audit-project/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_evidence/projects/mock-audit-project/brd.md +0 -19
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_evidence/projects/mock-audit-project/design.md +0 -32
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_evidence/projects/mock-audit-project/security.md +0 -23
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_evidence/projects/mock-audit-project/srs.md +0 -25
- package/tools/vds-scripts/audit_orchestrator/tests/integration/fixtures/mock_evidence/projects/mock-audit-project/test.md +0 -30
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_checkpoint_merge.py +0 -1371
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_decoupling_route_p149.py +0 -176
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_gap_analyzer_batch_p149.py +0 -151
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_hybrid_search.py +0 -799
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_mcp_integration.py +0 -741
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_merge_ranking_p149.py +0 -98
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_modality_mismatch_p149.py +0 -171
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_phase117_118_storage.py +0 -350
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_phase121_short_circuit.py +0 -732
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_phase18_workflow.py +0 -223
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_phase48_e2e_verification.py +0 -763
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_phase81_doc_anchor_regression.py +0 -252
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_provider_failure_finding_p149.py +0 -339
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_readiness_e2e.py +0 -430
- package/tools/vds-scripts/audit_orchestrator/tests/integration/test_refined_workflow.py +0 -1180
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/snapshots/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/snapshots/department_renderer.md +0 -24
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/snapshots/project_renderer.md +0 -8
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/snapshots/repo_renderer.md +0 -10
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/test_department_pdf.py +0 -112
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/test_e2e_pdf.py +0 -135
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/test_idempotency.py +0 -45
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/test_md_renderer.py +0 -46
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/test_pdf_cmds.py +0 -97
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/test_pdf_snapshot.py +0 -77
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/test_pdf_writer.py +0 -65
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/test_project_builder.py +0 -199
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/test_public_api.py +0 -135
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/test_repo_builder.py +0 -246
- package/tools/vds-scripts/audit_orchestrator/tests/pdf/test_workflow_pdf_flags.py +0 -36
- package/tools/vds-scripts/audit_orchestrator/tests/property/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/property/test_properties.py +0 -807
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_agent_error_compat.py +0 -38
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_agentic_skill_policy_skip.py +0 -234
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_base_event_stream_logging.py +0 -785
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_base_timeout_policy.py +0 -277
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_base_trace_payload_sanitization.py +0 -92
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_code_agent.py +0 -2311
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_code_agent_re_exports.py +0 -25
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_code_analysis_helpers.py +0 -94
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_create_audit_agent_reasoning_effort.py +0 -69
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_docs_agent.py +0 -2044
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_langgraph_workflow_efficiency_metrics.py +0 -71
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_output_validators.py +0 -317
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_phase41_toolsets.py +0 -6427
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_pydantic_ai_models.py +0 -1219
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_pydantic_base_url_resolution.py +0 -84
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_security_agent.py +0 -2069
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_skill_manager_focus.py +0 -439
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_synthesis_agent.py +0 -1195
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_tool_efficiency_guard_fr120.py +0 -683
- package/tools/vds-scripts/audit_orchestrator/tests/test_agents/test_toolsets.py +0 -716
- package/tools/vds-scripts/audit_orchestrator/tests/test_aggregator_p149.py +0 -171
- package/tools/vds-scripts/audit_orchestrator/tests/test_alembic_migrations.py +0 -287
- package/tools/vds-scripts/audit_orchestrator/tests/test_anchor_allowlist_p149.py +0 -273
- package/tools/vds-scripts/audit_orchestrator/tests/test_audit_otel.py +0 -283
- package/tools/vds-scripts/audit_orchestrator/tests/test_checklist_models.py +0 -583
- package/tools/vds-scripts/audit_orchestrator/tests/test_checks/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_checks/test_base_check.py +0 -211
- package/tools/vds-scripts/audit_orchestrator/tests/test_checks/test_llm_checks.py +0 -126
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_analyze_command.py +0 -400
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_archive_stale_page_cli.py +0 -217
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_bitbucket_metadata_cli.py +0 -354
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_cli_impl_profile_availability.py +0 -114
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_codex_profile.py +0 -174
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_compare_backends_cli.py +0 -449
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_confluence_parent_auto_resolve.py +0 -451
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_corpus_purge_cli.py +0 -290
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_credentials_preflight.py +0 -106
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_debug_bundle.py +0 -37
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_deprecation_phase157.py +0 -484
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_dispatch_concurrency_diagnostics.py +0 -758
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_check_confluence_cli.py +0 -320
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_codex.py +0 -187
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_corpus_status_cli.py +0 -236
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_correlation_cli.py +0 -128
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_crawl_status_cli.py +0 -192
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_credentials_cli.py +0 -86
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_dispatch_status_cli.py +0 -421
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_heartbeat_phase169.py +0 -173
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_hierarchy_status_cli.py +0 -199
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_locks_cli.py +0 -134
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_logs_follow_cli.py +0 -305
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_migration.py +0 -333
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_profile_availability_cli.py +0 -151
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_doctor_skills_policy_cli.py +0 -153
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_evidence_quality_cli.py +0 -307
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_export_debug_bundle_phase36.py +0 -60
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_export_git_manifest_cli.py +0 -172
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_file_removal_phase157e.py +0 -770
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_grounding_classifier.py +0 -226
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_logging.py +0 -49
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_materials_cli.py +0 -9127
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_metadata_completeness_phase92.py +0 -364
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_parent_dispatch_finalization_phase168f.py +0 -111
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_parse_cli.py +0 -590
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_phase117_118_feature_flags.py +0 -219
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_phase164_control_plane.py +0 -718
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_phase165_runner_scripts.py +0 -230
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_preparation_classifications.py +0 -146
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_prepare_cli.py +0 -398
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_publication_quality_gate.py +0 -126
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_publish_system_doc_cli.py +0 -158
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_query_checklist_cli.py +0 -219
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_readiness_cli.py +0 -673
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_readiness_cli_integration.py +0 -689
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_removed_flags_phase92.py +0 -36
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_report_cmds.py +0 -1317
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_run_history_index.py +0 -57
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_run_management.py +0 -1194
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_runtime_profiles_cli.py +0 -1658
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_smart_run_selection.py +0 -1562
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_state_cli.py +0 -2467
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_state_migration.py +0 -339
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_sync_repos_debug_artifacts.py +0 -1109
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_upload_results_cli.py +0 -809
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_validate_checklist.py +0 -178
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_validate_checklist_cli.py +0 -110
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_validate_spec_sync_cli.py +0 -519
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_workflow_default_parameters_baseline.py +0 -101
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_workflow_options.py +0 -7896
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_workflow_project_db_modes.py +0 -6516
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_workflow_project_project_scope.py +0 -831
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_workflow_project_target.py +0 -611
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_workflow_projects_phase131_lifecycle.py +0 -2488
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_workflow_projects_phase131_scaffolding.py +0 -96
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_workflow_row_key_guard.py +0 -78
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli/test_workflow_summary_artifacts.py +0 -1872
- package/tools/vds-scripts/audit_orchestrator/tests/test_cli_paths_phase2.py +0 -45
- package/tools/vds-scripts/audit_orchestrator/tests/test_clients/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_clients/test_bitbucket_cli_client.py +0 -124
- package/tools/vds-scripts/audit_orchestrator/tests/test_clients/test_cli_parity.py +0 -110
- package/tools/vds-scripts/audit_orchestrator/tests/test_clients/test_confluence_cli_client.py +0 -1149
- package/tools/vds-scripts/audit_orchestrator/tests/test_clients/test_sonarqube_cli_client.py +0 -19
- package/tools/vds-scripts/audit_orchestrator/tests/test_collectors/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_collectors/test_linked_page_tracker.py +0 -118
- package/tools/vds-scripts/audit_orchestrator/tests/test_companion_context_service.py +0 -230
- package/tools/vds-scripts/audit_orchestrator/tests/test_dspy_modules/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_dspy_modules/conftest.py +0 -11
- package/tools/vds-scripts/audit_orchestrator/tests/test_dspy_modules/test_compile_artifact.py +0 -465
- package/tools/vds-scripts/audit_orchestrator/tests/test_dspy_modules/test_cross_provider_critique.py +0 -120
- package/tools/vds-scripts/audit_orchestrator/tests/test_dspy_modules/test_cross_provider_critique_e2e.py +0 -75
- package/tools/vds-scripts/audit_orchestrator/tests/test_dspy_modules/test_evaluation.py +0 -515
- package/tools/vds-scripts/audit_orchestrator/tests/test_dspy_modules/test_runtime_loader.py +0 -537
- package/tools/vds-scripts/audit_orchestrator/tests/test_dspy_modules/test_signatures_normalization.py +0 -172
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_auditor_applicability.py +0 -68
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_checklist_generator.py +0 -1252
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_checklist_projection.py +0 -54
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_confluence_checklist_projection_consistency.py +0 -1696
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_critique_merger_matrix.py +0 -120
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_cross_check_rules.py +0 -459
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_cross_provider_critique.py +0 -55
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_doc_loader.py +0 -73
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_drift_detector.py +0 -34
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_evidence_collectors.py +0 -93
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_lease_timeout.py +0 -114
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_loader.py +0 -350
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_loader_parity.py +0 -179
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_low_confidence_reeval.py +0 -691
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_phase145a_completion.py +0 -209
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_phase31_row_consistency_retry_benchmark.py +0 -150
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_profile_detector.py +0 -286
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_regression_guard.py +0 -53
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_result_merger.py +0 -619
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_row_evaluator.py +0 -15783
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_row_failover.py +0 -215
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_scorer.py +0 -597
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_skill_breakdown_telemetry_fr137.py +0 -421
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_targeted_auto_merge.py +0 -229
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_timeout_failover.py +0 -488
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_timeout_telemetry.py +0 -73
- package/tools/vds-scripts/audit_orchestrator/tests/test_engine/test_validator.py +0 -419
- package/tools/vds-scripts/audit_orchestrator/tests/test_incremental/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_incremental/test_diff_detector.py +0 -111
- package/tools/vds-scripts/audit_orchestrator/tests/test_infra_persistence.py +0 -291
- package/tools/vds-scripts/audit_orchestrator/tests/test_integration/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_integration/test_phase3_integration.py +0 -516
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_cache.py +0 -670
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_codex_model_builder.py +0 -281
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_codex_oauth.py +0 -330
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_codex_streaming.py +0 -433
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_cost_tracker.py +0 -27
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_engine.py +0 -876
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_evaluator.py +0 -212
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_instruction_templates.py +0 -639
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_prompt_metadata.py +0 -97
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_prompts.py +0 -660
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_provider.py +0 -330
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_provider_contract_sync.py +0 -18
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_reasoning_effort_validation.py +0 -565
- package/tools/vds-scripts/audit_orchestrator/tests/test_llm/test_schemas.py +0 -827
- package/tools/vds-scripts/audit_orchestrator/tests/test_logging_config.py +0 -297
- package/tools/vds-scripts/audit_orchestrator/tests/test_models/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_models/test_enums.py +0 -185
- package/tools/vds-scripts/audit_orchestrator/tests/test_models/test_findings.py +0 -1159
- package/tools/vds-scripts/audit_orchestrator/tests/test_models/test_project_profile.py +0 -307
- package/tools/vds-scripts/audit_orchestrator/tests/test_models/test_registry.py +0 -532
- package/tools/vds-scripts/audit_orchestrator/tests/test_models/test_template.py +0 -708
- package/tools/vds-scripts/audit_orchestrator/tests/test_observability/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_observability/test_metrics.py +0 -60
- package/tools/vds-scripts/audit_orchestrator/tests/test_paths_config_phase2.py +0 -21
- package/tools/vds-scripts/audit_orchestrator/tests/test_performance/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_performance/test_fr79_performance_guardrails.py +0 -199
- package/tools/vds-scripts/audit_orchestrator/tests/test_phase156_hardening.py +0 -498
- package/tools/vds-scripts/audit_orchestrator/tests/test_phase93_regression_guards.py +0 -123
- package/tools/vds-scripts/audit_orchestrator/tests/test_pipeline_integration.py +0 -517
- package/tools/vds-scripts/audit_orchestrator/tests/test_profiles/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_profiles/test_detection.py +0 -146
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_bitbucket_link_resolver.py +0 -55
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_checklist_renderer.py +0 -84
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_checklist_renderer_projection.py +0 -97
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_confluence_macros.py +0 -58
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_confluence_publisher.py +0 -2171
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_evidence_links.py +0 -129
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_field_sanitizer.py +0 -108
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_hierarchy_publisher.py +0 -134
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_incremental_plan_parser.py +0 -62
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_live_data_injector.py +0 -48
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_macro_builder.py +0 -22
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_p161_confluence_optimization.py +0 -168
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_priority_renderer.py +0 -96
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_project_aggregate_renderer.py +0 -364
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_storage_validation.py +0 -273
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_summary_refactor.py +0 -118
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_system_doc_hierarchy.py +0 -50
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_table_builder.py +0 -23
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_vietnamese_templates.py +0 -37
- package/tools/vds-scripts/audit_orchestrator/tests/test_publishers/test_wiring_integration.py +0 -290
- package/tools/vds-scripts/audit_orchestrator/tests/test_reports/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_reports/test_aggregation_report.py +0 -181
- package/tools/vds-scripts/audit_orchestrator/tests/test_reports/test_checklist_generator.py +0 -258
- package/tools/vds-scripts/audit_orchestrator/tests/test_reports/test_gap_report.py +0 -73
- package/tools/vds-scripts/audit_orchestrator/tests/test_reports/test_json_generator.py +0 -317
- package/tools/vds-scripts/audit_orchestrator/tests/test_result_merger_p149.py +0 -347
- package/tools/vds-scripts/audit_orchestrator/tests/test_route_mode_p149.py +0 -178
- package/tools/vds-scripts/audit_orchestrator/tests/test_rubric_parser.py +0 -179
- package/tools/vds-scripts/audit_orchestrator/tests/test_scorer.py +0 -110
- package/tools/vds-scripts/audit_orchestrator/tests/test_state/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_state/test_sparse_coverage.py +0 -117
- package/tools/vds-scripts/audit_orchestrator/tests/test_workflow/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/test_workflow/test_langgraph_workflow.py +0 -2072
- package/tools/vds-scripts/audit_orchestrator/tests/test_workflow/test_p161_runtime_hardening.py +0 -341
- package/tools/vds-scripts/audit_orchestrator/tests/test_workflow_cmds_p149.py +0 -112
- package/tools/vds-scripts/audit_orchestrator/tests/test_workflow_cmds_p172.py +0 -126
- package/tools/vds-scripts/audit_orchestrator/tests/test_workflow_guidance_p150.py +0 -95
- package/tools/vds-scripts/audit_orchestrator/tests/unit/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_agentic_investigator_phase115.py +0 -42
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_requirement_analysis_agent.py +0 -412
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_security_agent_updates.py +0 -131
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_security_scanner.py +0 -397
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_skill_executor.py +0 -316
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_skill_fallback.py +0 -299
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_skill_policy.py +0 -520
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_skill_telemetry.py +0 -306
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_synthesis_fixes.py +0 -761
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_tool_argument_robustness.py +0 -272
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_tool_registry.py +0 -2548
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_tool_registry_ast_grep.py +0 -87
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_tool_registry_phase123_scoping.py +0 -353
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_tool_registry_phase94_ff.py +0 -445
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_tool_registry_vector_search_phase115.py +0 -35
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_utils.py +0 -1007
- package/tools/vds-scripts/audit_orchestrator/tests/unit/agents/test_vector_evidence_toolset.py +0 -622
- package/tools/vds-scripts/audit_orchestrator/tests/unit/cli/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/cli/test_workflow_cli.py +0 -123
- package/tools/vds-scripts/audit_orchestrator/tests/unit/collectors/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/collectors/test_cache_guard.py +0 -479
- package/tools/vds-scripts/audit_orchestrator/tests/unit/collectors/test_checklist_parser_phase120.py +0 -55
- package/tools/vds-scripts/audit_orchestrator/tests/unit/collectors/test_diagram_extractor.py +0 -467
- package/tools/vds-scripts/audit_orchestrator/tests/unit/collectors/test_enrichment_extractor.py +0 -59
- package/tools/vds-scripts/audit_orchestrator/tests/unit/collectors/test_graphify_collector.py +0 -158
- package/tools/vds-scripts/audit_orchestrator/tests/unit/completeness/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/completeness/test_completeness.py +0 -563
- package/tools/vds-scripts/audit_orchestrator/tests/unit/completeness/test_freshness_evaluator.py +0 -493
- package/tools/vds-scripts/audit_orchestrator/tests/unit/completeness/test_material_cache_metrics.py +0 -365
- package/tools/vds-scripts/audit_orchestrator/tests/unit/completeness/test_material_completeness_service.py +0 -2736
- package/tools/vds-scripts/audit_orchestrator/tests/unit/config_resolution/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/config_resolution/test_discovery.py +0 -47
- package/tools/vds-scripts/audit_orchestrator/tests/unit/config_resolution/test_redaction.py +0 -15
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_ad_hoc_analyzer.py +0 -576
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_agent_loop.py +0 -1896
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_anchor_filter_cl003.py +0 -181
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_batch_evidence_cache.py +0 -155
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_batch_processor.py +0 -3608
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_checklist_contract.py +0 -55
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_checklist_scoping.py +0 -371
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_config_companion_phase123.py +0 -142
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_config_evidence_phase123.py +0 -249
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_confluence_checklist_contract_export_parity.py +0 -813
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_cross_repo_config_phase122.py +0 -613
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_dspy_requirement_classifier.py +0 -517
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_evidence_diversity.py +0 -144
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_evidence_truncation.py +0 -108
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_grounding_validator.py +0 -127
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_guidance_injection_phase120.py +0 -105
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_instruction_analysis_phase122.py +0 -761
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_instruction_pre_filter_phase167.py +0 -334
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_llm_row_evaluator_retries.py +0 -3684
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_loader_phase123.py +0 -345
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_manual_check_gating_phase122.py +0 -474
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_parallel_eval.py +0 -263
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_phase122_verifier_phase122.py +0 -169
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_phase166_route_failover.py +0 -437
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_post_eval_cl003_shared_lib.py +0 -267
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_postproc_streaming.py +0 -194
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_pre_eval_gating_phase122.py +0 -362
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_prepare_topology_coverage.py +0 -247
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_private_dns_sanitization_phase104.py +0 -397
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_project_evidence_context.py +0 -450
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_project_knowledge_store.py +0 -487
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_project_topology.py +0 -1142
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_provider_failure_classifier.py +0 -195
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_readiness_extractor.py +0 -496
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_readiness_synthesizer.py +0 -653
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_repo_type_classifier.py +0 -303
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_requirement_analysis.py +0 -508
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_requirement_analysis_execution_scope.py +0 -239
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_requirement_analysis_phase114.py +0 -919
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_requirement_analysis_phase115.py +0 -97
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_requirement_analysis_shared_lib.py +0 -340
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_requirement_classification_drift.py +0 -729
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_requirement_classification_nlp.py +0 -670
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_requirement_scope_phase122.py +0 -615
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_route_matrix.py +0 -258
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_route_override.py +0 -141
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_routing_precision.py +0 -650
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_row_evaluator_dual_evidence.py +0 -2987
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_row_evaluator_instruction_runtime_phase122.py +0 -365
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_row_evaluator_runtime.py +0 -830
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_runtime_hardening_phase122.py +0 -225
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_scoped_na_skip.py +0 -107
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_scoring_enhancements.py +0 -404
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_shared_library_retrieval_phase123.py +0 -441
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_shared_library_routing_phase123.py +0 -279
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_shared_resource_indexing_phase122.py +0 -188
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_skill_recommendation.py +0 -225
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_skill_routing_cl003_shared_lib.py +0 -338
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_skills_toolset.py +0 -319
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_stability_metric.py +0 -60
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_target_selector.py +0 -958
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_token_tracker.py +0 -121
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_token_wiring.py +0 -119
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_tool_first_planner.py +0 -7103
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_topology_knowledge_persistence.py +0 -332
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_topology_query_service.py +0 -55
- package/tools/vds-scripts/audit_orchestrator/tests/unit/engine/test_unverified_ref_retry.py +0 -909
- package/tools/vds-scripts/audit_orchestrator/tests/unit/models/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/models/test_evidence.py +0 -515
- package/tools/vds-scripts/audit_orchestrator/tests/unit/models/test_gaps.py +0 -422
- package/tools/vds-scripts/audit_orchestrator/tests/unit/models/test_readiness.py +0 -428
- package/tools/vds-scripts/audit_orchestrator/tests/unit/publishers/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/publishers/test_confluence_hierarchy.py +0 -227
- package/tools/vds-scripts/audit_orchestrator/tests/unit/publishers/test_project_title_generation.py +0 -335
- package/tools/vds-scripts/audit_orchestrator/tests/unit/publishers/test_publisher_registry_helpers.py +0 -290
- package/tools/vds-scripts/audit_orchestrator/tests/unit/publishers/test_publisher_registry_integration.py +0 -557
- package/tools/vds-scripts/audit_orchestrator/tests/unit/publishers/test_readiness_renderer.py +0 -381
- package/tools/vds-scripts/audit_orchestrator/tests/unit/publishers/test_repo_title_consistency.py +0 -266
- package/tools/vds-scripts/audit_orchestrator/tests/unit/publishers/test_upload_hierarchy_integration.py +0 -470
- package/tools/vds-scripts/audit_orchestrator/tests/unit/scripts/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/scripts/test_benchmark_dspy.py +0 -177
- package/tools/vds-scripts/audit_orchestrator/tests/unit/scripts/test_benchmark_nlp_accuracy.py +0 -72
- package/tools/vds-scripts/audit_orchestrator/tests/unit/scripts/test_benchmark_retrieval_modes.py +0 -123
- package/tools/vds-scripts/audit_orchestrator/tests/unit/scripts/test_verify_phase111_requirement_analysis.py +0 -409
- package/tools/vds-scripts/audit_orchestrator/tests/unit/seed/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/seed/test_seed_chain_cli.py +0 -277
- package/tools/vds-scripts/audit_orchestrator/tests/unit/seed/test_seed_loader.py +0 -502
- package/tools/vds-scripts/audit_orchestrator/tests/unit/skills/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/skills/test_skill_routing.py +0 -209
- package/tools/vds-scripts/audit_orchestrator/tests/unit/sources/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/sources/test_bitbucket_source.py +0 -66
- package/tools/vds-scripts/audit_orchestrator/tests/unit/sources/test_non_retryable_markers.py +0 -88
- package/tools/vds-scripts/audit_orchestrator/tests/unit/sources/test_repo_info.py +0 -212
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_completeness.py +0 -598
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_dispatch_events_contract_phase169.py +0 -100
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_dispatch_hardening_phase158.py +0 -392
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_dispatch_persistence_phase157.py +0 -914
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_embedding_client.py +0 -64
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_get_latest_completed_run.py +0 -313
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_heartbeat_phase169.py +0 -109
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_hybrid_search.py +0 -398
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_normalize_url.py +0 -262
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_phase152_query_surface.py +0 -59
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_phase98_confluence_document_model.py +0 -202
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_published_pages.py +0 -754
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_readiness_helpers.py +0 -193
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_run_ledger.py +0 -522
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_run_management.py +0 -378
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_schema_contract_phase170.py +0 -755
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_state_cmds.py +0 -231
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_state_loaders.py +0 -2151
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_state_run_api.py +0 -2226
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_store.py +0 -1435
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_store_dispatch.py +0 -646
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_store_dispatch_status_view.py +0 -181
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_store_scope.py +0 -213
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_utilization_persist_phase169.py +0 -77
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_vds_search.py +0 -263
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_vector_index_api.py +0 -319
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_vector_index_runtime.py +0 -175
- package/tools/vds-scripts/audit_orchestrator/tests/unit/state/test_vector_index_store.py +0 -1756
- package/tools/vds-scripts/audit_orchestrator/tests/unit/sync/__init__.py +0 -0
- package/tools/vds-scripts/audit_orchestrator/tests/unit/sync/test_repo_sync.py +0 -257
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_artifact_exclusion.py +0 -119
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_auto_promote_phase158.py +0 -337
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_carry_forward_artifact_filtering.py +0 -317
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_checklist_precache_p160a.py +0 -416
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_cli_decomposition_fr219.py +0 -269
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_code_chunk_carry_forward.py +0 -203
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_config_coherence.py +0 -180
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_config_secret_policy.py +0 -522
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_corpus_project_id_migration.py +0 -318
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_corpus_status_diagnostics.py +0 -239
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_department_priority_ordering.py +0 -131
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_dispatch_coordinator_phase158.py +0 -402
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_dispatch_job_identity_p167a.py +0 -238
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_dispatch_ramp_up_phase171.py +0 -434
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_dispatcher.py +0 -911
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_doc_type_en_inference.py +0 -246
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_early_exit_unchunked_repos.py +0 -111
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_errors.py +0 -237
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_errors_taxonomy.py +0 -83
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_extract_chunking_config_phase98.py +0 -73
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_extract_cmds_state_helpers.py +0 -33
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_extract_docs_code_chunking.py +0 -260
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_finalize_dispatch_run_phase168.py +0 -341
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_identity.py +0 -221
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_infrastructure_detection.py +0 -441
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_junction_table_phase95.py +0 -259
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_late_binding_assignment_p167c.py +0 -286
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_misc_cmds_fr224_225_hardening.py +0 -194
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_p172_integration.py +0 -306
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_parent_provider_preflight.py +0 -118
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_performance_gates_phase92.py +0 -141
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_performance_gates_phase93.py +0 -50
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_phase115_search_strategy.py +0 -106
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_phase154_title_consistency.py +0 -117
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_phase155_param_forwarding.py +0 -304
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_phase158_concurrency_defaults.py +0 -207
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_phase170_doctor_schema.py +0 -319
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_phase170_regression.py +0 -334
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_phase94_corpus_lifecycle.py +0 -307
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_phase96_repo_key_migration.py +0 -305
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_pipelined_scheduling.py +0 -130
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_profile_availability_probe.py +0 -616
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_profile_aware_row_timeout.py +0 -102
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_profile_timeout_stagger_p160cd.py +0 -205
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_progress_summary_phase169.py +0 -96
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_registry_checklist_diagnostics.py +0 -124
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_resume_manifest_p167b.py +0 -268
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_risk_mitigations_p160e1.py +0 -348
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_single_row_shards_p160b.py +0 -357
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_state_repo_discovery.py +0 -504
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_sync_metadata_entries.py +0 -57
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_task_models.py +0 -1796
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_utilization_telemetry_p167e.py +0 -259
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_vietnamese_fts_hardening.py +0 -160
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_workflow_phase98_enrichment.py +0 -92
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_workflow_project_merge_materialization.py +0 -322
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_workflow_row_key_migration_guard.py +0 -88
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_workflow_short_circuit_phase121.py +0 -564
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_workflow_single_target_row_context.py +0 -49
- package/tools/vds-scripts/audit_orchestrator/tests/unit/test_zero_result_messaging.py +0 -76
- package/tools/vds-scripts/bandit-report.json +0 -2974
- package/tools/vds-scripts/brd_orchestrator/README.md +0 -29
- package/tools/vds-scripts/brd_orchestrator/pyproject.toml +0 -63
- package/tools/vds-scripts/brd_orchestrator/src/vds_brd_orchestrator/__init__.py +0 -17
- package/tools/vds-scripts/brd_orchestrator/src/vds_brd_orchestrator/cli.py +0 -187
- package/tools/vds-scripts/brd_orchestrator/src/vds_brd_orchestrator/validator.py +0 -121
- package/tools/vds-scripts/brd_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/brd_orchestrator/tests/test_cli.py +0 -62
- package/tools/vds-scripts/brd_orchestrator/tests/test_validator.py +0 -33
- package/tools/vds-scripts/circular_dependency_orchestrator/README.md +0 -30
- package/tools/vds-scripts/circular_dependency_orchestrator/pyproject.toml +0 -43
- package/tools/vds-scripts/circular_dependency_orchestrator/src/vds_circular_dependency_orchestrator/__init__.py +0 -16
- package/tools/vds-scripts/circular_dependency_orchestrator/src/vds_circular_dependency_orchestrator/cli.py +0 -904
- package/tools/vds-scripts/circular_dependency_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/circular_dependency_orchestrator/tests/unit/__init__.py +0 -0
- package/tools/vds-scripts/circular_dependency_orchestrator/tests/unit/test_cli.py +0 -354
- package/tools/vds-scripts/coverage.json +0 -1
- package/tools/vds-scripts/create_pr.py +0 -57
- package/tools/vds-scripts/diagram_generator/README.md +0 -663
- package/tools/vds-scripts/diagram_generator/ci_validate.sh +0 -16
- package/tools/vds-scripts/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-component.png +0 -0
- package/tools/vds-scripts/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-component.puml +0 -23
- package/tools/vds-scripts/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-sequence.png +0 -0
- package/tools/vds-scripts/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-sequence.puml +0 -21
- package/tools/vds-scripts/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-usecase.png +0 -0
- package/tools/vds-scripts/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-usecase.puml +0 -14
- package/tools/vds-scripts/diagram_generator/examples/github-actions-validate.yml +0 -39
- package/tools/vds-scripts/diagram_generator/generate_all_diagrams.py +0 -827
- package/tools/vds-scripts/diagram_generator/generate_insurance_c4_diagrams.py +0 -261
- package/tools/vds-scripts/diagram_generator/generate_insurance_c4_quick.py +0 -486
- package/tools/vds-scripts/diagram_generator/pyproject.toml +0 -28
- package/tools/vds-scripts/diagram_generator/render_png.py +0 -59
- package/tools/vds-scripts/diagram_generator/src/vds_diagram_generator/__init__.py +0 -3
- package/tools/vds-scripts/diagram_generator/src/vds_diagram_generator/cli.py +0 -50
- package/tools/vds-scripts/diagram_generator/test_c4_hierarchical.py +0 -142
- package/tools/vds-scripts/diagram_generator/test_c4_quick.py +0 -131
- package/tools/vds-scripts/diagram_generator/tests/__init__.py +0 -0
- package/tools/vds-scripts/diagram_generator/tests/test_analyzer_completeness.py +0 -260
- package/tools/vds-scripts/diagram_generator/tests/test_c4_syntax_correctness.py +0 -138
- package/tools/vds-scripts/diagram_generator/tests/test_component_coverage.py +0 -182
- package/tools/vds-scripts/diagram_generator/tests/test_mermaid_output.py +0 -80
- package/tools/vds-scripts/diagram_generator/tests/test_png_generation.py +0 -112
- package/tools/vds-scripts/diagram_generator/tests/test_scenario_templates.py +0 -15
- package/tools/vds-scripts/diagram_generator/tests/test_sequence_accuracy.py +0 -93
- package/tools/vds-scripts/diagram_generator/tests/test_structurizr_export.py +0 -177
- package/tools/vds-scripts/diagram_generator/tests/test_style_consistency.py +0 -174
- package/tools/vds-scripts/diagram_generator/tests/test_usecase_generator.py +0 -201
- package/tools/vds-scripts/diagram_generator/tests/test_usecase_integration.py +0 -124
- package/tools/vds-scripts/docker/compose.phase2-verification.yml +0 -31
- package/tools/vds-scripts/docker-compose.openapi-validator.yml +0 -14
- package/tools/vds-scripts/excel_orchestrator/README.md +0 -288
- package/tools/vds-scripts/excel_orchestrator/RESEARCH_BASED_UPDATES_REPORT.md +0 -261
- package/tools/vds-scripts/excel_orchestrator/add_essential_missing_effort.py +0 -255
- package/tools/vds-scripts/excel_orchestrator/adjust_effort_complexity.py +0 -184
- package/tools/vds-scripts/excel_orchestrator/brd_analysis_and_task_breakdown.py +0 -632
- package/tools/vds-scripts/excel_orchestrator/brd_analysis_comprehensive.py +0 -1029
- package/tools/vds-scripts/excel_orchestrator/check_overlaps_and_brd_coverage.py +0 -570
- package/tools/vds-scripts/excel_orchestrator/clean_remarks_column.py +0 -127
- package/tools/vds-scripts/excel_orchestrator/comprehensive_brd_check.py +0 -322
- package/tools/vds-scripts/excel_orchestrator/create_buffered_summary.py +0 -119
- package/tools/vds-scripts/excel_orchestrator/create_service_totals_sheet.py +0 -118
- package/tools/vds-scripts/excel_orchestrator/examples/basic_operations.py +0 -85
- package/tools/vds-scripts/excel_orchestrator/expand_all_tasks.py +0 -341
- package/tools/vds-scripts/excel_orchestrator/expand_tasks.py +0 -304
- package/tools/vds-scripts/excel_orchestrator/fill_brd_references.py +0 -347
- package/tools/vds-scripts/excel_orchestrator/fill_remarks_and_colors.py +0 -132
- package/tools/vds-scripts/excel_orchestrator/finalize_brd_and_cleanup.py +0 -295
- package/tools/vds-scripts/excel_orchestrator/finalize_brd_coverage.py +0 -327
- package/tools/vds-scripts/excel_orchestrator/fix_all_formulas.py +0 -99
- package/tools/vds-scripts/excel_orchestrator/fix_detail_presentation.py +0 -113
- package/tools/vds-scripts/excel_orchestrator/fix_presentation_and_effort.py +0 -116
- package/tools/vds-scripts/excel_orchestrator/fix_presentation_consistency.py +0 -231
- package/tools/vds-scripts/excel_orchestrator/fix_remarks_matching.py +0 -179
- package/tools/vds-scripts/excel_orchestrator/group_tasks_by_service_id.py +0 -210
- package/tools/vds-scripts/excel_orchestrator/increase_brd_coverage.py +0 -497
- package/tools/vds-scripts/excel_orchestrator/increase_effort_complexity.py +0 -155
- package/tools/vds-scripts/excel_orchestrator/organize_and_deduplicate.py +0 -273
- package/tools/vds-scripts/excel_orchestrator/pyproject.toml +0 -64
- package/tools/vds-scripts/excel_orchestrator/rebuild_all_formulas.py +0 -146
- package/tools/vds-scripts/excel_orchestrator/remove_base_multiplier_and_check_duplicates.py +0 -310
- package/tools/vds-scripts/excel_orchestrator/remove_duplicate_brd_tasks.py +0 -137
- package/tools/vds-scripts/excel_orchestrator/research_based_updates.py +0 -457
- package/tools/vds-scripts/excel_orchestrator/restore_e_values.py +0 -172
- package/tools/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/__init__.py +0 -5
- package/tools/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/cli.py +0 -746
- package/tools/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/config.py +0 -74
- package/tools/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/converters.py +0 -226
- package/tools/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/errors.py +0 -88
- package/tools/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/excel_client.py +0 -443
- package/tools/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/formatters.py +0 -211
- package/tools/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/logging.py +0 -57
- package/tools/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/source_contract.py +0 -29
- package/tools/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/target_state_status.py +0 -837
- package/tools/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/ulnc_alignment.py +0 -1291
- package/tools/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/validators.py +0 -164
- package/tools/vds-scripts/excel_orchestrator/sync_detail_and_total_sheets.py +0 -211
- package/tools/vds-scripts/excel_orchestrator/tests/__init__.py +0 -1
- package/tools/vds-scripts/excel_orchestrator/tests/conftest.py +0 -36
- package/tools/vds-scripts/excel_orchestrator/tests/test_cli.py +0 -383
- package/tools/vds-scripts/excel_orchestrator/tests/test_excel_client.py +0 -129
- package/tools/vds-scripts/excel_orchestrator/tests/test_ulnc_alignment.py +0 -373
- package/tools/vds-scripts/excel_orchestrator/tests/test_validators.py +0 -64
- package/tools/vds-scripts/excel_orchestrator/update_api_database_effort.py +0 -261
- package/tools/vds-scripts/excel_orchestrator/update_buffers_inline.py +0 -115
- package/tools/vds-scripts/excel_orchestrator/update_complex_services_and_add_new.py +0 -336
- package/tools/vds-scripts/excel_orchestrator/update_responsibility_and_fix_rows.py +0 -208
- package/tools/vds-scripts/excel_orchestrator/update_task_breakdown_vietnamese.py +0 -309
- package/tools/vds-scripts/excel_orchestrator/update_vietnamese_and_responsibility.py +0 -415
- package/tools/vds-scripts/excel_orchestrator/verify_brd_coverage_comprehensive.py +0 -401
- package/tools/vds-scripts/hexagonal_orchestrator/README.md +0 -530
- package/tools/vds-scripts/hexagonal_orchestrator/pyproject.toml +0 -48
- package/tools/vds-scripts/hexagonal_orchestrator/src/vds_hexagonal_orchestrator/__init__.py +0 -39
- package/tools/vds-scripts/hexagonal_orchestrator/src/vds_hexagonal_orchestrator/analyzers/__init__.py +0 -19
- package/tools/vds-scripts/hexagonal_orchestrator/src/vds_hexagonal_orchestrator/analyzers/base.py +0 -95
- package/tools/vds-scripts/hexagonal_orchestrator/src/vds_hexagonal_orchestrator/analyzers/fallback.py +0 -614
- package/tools/vds-scripts/hexagonal_orchestrator/src/vds_hexagonal_orchestrator/analyzers/java.py +0 -372
- package/tools/vds-scripts/hexagonal_orchestrator/src/vds_hexagonal_orchestrator/analyzers/python.py +0 -437
- package/tools/vds-scripts/hexagonal_orchestrator/src/vds_hexagonal_orchestrator/cache.py +0 -331
- package/tools/vds-scripts/hexagonal_orchestrator/src/vds_hexagonal_orchestrator/classifier.py +0 -263
- package/tools/vds-scripts/hexagonal_orchestrator/src/vds_hexagonal_orchestrator/cli.py +0 -554
- package/tools/vds-scripts/hexagonal_orchestrator/src/vds_hexagonal_orchestrator/config.py +0 -577
- package/tools/vds-scripts/hexagonal_orchestrator/src/vds_hexagonal_orchestrator/models.py +0 -159
- package/tools/vds-scripts/hexagonal_orchestrator/src/vds_hexagonal_orchestrator/profiler.py +0 -451
- package/tools/vds-scripts/hexagonal_orchestrator/test-config.yaml +0 -38
- package/tools/vds-scripts/hexagonal_orchestrator/tests/__init__.py +0 -1
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/__init__.py +0 -1
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/java-compliant/adapter/driven/persistence/InMemoryUserRepository.java +0 -62
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/java-compliant/adapter/driving/api/UserController.java +0 -101
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/java-compliant/application/port/EmailService.java +0 -33
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/java-compliant/application/port/UserRepository.java +0 -45
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/java-compliant/application/usecase/CreateUser.java +0 -58
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/java-compliant/domain/entity/Email.java +0 -80
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/java-compliant/domain/entity/User.java +0 -98
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/java-noncompliant/domain/User.java +0 -64
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/java-with-frameworks/domain/Menu.java +0 -13
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/java-with-frameworks/domain/Product.java +0 -16
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/python-compliant/__init__.py +0 -1
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/python-compliant/application/__init__.py +0 -1
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/python-compliant/application/ports/__init__.py +0 -1
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/python-compliant/application/ports/email_service.py +0 -60
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/python-compliant/application/ports/user_repository.py +0 -78
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/python-compliant/domain/__init__.py +0 -1
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/python-compliant/domain/entities/__init__.py +0 -1
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/python-compliant/domain/entities/user.py +0 -56
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/python-compliant/domain/value_objects/__init__.py +0 -1
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/python-compliant/domain/value_objects/email.py +0 -63
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/python-noncompliant/application/user_service.py +0 -1837
- package/tools/vds-scripts/hexagonal_orchestrator/tests/fixtures/python-noncompliant/domain/user.py +0 -43
- package/tools/vds-scripts/hexagonal_orchestrator/tests/test_cache.py +0 -458
- package/tools/vds-scripts/hexagonal_orchestrator/tests/test_cli_integration.py +0 -942
- package/tools/vds-scripts/hexagonal_orchestrator/tests/test_cli_unit.py +0 -557
- package/tools/vds-scripts/hexagonal_orchestrator/tests/test_cross_repo_pollution.py +0 -275
- package/tools/vds-scripts/hexagonal_orchestrator/tests/test_foundation.py +0 -129
- package/tools/vds-scripts/hexagonal_orchestrator/tests/test_integration.py +0 -1524
- package/tools/vds-scripts/hexagonal_orchestrator/tests/test_java_analyzer.py +0 -642
- package/tools/vds-scripts/hexagonal_orchestrator/tests/test_timing_unit.py +0 -60
- package/tools/vds-scripts/intellij_orchestrator/README.md +0 -55
- package/tools/vds-scripts/intellij_orchestrator/pyproject.toml +0 -64
- package/tools/vds-scripts/intellij_orchestrator/src/vds_intellij_orchestrator/__init__.py +0 -17
- package/tools/vds-scripts/intellij_orchestrator/src/vds_intellij_orchestrator/cli.py +0 -210
- package/tools/vds-scripts/intellij_orchestrator/src/vds_intellij_orchestrator/core.py +0 -260
- package/tools/vds-scripts/intellij_orchestrator/tests/__init__.py +0 -1
- package/tools/vds-scripts/intellij_orchestrator/tests/test_cli.py +0 -112
- package/tools/vds-scripts/intellij_orchestrator/tests/test_core.py +0 -83
- package/tools/vds-scripts/links_orchestrator/README.md +0 -63
- package/tools/vds-scripts/links_orchestrator/pyproject.toml +0 -64
- package/tools/vds-scripts/links_orchestrator/src/vds_links_orchestrator/__init__.py +0 -10
- package/tools/vds-scripts/links_orchestrator/src/vds_links_orchestrator/cli.py +0 -254
- package/tools/vds-scripts/links_orchestrator/src/vds_links_orchestrator/validator.py +0 -244
- package/tools/vds-scripts/links_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/links_orchestrator/tests/test_cli.py +0 -128
- package/tools/vds-scripts/links_orchestrator/tests/test_validator.py +0 -76
- package/tools/vds-scripts/lsp_orchestrator/.dockerignore +0 -69
- package/tools/vds-scripts/lsp_orchestrator/ARCHITECTURE.md +0 -383
- package/tools/vds-scripts/lsp_orchestrator/CODE_QUALITY_IMPROVEMENTS.md +0 -196
- package/tools/vds-scripts/lsp_orchestrator/COMMANDS.md +0 -870
- package/tools/vds-scripts/lsp_orchestrator/Dockerfile +0 -59
- package/tools/vds-scripts/lsp_orchestrator/IMPLEMENTATION_SUMMARY.md +0 -490
- package/tools/vds-scripts/lsp_orchestrator/LSP_ISSUES_AND_FINDINGS.md +0 -380
- package/tools/vds-scripts/lsp_orchestrator/README.md +0 -616
- package/tools/vds-scripts/lsp_orchestrator/SETUP.md +0 -143
- package/tools/vds-scripts/lsp_orchestrator/TEST_COVERAGE_SUMMARY.md +0 -255
- package/tools/vds-scripts/lsp_orchestrator/VERIFICATION_CHECKLIST.md +0 -814
- package/tools/vds-scripts/lsp_orchestrator/docker-compose.yml +0 -102
- package/tools/vds-scripts/lsp_orchestrator/docs/FOR_LLMS.md +0 -401
- package/tools/vds-scripts/lsp_orchestrator/docs/explanation/lsp-response-matching.md +0 -79
- package/tools/vds-scripts/lsp_orchestrator/docs/how-to-guides/automate-with-json.md +0 -159
- package/tools/vds-scripts/lsp_orchestrator/docs/how-to-guides/docker-mode.md +0 -256
- package/tools/vds-scripts/lsp_orchestrator/docs/how-to-guides/navigate-code.md +0 -116
- package/tools/vds-scripts/lsp_orchestrator/docs/how-to-guides/parallel-processing.md +0 -179
- package/tools/vds-scripts/lsp_orchestrator/docs/how-to-guides/project-tool-detection.md +0 -320
- package/tools/vds-scripts/lsp_orchestrator/docs/how-to-guides/type-check-code.md +0 -46
- package/tools/vds-scripts/lsp_orchestrator/docs/how-to-guides/use-daemon-mode.md +0 -78
- package/tools/vds-scripts/lsp_orchestrator/docs/how-to-guides/wsl2-optimization.md +0 -227
- package/tools/vds-scripts/lsp_orchestrator/docs/index.md +0 -88
- package/tools/vds-scripts/lsp_orchestrator/docs/operator-hover-definition.md +0 -143
- package/tools/vds-scripts/lsp_orchestrator/docs/reference/commands.md +0 -581
- package/tools/vds-scripts/lsp_orchestrator/docs/reference/configuration.md +0 -422
- package/tools/vds-scripts/lsp_orchestrator/docs/tutorials/00-quick-start.md +0 -169
- package/tools/vds-scripts/lsp_orchestrator/pyproject.toml +0 -63
- package/tools/vds-scripts/lsp_orchestrator/src/test_file.py +0 -5
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/__init__.py +0 -3
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/aggregator.py +0 -340
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/basedpyright_runner.py +0 -167
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/cli.py +0 -3370
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/code_actions.py +0 -79
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/core.py +0 -3295
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/daemon_client.py +0 -672
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/daemon_manager.py +0 -577
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/daemon_server.py +0 -1040
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/detectors/__init__.py +0 -9
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/detectors/project_detector.py +0 -537
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/formatters.py +0 -141
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/ipc_protocol.py +0 -225
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/lsp_client.py +0 -957
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/lsp_router.py +0 -335
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/mcp_server.py +0 -181
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/models/__init__.py +0 -201
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/models/project_detector.py +0 -646
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/models/project_tools.py +0 -114
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/models.py +0 -399
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/mypy_runner.py +0 -209
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/protocols.py +0 -52
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/ruff_lsp_client.py +0 -109
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/ruff_runner.py +0 -44
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/utils.py +0 -959
- package/tools/vds-scripts/lsp_orchestrator/src/vds_lsp_orchestrator/workspace_indexer.py +0 -1037
- package/tools/vds-scripts/lsp_orchestrator/test_workspace_lsp.py +0 -6
- package/tools/vds-scripts/lsp_orchestrator/tests/__init__.py +0 -1
- package/tools/vds-scripts/lsp_orchestrator/tests/conftest.py +0 -6
- package/tools/vds-scripts/lsp_orchestrator/tests/test_aggregator.py +0 -59
- package/tools/vds-scripts/lsp_orchestrator/tests/test_cli.py +0 -111
- package/tools/vds-scripts/lsp_orchestrator/tests/test_detect_tools_command.py +0 -186
- package/tools/vds-scripts/lsp_orchestrator/tests/test_formatter_linter_detection.py +0 -519
- package/tools/vds-scripts/lsp_orchestrator/tests/test_integration_phase9_10_11.py +0 -367
- package/tools/vds-scripts/lsp_orchestrator/tests/test_mypy_runner.py +0 -482
- package/tools/vds-scripts/lsp_orchestrator/tests/test_package_manager_detection.py +0 -399
- package/tools/vds-scripts/lsp_orchestrator/tests/test_phase10.py +0 -389
- package/tools/vds-scripts/lsp_orchestrator/tests/test_phase11.py +0 -327
- package/tools/vds-scripts/lsp_orchestrator/tests/test_phase12_integration.py +0 -634
- package/tools/vds-scripts/lsp_orchestrator/tests/test_phase9.py +0 -196
- package/tools/vds-scripts/lsp_orchestrator/tests/test_project_detector.py +0 -377
- package/tools/vds-scripts/lsp_orchestrator/tests/test_test_runner_detection.py +0 -549
- package/tools/vds-scripts/lsp_orchestrator/tests/test_type_checker_routing.py +0 -362
- package/tools/vds-scripts/lsp_orchestrator/tests/test_workspace_indexer.py +0 -144
- package/tools/vds-scripts/markdown_orchestrator/README.md +0 -72
- package/tools/vds-scripts/markdown_orchestrator/pyproject.toml +0 -39
- package/tools/vds-scripts/markdown_orchestrator/src/vds_markdown_orchestrator/__init__.py +0 -5
- package/tools/vds-scripts/markdown_orchestrator/src/vds_markdown_orchestrator/cli.py +0 -102
- package/tools/vds-scripts/multi_agent_orchestrator/Dockerfile +0 -65
- package/tools/vds-scripts/multi_agent_orchestrator/README.md +0 -306
- package/tools/vds-scripts/multi_agent_orchestrator/postman/README.md +0 -264
- package/tools/vds-scripts/multi_agent_orchestrator/postman/TEST_RESULTS_SUMMARY.md +0 -197
- package/tools/vds-scripts/multi_agent_orchestrator/postman/VDS-Multi-Agent-Orchestrator-API.postman_collection.json +0 -1010
- package/tools/vds-scripts/multi_agent_orchestrator/postman/environments/local-development.postman_environment.json +0 -55
- package/tools/vds-scripts/multi_agent_orchestrator/postman/test-results.json +0 -24146
- package/tools/vds-scripts/multi_agent_orchestrator/pyproject.toml +0 -63
- package/tools/vds-scripts/multi_agent_orchestrator/run_api.py +0 -9
- package/tools/vds-scripts/multi_agent_orchestrator/run_mock_api.py +0 -9
- package/tools/vds-scripts/multi_agent_orchestrator/simple_test.py +0 -53
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/__init__.py +0 -25
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/agent_pool.py +0 -433
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/api/__init__.py +0 -5
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/api/main.py +0 -722
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/api/mock_main.py +0 -812
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/change_log.py +0 -515
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/cli.py +0 -424
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/config.py +0 -220
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/conflict_resolver.py +0 -462
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/coordinator.py +0 -627
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/models.py +0 -389
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/progress_dashboard.py +0 -380
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/redis_client.py +0 -245
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/scheduler_subscriber.py +0 -272
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/task_manager.py +0 -536
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/task_tracking.py +0 -550
- package/tools/vds-scripts/multi_agent_orchestrator/src/vds_multi_agent_orchestrator/vds_ai_memory_client.py +0 -352
- package/tools/vds-scripts/multi_agent_orchestrator/test_complete_system.py +0 -149
- package/tools/vds-scripts/multi_agent_orchestrator/test_infrastructure_only.py +0 -194
- package/tools/vds-scripts/multi_agent_orchestrator/test_integration.py +0 -108
- package/tools/vds-scripts/multi_agent_orchestrator/tests/__init__.py +0 -1
- package/tools/vds-scripts/multi_agent_orchestrator/tests/test_agent_registration_credential_validator.py +0 -223
- package/tools/vds-scripts/multi_agent_orchestrator/tests/test_config.py +0 -210
- package/tools/vds-scripts/multi_agent_orchestrator/tests/test_models.py +0 -195
- package/tools/vds-scripts/multi_agent_orchestrator/tests/test_w9_agent_routes.py +0 -321
- package/tools/vds-scripts/openapi_orchestrator/README.md +0 -197
- package/tools/vds-scripts/openapi_orchestrator/pyproject.toml +0 -106
- package/tools/vds-scripts/openapi_orchestrator/src/vds_openapi_orchestrator/__init__.py +0 -29
- package/tools/vds-scripts/openapi_orchestrator/src/vds_openapi_orchestrator/cli.py +0 -345
- package/tools/vds-scripts/openapi_orchestrator/src/vds_openapi_orchestrator/full_validator.py +0 -183
- package/tools/vds-scripts/openapi_orchestrator/src/vds_openapi_orchestrator/spec_validator.py +0 -197
- package/tools/vds-scripts/openapi_orchestrator/tests/__init__.py +0 -1
- package/tools/vds-scripts/openapi_orchestrator/tests/test_cli.py +0 -234
- package/tools/vds-scripts/openapi_orchestrator/tests/test_full_validator.py +0 -203
- package/tools/vds-scripts/openapi_orchestrator/tests/test_spec_validator.py +0 -295
- package/tools/vds-scripts/pdf_orchestrator/.dockerignore +0 -93
- package/tools/vds-scripts/pdf_orchestrator/.env.example +0 -40
- package/tools/vds-scripts/pdf_orchestrator/.ruff_rules.py +0 -350
- package/tools/vds-scripts/pdf_orchestrator/.yamllint.yml +0 -43
- package/tools/vds-scripts/pdf_orchestrator/DEVELOPMENT_PLAN.md +0 -80
- package/tools/vds-scripts/pdf_orchestrator/Dockerfile +0 -87
- package/tools/vds-scripts/pdf_orchestrator/README.md +0 -608
- package/tools/vds-scripts/pdf_orchestrator/cli_verification_test/test.md +0 -6
- package/tools/vds-scripts/pdf_orchestrator/cli_verification_test/test.pdf +0 -0
- package/tools/vds-scripts/pdf_orchestrator/config/alertmanager.yml +0 -83
- package/tools/vds-scripts/pdf_orchestrator/config/prometheus.prod.yml +0 -98
- package/tools/vds-scripts/pdf_orchestrator/config/prometheus.yml +0 -40
- package/tools/vds-scripts/pdf_orchestrator/config/redis.conf +0 -78
- package/tools/vds-scripts/pdf_orchestrator/docs/COMPETITIVE_ANALYSIS_REPORT.md +0 -309
- package/tools/vds-scripts/pdf_orchestrator/docs/FEATURES_GUIDE.md +0 -518
- package/tools/vds-scripts/pdf_orchestrator/docs/MULTI_USER_DEPLOYMENT_GUIDE.md +0 -615
- package/tools/vds-scripts/pdf_orchestrator/docs/USER_GUIDE.md +0 -829
- package/tools/vds-scripts/pdf_orchestrator/pyproject.toml +0 -87
- package/tools/vds-scripts/pdf_orchestrator/pytest.ini +0 -71
- package/tools/vds-scripts/pdf_orchestrator/ruff.toml +0 -6
- package/tools/vds-scripts/pdf_orchestrator/scripts/debug_security_report.py +0 -59
- package/tools/vds-scripts/pdf_orchestrator/scripts/demo_library_selector.py +0 -109
- package/tools/vds-scripts/pdf_orchestrator/scripts/generate_project_stats.py +0 -52
- package/tools/vds-scripts/pdf_orchestrator/scripts/generate_styled_pdf.py +0 -95
- package/tools/vds-scripts/pdf_orchestrator/scripts/migrate_render_pdfs.py +0 -285
- package/tools/vds-scripts/pdf_orchestrator/scripts/setup_team.bat +0 -283
- package/tools/vds-scripts/pdf_orchestrator/scripts/setup_team.sh +0 -324
- package/tools/vds-scripts/pdf_orchestrator/src/vds_pdf_orchestrator/__init__.py +0 -5
- package/tools/vds-scripts/pdf_orchestrator/src/vds_pdf_orchestrator/cli.py +0 -542
- package/tools/vds-scripts/pdf_orchestrator/src/vds_pdf_orchestrator/config.py +0 -33
- package/tools/vds-scripts/pdf_orchestrator/tests/README.md +0 -650
- package/tools/vds-scripts/pdf_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/pdf_orchestrator/tests/conftest.py +0 -520
- package/tools/vds-scripts/pdf_orchestrator/tests/requirements.txt +0 -51
- package/tools/vds-scripts/pdf_orchestrator/tests/run_tests.py +0 -659
- package/tools/vds-scripts/pdf_orchestrator/tests/test_config.py +0 -36
- package/tools/vds-scripts/progress_report_orchestrator/Dockerfile +0 -77
- package/tools/vds-scripts/progress_report_orchestrator/README.md +0 -39
- package/tools/vds-scripts/progress_report_orchestrator/alembic/env.py +0 -42
- package/tools/vds-scripts/progress_report_orchestrator/alembic/script.py.mako +0 -28
- package/tools/vds-scripts/progress_report_orchestrator/alembic/versions/0001_initial_progress_schema.py +0 -180
- package/tools/vds-scripts/progress_report_orchestrator/alembic.ini +0 -67
- package/tools/vds-scripts/progress_report_orchestrator/pyproject.toml +0 -67
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/__init__.py +0 -3
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/analyzers/__init__.py +0 -1
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/analyzers/endpoint_scanner.py +0 -238
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/analyzers/git_activity.py +0 -159
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/analyzers/hexagonal.py +0 -100
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/analyzers/test_scanner.py +0 -136
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/cli.py +0 -743
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/config.py +0 -50
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/db/__init__.py +0 -12
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/db/alembic_filters.py +0 -64
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/memory.py +0 -82
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/models/__init__.py +0 -1
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/models/analysis.py +0 -84
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/models/report.py +0 -117
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/models/topology.py +0 -101
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/parsers/__init__.py +0 -1
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/parsers/kg_parser.py +0 -252
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/parsers/uc_reader.py +0 -159
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/pipeline/__init__.py +0 -1
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/pipeline/concurrency.py +0 -39
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/pipeline/llm_eval.py +0 -570
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/pipeline/report.py +0 -1256
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/pipeline/structural.py +0 -384
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/pipeline/sync.py +0 -143
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/recommendations/__init__.py +0 -5
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/recommendations/engine.py +0 -105
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/recommendations/templates.py +0 -236
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/scheduler_subscriber.py +0 -238
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/skills/README.md +0 -56
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/skills/__init__.py +0 -1
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/skills/srs-architecture-reviewer/SKILL.md +0 -67
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/skills/srs-endpoint-matcher/SKILL.md +0 -67
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/state/__init__.py +0 -1
- package/tools/vds-scripts/progress_report_orchestrator/src/progress_report_orchestrator/state/schema.py +0 -625
- package/tools/vds-scripts/progress_report_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/progress_report_orchestrator/tests/fixtures/__init__.py +0 -0
- package/tools/vds-scripts/progress_report_orchestrator/tests/fixtures/kg/.gitkeep +0 -0
- package/tools/vds-scripts/progress_report_orchestrator/tests/fixtures/kg/__init__.py +0 -0
- package/tools/vds-scripts/progress_report_orchestrator/tests/fixtures/kg/doc-dependencies.yaml +0 -79
- package/tools/vds-scripts/progress_report_orchestrator/tests/fixtures/kg/fr-to-docs.yaml +0 -478
- package/tools/vds-scripts/progress_report_orchestrator/tests/fixtures/kg/fr-to-services.yaml +0 -18
- package/tools/vds-scripts/progress_report_orchestrator/tests/fixtures/kg/registry.yaml +0 -346
- package/tools/vds-scripts/progress_report_orchestrator/tests/fixtures/phase3_baseline_standard.md +0 -564
- package/tools/vds-scripts/progress_report_orchestrator/tests/integration/__init__.py +0 -0
- package/tools/vds-scripts/progress_report_orchestrator/tests/integration/test_checkpoint.py +0 -276
- package/tools/vds-scripts/progress_report_orchestrator/tests/test_alembic_migrations.py +0 -265
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/__init__.py +0 -0
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_analyzers.py +0 -267
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_bounded_gather.py +0 -176
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_cli_phase_report.py +0 -119
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_delta.py +0 -169
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_error_handling.py +0 -150
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_gate_exit_codes.py +0 -230
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_git_activity.py +0 -215
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_kg_parser.py +0 -267
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_llm_autodetect.py +0 -183
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_llm_eval.py +0 -529
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_memory_integration.py +0 -151
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_migration_contract.py +0 -254
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_mode_rendering.py +0 -576
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_models.py +0 -251
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_progress_llm_config.py +0 -67
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_recommendations.py +0 -480
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_report_enhancements.py +0 -415
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_resume_reload.py +0 -343
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_trend_regression.py +0 -294
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_uc_reader.py +0 -169
- package/tools/vds-scripts/progress_report_orchestrator/tests/unit/test_valence_gap.py +0 -293
- package/tools/vds-scripts/project-cycle-report.json +0 -14
- package/tools/vds-scripts/project-dependency-graph.json +0 -11361
- package/tools/vds-scripts/project-topology.json +0 -99
- package/tools/vds-scripts/public_interface_boundary_orchestrator/pyproject.toml +0 -18
- package/tools/vds-scripts/public_interface_boundary_orchestrator/src/vds_public_interface_boundary_orchestrator/__init__.py +0 -0
- package/tools/vds-scripts/public_interface_boundary_orchestrator/src/vds_public_interface_boundary_orchestrator/cli.py +0 -232
- package/tools/vds-scripts/public_interface_boundary_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/public_interface_boundary_orchestrator/tests/test_cli.py +0 -108
- package/tools/vds-scripts/research_orchestrator/README.md +0 -68
- package/tools/vds-scripts/research_orchestrator/py.typed +0 -0
- package/tools/vds-scripts/research_orchestrator/pyproject.toml +0 -95
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/__init__.py +0 -3
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/_env.py +0 -11
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/cli.py +0 -335
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/config.py +0 -43
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/evidence/__init__.py +0 -0
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/evidence/models.py +0 -89
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/evidence/scoring.py +0 -102
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/exceptions.py +0 -78
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/http_client.py +0 -160
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/logging.py +0 -49
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/output/__init__.py +0 -0
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/output/formatters.py +0 -93
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/py.typed +0 -1
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/report/__init__.py +0 -0
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/report/build.py +0 -156
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/report/format.py +0 -147
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/tools/__init__.py +0 -0
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/tools/health.py +0 -66
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/tools/health_graph.py +0 -52
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/tools/registry.py +0 -127
- package/tools/vds-scripts/research_orchestrator/src/vds_research_orchestrator/tools/search.py +0 -230
- package/tools/vds-scripts/research_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/research_orchestrator/tests/conftest.py +0 -53
- package/tools/vds-scripts/research_orchestrator/tests/test_cli.py +0 -222
- package/tools/vds-scripts/research_orchestrator/tests/test_config.py +0 -23
- package/tools/vds-scripts/research_orchestrator/tests/test_exceptions.py +0 -62
- package/tools/vds-scripts/research_orchestrator/tests/test_formatters.py +0 -89
- package/tools/vds-scripts/research_orchestrator/tests/test_graph_integration.py +0 -149
- package/tools/vds-scripts/research_orchestrator/tests/test_http_client.py +0 -134
- package/tools/vds-scripts/research_orchestrator/tests/test_report_build.py +0 -128
- package/tools/vds-scripts/research_orchestrator/tests/test_report_format.py +0 -91
- package/tools/vds-scripts/research_orchestrator/tests/test_scoring.py +0 -95
- package/tools/vds-scripts/research_orchestrator/tests/vds_research_orchestrator/test_tools/__init__.py +0 -1
- package/tools/vds-scripts/research_orchestrator/tests/vds_research_orchestrator/test_tools/test_health.py +0 -139
- package/tools/vds-scripts/research_orchestrator/tests/vds_research_orchestrator/test_tools/test_registry.py +0 -135
- package/tools/vds-scripts/research_orchestrator/tests/vds_research_orchestrator/test_tools/test_search.py +0 -238
- package/tools/vds-scripts/run-history.json +0 -26
- package/tools/vds-scripts/schema_converter/README.md +0 -109
- package/tools/vds-scripts/schema_converter/pyproject.toml +0 -37
- package/tools/vds-scripts/schema_converter/src/vds_schema_converter/__init__.py +0 -3
- package/tools/vds-scripts/schema_converter/src/vds_schema_converter/cli.py +0 -50
- package/tools/vds-scripts/schema_converter/tests/__init__.py +0 -0
- package/tools/vds-scripts/schema_converter/tests/test_json_schema_generator.py +0 -115
- package/tools/vds-scripts/schema_converter/tests/test_mermaid_generator.py +0 -112
- package/tools/vds-scripts/schema_converter/tests/test_parser.py +0 -111
- package/tools/vds-scripts/schema_converter/tests/test_plantuml_generator.py +0 -112
- package/tools/vds-scripts/schema_converter/tests/test_plantuml_validator.py +0 -69
- package/tools/vds-scripts/schema_converter/tests/test_prisma_generator.py +0 -113
- package/tools/vds-scripts/schema_converter/tests/test_sql_generator.py +0 -138
- package/tools/vds-scripts/schema_converter/tests/test_typeorm_generator.py +0 -110
- package/tools/vds-scripts/schema_converter/tests/test_validators.py +0 -96
- package/tools/vds-scripts/spec_orchestrator/README.md +0 -13
- package/tools/vds-scripts/spec_orchestrator/pyproject.toml +0 -40
- package/tools/vds-scripts/spec_orchestrator/src/vds_spec_orchestrator/__init__.py +0 -5
- package/tools/vds-scripts/spec_orchestrator/src/vds_spec_orchestrator/cli.py +0 -162
- package/tools/vds-scripts/spec_orchestrator/src/vds_spec_orchestrator/core.py +0 -575
- package/tools/vds-scripts/spec_orchestrator/src/vds_spec_orchestrator/sync.py +0 -306
- package/tools/vds-scripts/spec_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/spec_orchestrator/tests/test_frontmatter_drift.py +0 -243
- package/tools/vds-scripts/spec_orchestrator/tests/test_sync.py +0 -342
- package/tools/vds-scripts/structure_orchestrator/README.md +0 -60
- package/tools/vds-scripts/structure_orchestrator/pyproject.toml +0 -103
- package/tools/vds-scripts/structure_orchestrator/src/vds_structure_orchestrator/__init__.py +0 -13
- package/tools/vds-scripts/structure_orchestrator/src/vds_structure_orchestrator/cli.py +0 -308
- package/tools/vds-scripts/structure_orchestrator/src/vds_structure_orchestrator/validator.py +0 -257
- package/tools/vds-scripts/structure_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/structure_orchestrator/tests/test_cli.py +0 -161
- package/tools/vds-scripts/structure_orchestrator/tests/test_helpers.py +0 -115
- package/tools/vds-scripts/structure_orchestrator/tests/test_validator.py +0 -104
- package/tools/vds-scripts/task_orchestrator/README.md +0 -50
- package/tools/vds-scripts/task_orchestrator/__init__.py +0 -18
- package/tools/vds-scripts/task_orchestrator/pyproject.toml +0 -43
- package/tools/vds-scripts/task_orchestrator/scripts/run_excel_sync.py +0 -36
- package/tools/vds-scripts/task_orchestrator/src/vds_task_orchestrator/__init__.py +0 -13
- package/tools/vds-scripts/task_orchestrator/src/vds_task_orchestrator/audit.py +0 -134
- package/tools/vds-scripts/task_orchestrator/src/vds_task_orchestrator/cli.py +0 -127
- package/tools/vds-scripts/task_orchestrator/src/vds_task_orchestrator/debug.py +0 -133
- package/tools/vds-scripts/task_orchestrator/src/vds_task_orchestrator/normalize.py +0 -113
- package/tools/vds-scripts/task_orchestrator/src/vds_task_orchestrator/refine.py +0 -201
- package/tools/vds-scripts/task_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/task_orchestrator/tests/test_task_orchestrator.py +0 -84
- package/tools/vds-scripts/temp_query_projects.py +0 -2
- package/tools/vds-scripts/test_small.md +0 -1
- package/tools/vds-scripts/text_utils_orchestrator/pyproject.toml +0 -20
- package/tools/vds-scripts/text_utils_orchestrator/src/vds_text_utils/__init__.py +0 -7
- package/tools/vds-scripts/text_utils_orchestrator/src/vds_text_utils/i18n.py +0 -143
- package/tools/vds-scripts/text_utils_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/text_utils_orchestrator/tests/test_i18n.py +0 -53
- package/tools/vds-scripts/upgrade_major.py +0 -61
- package/tools/vds-scripts/upgrade_major_v2.py +0 -64
- package/tools/vds-scripts/verify_violations.py +0 -57
- package/tools/vds-scripts/workflow-summary.json +0 -325
- package/tools/vds-scripts/workflow-summary.md +0 -8
|
@@ -1,4689 +0,0 @@
|
|
|
1
|
-
"""Miscellaneous commands for vds-audit CLI (Phase 91 decomposition)."""
|
|
2
|
-
|
|
3
|
-
# pyright: reportUnusedVariable=false
|
|
4
|
-
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
|
-
import asyncio
|
|
8
|
-
import dataclasses
|
|
9
|
-
import json
|
|
10
|
-
import os
|
|
11
|
-
from dataclasses import dataclass, field
|
|
12
|
-
from datetime import UTC, datetime
|
|
13
|
-
from fnmatch import fnmatch
|
|
14
|
-
from importlib import metadata
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from time import perf_counter
|
|
17
|
-
from typing import Any, cast
|
|
18
|
-
from uuid import uuid4
|
|
19
|
-
|
|
20
|
-
import structlog
|
|
21
|
-
import typer
|
|
22
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
23
|
-
from rich.table import Table
|
|
24
|
-
from rich.tree import Tree
|
|
25
|
-
from vds_platform_core.credentials import resolve_secret
|
|
26
|
-
|
|
27
|
-
from vds_audit_orchestrator.checklist_query import (
|
|
28
|
-
ChecklistQueryFilters,
|
|
29
|
-
query_checklist_template,
|
|
30
|
-
)
|
|
31
|
-
from vds_audit_orchestrator.checks.registry import CheckRegistry
|
|
32
|
-
from vds_audit_orchestrator.cli_common import _STATE_DSN_ENV_VAR, APP_NAME, _cli_state, _get_cli_ctx, app, console
|
|
33
|
-
from vds_audit_orchestrator.cli_impl import (
|
|
34
|
-
_DEFAULT_EMBEDDING_BASE_URL,
|
|
35
|
-
_active_runtime_profile_name,
|
|
36
|
-
_DebugArtifactsCollector,
|
|
37
|
-
_emit_json,
|
|
38
|
-
_emit_sync_decision,
|
|
39
|
-
_load_manifest_payload_from_state,
|
|
40
|
-
_normalize_run_parameters,
|
|
41
|
-
_normalize_storage_key,
|
|
42
|
-
_parse_confluence_ref,
|
|
43
|
-
_parse_registry_metadata,
|
|
44
|
-
_persist_typed_run_terminal,
|
|
45
|
-
_project_storage_key_from_payload,
|
|
46
|
-
_require_consumer_state_dsn,
|
|
47
|
-
_require_producer_state_dsn,
|
|
48
|
-
_resolve_confluence_page_id,
|
|
49
|
-
_resolve_parse_manifest_payload,
|
|
50
|
-
_resolve_state_dsn,
|
|
51
|
-
_validate_required_credentials,
|
|
52
|
-
_write_debug_bundle,
|
|
53
|
-
)
|
|
54
|
-
from vds_audit_orchestrator.clients.confluence_cli_client import ConfluenceCliClient
|
|
55
|
-
from vds_audit_orchestrator.collectors.checklist_parser import ChecklistParser
|
|
56
|
-
from vds_audit_orchestrator.collectors.orchestrator import EvidenceOrchestrator
|
|
57
|
-
from vds_audit_orchestrator.config import FeatureFlag, get_config
|
|
58
|
-
from vds_audit_orchestrator.engine.auditor import AuditEngine
|
|
59
|
-
from vds_audit_orchestrator.engine.checkpoint import (
|
|
60
|
-
AuditCheckpoint,
|
|
61
|
-
load_checkpoint,
|
|
62
|
-
save_checkpoint,
|
|
63
|
-
)
|
|
64
|
-
from vds_audit_orchestrator.engine.gap_analyzer import GapAnalyzer
|
|
65
|
-
from vds_audit_orchestrator.engine.loader import (
|
|
66
|
-
ExcelLoader,
|
|
67
|
-
export_template_from_google_source,
|
|
68
|
-
load_template_from_google_sheet,
|
|
69
|
-
resolve_mapping_path,
|
|
70
|
-
)
|
|
71
|
-
from vds_audit_orchestrator.engine.mapping import load_mapping_file
|
|
72
|
-
from vds_audit_orchestrator.engine.scorer import Scorer
|
|
73
|
-
from vds_audit_orchestrator.engine.section_packs import get_default_registry
|
|
74
|
-
from vds_audit_orchestrator.engine.template_analyzer import TemplateAnalyzer
|
|
75
|
-
from vds_audit_orchestrator.engine.validator import TemplateValidator
|
|
76
|
-
from vds_audit_orchestrator.engine.weight_policy import compute_adaptive_weights
|
|
77
|
-
from vds_audit_orchestrator.errors import TemplateValidationError
|
|
78
|
-
from vds_audit_orchestrator.evidence.bootstrap import resolve_checklist_profile
|
|
79
|
-
from vds_audit_orchestrator.llm.cost_tracker import global_tracker
|
|
80
|
-
from vds_audit_orchestrator.llm.provider import LLMProvider
|
|
81
|
-
from vds_audit_orchestrator.models.checklist import AuditChecklist
|
|
82
|
-
from vds_audit_orchestrator.models.enums import MaturityLevel
|
|
83
|
-
from vds_audit_orchestrator.models.evidence import Evidence, EvidenceRequirement
|
|
84
|
-
from vds_audit_orchestrator.models.gaps import GapAnalysisResult as RepoGapAnalysisResult
|
|
85
|
-
from vds_audit_orchestrator.models.reporting import AuditRunReport
|
|
86
|
-
from vds_audit_orchestrator.profiles.detection import resolve_profile, resolve_profile_async
|
|
87
|
-
from vds_audit_orchestrator.profiles.models import ProjectProfile, default_profile
|
|
88
|
-
from vds_audit_orchestrator.reports.reporting import build_metadata, generate_reports
|
|
89
|
-
from vds_audit_orchestrator.seed import select_project
|
|
90
|
-
from vds_audit_orchestrator.spec_sync_validator import validate_spec_sync
|
|
91
|
-
from vds_audit_orchestrator.utils.debug_bundle import export_debug_bundle as export_debug_bundle_func
|
|
92
|
-
|
|
93
|
-
logger = structlog.get_logger(__name__)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def _log_hardened_path_error(
|
|
97
|
-
event: str,
|
|
98
|
-
exc: Exception,
|
|
99
|
-
*,
|
|
100
|
-
context: str,
|
|
101
|
-
recovery_action: str,
|
|
102
|
-
level: str = "warning",
|
|
103
|
-
) -> None:
|
|
104
|
-
log_method = logger.exception if level == "exception" else logger.warning
|
|
105
|
-
log_method(
|
|
106
|
-
event,
|
|
107
|
-
error=str(exc),
|
|
108
|
-
error_type=type(exc).__name__,
|
|
109
|
-
source_module=__name__,
|
|
110
|
-
context=context,
|
|
111
|
-
recovery_action=recovery_action,
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# ---------------------------------------------------------------------------
|
|
116
|
-
# embedding-check
|
|
117
|
-
# ---------------------------------------------------------------------------
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
@app.command(name="embedding-check")
|
|
121
|
-
def embedding_check(
|
|
122
|
-
state_dsn: str | None = typer.Option(
|
|
123
|
-
None,
|
|
124
|
-
"--state-dsn",
|
|
125
|
-
envvar=_STATE_DSN_ENV_VAR,
|
|
126
|
-
help=f"State backend DSN for pgvector readiness check (fallback: {_STATE_DSN_ENV_VAR}).",
|
|
127
|
-
),
|
|
128
|
-
embedding_base_url: str | None = typer.Option(
|
|
129
|
-
None,
|
|
130
|
-
"--embedding-base-url",
|
|
131
|
-
envvar="VDS_AUDIT_EMBEDDING__BASE_URL",
|
|
132
|
-
help="Embedding API base URL (default: config.embedding.base_url, fallback: config.llm.base_url, then http://127.0.0.1:11434).",
|
|
133
|
-
),
|
|
134
|
-
model: str = typer.Option(
|
|
135
|
-
"bge-m3",
|
|
136
|
-
"--model",
|
|
137
|
-
help="Primary embedding model to verify (default: bge-m3).",
|
|
138
|
-
),
|
|
139
|
-
allow_fallback: bool = typer.Option(
|
|
140
|
-
False,
|
|
141
|
-
"--allow-fallback/--no-allow-fallback",
|
|
142
|
-
help="When primary model is bge-m3 and unavailable, allow fallback to nomic-embed-text.",
|
|
143
|
-
),
|
|
144
|
-
timeout_seconds: float = typer.Option(
|
|
145
|
-
15.0,
|
|
146
|
-
"--timeout-seconds",
|
|
147
|
-
min=1.0,
|
|
148
|
-
help="Timeout for Ollama probes in seconds.",
|
|
149
|
-
),
|
|
150
|
-
) -> None:
|
|
151
|
-
"""Validate embedding + pgvector readiness (Python package `pgvector` is separate from Postgres extension `vector`)."""
|
|
152
|
-
# Resolve probe helpers via module lookup so test monkeypatches applied to
|
|
153
|
-
# `vds_audit_orchestrator.cli` are honored by this command path.
|
|
154
|
-
from vds_audit_orchestrator import cli as cli_module
|
|
155
|
-
|
|
156
|
-
resolved_state_dsn = _resolve_state_dsn(state_dsn, required=True)
|
|
157
|
-
assert resolved_state_dsn is not None
|
|
158
|
-
config = get_config()
|
|
159
|
-
resolved_embedding_base_url = (
|
|
160
|
-
embedding_base_url or config.embedding.base_url or config.llm.base_url or _DEFAULT_EMBEDDING_BASE_URL
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
errors: list[str] = []
|
|
164
|
-
checks = {
|
|
165
|
-
"ollama_reachable": False,
|
|
166
|
-
"model_available": False,
|
|
167
|
-
"embedding_dimensions_ok": False,
|
|
168
|
-
"pgvector_ready": False,
|
|
169
|
-
}
|
|
170
|
-
resolved_model: str | None = None
|
|
171
|
-
local_models: list[str] = []
|
|
172
|
-
probe: dict[str, Any] | None = None
|
|
173
|
-
pgvector: dict[str, Any] | None = None
|
|
174
|
-
fallback_used = False
|
|
175
|
-
|
|
176
|
-
try:
|
|
177
|
-
local_models = cli_module._fetch_ollama_local_models(
|
|
178
|
-
base_url=resolved_embedding_base_url,
|
|
179
|
-
timeout_seconds=timeout_seconds,
|
|
180
|
-
)
|
|
181
|
-
checks["ollama_reachable"] = True
|
|
182
|
-
except Exception as exc:
|
|
183
|
-
errors.append(f"Embedding endpoint reachability check failed at {resolved_embedding_base_url}: {exc}")
|
|
184
|
-
|
|
185
|
-
if checks["ollama_reachable"]:
|
|
186
|
-
try:
|
|
187
|
-
selection = cli_module._select_embedding_model_for_readiness(
|
|
188
|
-
requested_model=model,
|
|
189
|
-
local_models=local_models,
|
|
190
|
-
allow_fallback=allow_fallback,
|
|
191
|
-
)
|
|
192
|
-
resolved_model = str(selection["resolved_model"])
|
|
193
|
-
fallback_used = bool(selection["fallback_used"])
|
|
194
|
-
checks["model_available"] = True
|
|
195
|
-
probe = cli_module._probe_embedding_dimensions(
|
|
196
|
-
base_url=resolved_embedding_base_url,
|
|
197
|
-
model=resolved_model,
|
|
198
|
-
timeout_seconds=timeout_seconds,
|
|
199
|
-
expected_dimensions=cast("int | None", selection.get("expected_dimensions")),
|
|
200
|
-
api_key=resolve_secret(config.embedding.api_key),
|
|
201
|
-
)
|
|
202
|
-
checks["embedding_dimensions_ok"] = True
|
|
203
|
-
except Exception as exc:
|
|
204
|
-
errors.append(f"Embedding model check failed: {exc}")
|
|
205
|
-
|
|
206
|
-
try:
|
|
207
|
-
pgvector = cli_module._collect_pgvector_readiness(state_dsn=resolved_state_dsn)
|
|
208
|
-
checks["pgvector_ready"] = bool(pgvector.get("ready"))
|
|
209
|
-
if not checks["pgvector_ready"]:
|
|
210
|
-
errors.extend([str(item) for item in cast("list[Any]", pgvector.get("errors") or []) if str(item).strip()])
|
|
211
|
-
except Exception as exc:
|
|
212
|
-
errors.append(f"pgvector readiness check failed: {exc}")
|
|
213
|
-
|
|
214
|
-
status = "ok" if all(checks.values()) and not errors else "error"
|
|
215
|
-
payload = {
|
|
216
|
-
"status": status,
|
|
217
|
-
"operation": "embedding_check",
|
|
218
|
-
"requested_model": model,
|
|
219
|
-
"resolved_model": resolved_model,
|
|
220
|
-
"fallback_used": fallback_used,
|
|
221
|
-
"embedding_base_url": resolved_embedding_base_url,
|
|
222
|
-
"checks": checks,
|
|
223
|
-
"local_models": local_models,
|
|
224
|
-
"probe": probe,
|
|
225
|
-
"pgvector": pgvector,
|
|
226
|
-
"errors": errors,
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if _cli_state.get("json_only"):
|
|
230
|
-
_emit_json(payload)
|
|
231
|
-
else:
|
|
232
|
-
table = Table(title="Embedding Readiness")
|
|
233
|
-
table.add_column("Check")
|
|
234
|
-
table.add_column("Status")
|
|
235
|
-
table.add_row("Ollama reachable", "yes" if checks["ollama_reachable"] else "no")
|
|
236
|
-
table.add_row("Model available", "yes" if checks["model_available"] else "no")
|
|
237
|
-
table.add_row("Embedding dimensions", "yes" if checks["embedding_dimensions_ok"] else "no")
|
|
238
|
-
table.add_row("pgvector readiness", "yes" if checks["pgvector_ready"] else "no")
|
|
239
|
-
table.add_row("Requested model", model)
|
|
240
|
-
table.add_row("Resolved model", resolved_model or "-")
|
|
241
|
-
table.add_row("Fallback used", "yes" if fallback_used else "no")
|
|
242
|
-
table.add_row("Embedding base URL", resolved_embedding_base_url)
|
|
243
|
-
console.print(table)
|
|
244
|
-
if errors:
|
|
245
|
-
for err in errors:
|
|
246
|
-
console.print(f"[red]- {err}[/red]")
|
|
247
|
-
|
|
248
|
-
if status != "ok":
|
|
249
|
-
raise typer.Exit(1)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
# ---------------------------------------------------------------------------
|
|
253
|
-
# task-graph
|
|
254
|
-
# ---------------------------------------------------------------------------
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
@app.command(name="task-graph")
|
|
258
|
-
def task_graph(
|
|
259
|
-
thread_id: str = typer.Option(..., help="Thread ID to identify the workflow"),
|
|
260
|
-
output: Path = typer.Option(
|
|
261
|
-
Path("task_graph.mmd"),
|
|
262
|
-
"--output",
|
|
263
|
-
"-o",
|
|
264
|
-
help="Output path for the Mermaid file",
|
|
265
|
-
),
|
|
266
|
-
checkpointer: str = typer.Option(
|
|
267
|
-
"postgres",
|
|
268
|
-
help="Checkpointer type: memory or postgres",
|
|
269
|
-
),
|
|
270
|
-
postgres_dsn: str | None = typer.Option(
|
|
271
|
-
None,
|
|
272
|
-
help="Postgres DSN for checkpointing (required if checkpointer=postgres)",
|
|
273
|
-
envvar="VDS_AUDIT_LANGGRAPH__POSTGRES_DSN",
|
|
274
|
-
),
|
|
275
|
-
) -> None:
|
|
276
|
-
"""Generate a Mermaid diagram of the task graph for Phase 11 visualization.
|
|
277
|
-
|
|
278
|
-
Fetches the current workflow state from the checkpoint and generates a
|
|
279
|
-
Mermaid diagram showing all tasks with their status, dependencies, and
|
|
280
|
-
assignees.
|
|
281
|
-
|
|
282
|
-
Task nodes are colored by status:
|
|
283
|
-
- Green: completed
|
|
284
|
-
- Yellow: running
|
|
285
|
-
- Red: failed
|
|
286
|
-
- Gray: pending
|
|
287
|
-
- Orange: blocked
|
|
288
|
-
- Light gray: skipped
|
|
289
|
-
|
|
290
|
-
Examples:
|
|
291
|
-
# Generate task graph for a specific workflow
|
|
292
|
-
vds-audit task-graph --thread-id audit-123
|
|
293
|
-
|
|
294
|
-
# Specify custom output path
|
|
295
|
-
vds-audit task-graph --thread-id audit-123 --output my_tasks.mmd
|
|
296
|
-
|
|
297
|
-
# Use postgres checkpointer
|
|
298
|
-
vds-audit task-graph --thread-id audit-123 --checkpointer postgres
|
|
299
|
-
"""
|
|
300
|
-
from vds_audit_orchestrator.agents.langgraph_workflow import build_audit_graph
|
|
301
|
-
from vds_audit_orchestrator.models.task import AuditTask
|
|
302
|
-
|
|
303
|
-
postgres_dsn = postgres_dsn or get_config().langgraph.postgres_dsn
|
|
304
|
-
|
|
305
|
-
async def _generate_task_graph() -> str:
|
|
306
|
-
"""Fetch state and generate Mermaid diagram."""
|
|
307
|
-
graph = await build_audit_graph(checkpointer=checkpointer, postgres_dsn=postgres_dsn)
|
|
308
|
-
config = {"configurable": {"thread_id": thread_id}}
|
|
309
|
-
state = await graph.aget_state(config) # type: ignore[arg-type]
|
|
310
|
-
|
|
311
|
-
if not state or not state.values:
|
|
312
|
-
raise ValueError(f"No workflow state found for thread_id: {thread_id}")
|
|
313
|
-
|
|
314
|
-
tasks_dict: dict[str, dict[str, Any]] = state.values.get("tasks", {})
|
|
315
|
-
if not tasks_dict:
|
|
316
|
-
raise ValueError(f"No tasks found in workflow state for thread_id: {thread_id}")
|
|
317
|
-
|
|
318
|
-
# Build Mermaid diagram
|
|
319
|
-
lines: list[str] = ["graph TD"]
|
|
320
|
-
|
|
321
|
-
# Track dependencies for edges
|
|
322
|
-
edges: list[tuple[str, str]] = []
|
|
323
|
-
|
|
324
|
-
for task_id, task_data in tasks_dict.items():
|
|
325
|
-
# Parse task data - it may be a dict or AuditTask
|
|
326
|
-
task = AuditTask.model_validate(task_data) if isinstance(task_data, dict) else task_data
|
|
327
|
-
|
|
328
|
-
# Determine status class
|
|
329
|
-
status_str = task.status if isinstance(task.status, str) else task.status.value
|
|
330
|
-
status_class = status_str.lower()
|
|
331
|
-
|
|
332
|
-
# Escape task type and assignee for Mermaid label
|
|
333
|
-
task_type = task.type.replace('"', '\\"')
|
|
334
|
-
assignee = task.assignee.replace('"', '\\"')
|
|
335
|
-
|
|
336
|
-
# Create node with label showing type and assignee
|
|
337
|
-
node_label = f"{task_type}<br/>{assignee}"
|
|
338
|
-
lines.append(f' {task_id}["{node_label}"]:::{status_class}')
|
|
339
|
-
|
|
340
|
-
# Collect dependency edges
|
|
341
|
-
for dep_id in task.dependencies:
|
|
342
|
-
edges.append((dep_id, task_id))
|
|
343
|
-
|
|
344
|
-
# Add edges
|
|
345
|
-
for source, target in edges:
|
|
346
|
-
lines.append(f" {source} --> {target}")
|
|
347
|
-
|
|
348
|
-
# Add class definitions for status coloring
|
|
349
|
-
lines.append(" classDef completed fill:#90EE90")
|
|
350
|
-
lines.append(" classDef running fill:#FFD700")
|
|
351
|
-
lines.append(" classDef failed fill:#FF6B6B")
|
|
352
|
-
lines.append(" classDef pending fill:#D3D3D3")
|
|
353
|
-
lines.append(" classDef blocked fill:#FFA500")
|
|
354
|
-
lines.append(" classDef skipped fill:#E8E8E8")
|
|
355
|
-
|
|
356
|
-
return "\n".join(lines)
|
|
357
|
-
|
|
358
|
-
try:
|
|
359
|
-
mermaid_content = asyncio.run(_generate_task_graph())
|
|
360
|
-
|
|
361
|
-
# Write to output file
|
|
362
|
-
output.parent.mkdir(parents=True, exist_ok=True)
|
|
363
|
-
output.write_text(mermaid_content, encoding="utf-8")
|
|
364
|
-
|
|
365
|
-
if _cli_state.get("json_only"):
|
|
366
|
-
_emit_json(
|
|
367
|
-
{
|
|
368
|
-
"task_graph": "generated",
|
|
369
|
-
"path": str(output.resolve()),
|
|
370
|
-
"thread_id": thread_id,
|
|
371
|
-
}
|
|
372
|
-
)
|
|
373
|
-
return
|
|
374
|
-
|
|
375
|
-
console.print(f"[green]Task graph written to:[/green] {output.resolve()}")
|
|
376
|
-
|
|
377
|
-
except ValueError as e:
|
|
378
|
-
message = str(e)
|
|
379
|
-
if _cli_state.get("json_only"):
|
|
380
|
-
_emit_json({"error": message})
|
|
381
|
-
else:
|
|
382
|
-
console.print(f"[red]{message}[/red]")
|
|
383
|
-
raise typer.Exit(1) from e
|
|
384
|
-
except Exception as e:
|
|
385
|
-
message = f"Task graph generation failed: {e}"
|
|
386
|
-
if _cli_state.get("json_only"):
|
|
387
|
-
_emit_json({"error": message})
|
|
388
|
-
else:
|
|
389
|
-
console.print(f"[red]{message}[/red]")
|
|
390
|
-
raise typer.Exit(1) from e
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
# ---------------------------------------------------------------------------
|
|
394
|
-
# list-pending
|
|
395
|
-
# ---------------------------------------------------------------------------
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
@app.command(name="list-pending")
|
|
399
|
-
def list_pending_approvals(
|
|
400
|
-
checkpointer: str = typer.Option(
|
|
401
|
-
"postgres",
|
|
402
|
-
help="Checkpointer type: memory or postgres",
|
|
403
|
-
),
|
|
404
|
-
postgres_dsn: str | None = typer.Option(
|
|
405
|
-
None,
|
|
406
|
-
help="Postgres DSN for checkpointing (required if checkpointer=postgres)",
|
|
407
|
-
envvar="VDS_AUDIT_LANGGRAPH__POSTGRES_DSN",
|
|
408
|
-
),
|
|
409
|
-
) -> None:
|
|
410
|
-
"""List all pending audit approvals awaiting human review.
|
|
411
|
-
|
|
412
|
-
Queries the checkpoint store for workflows paused at the human_review node.
|
|
413
|
-
"""
|
|
414
|
-
from vds_audit_orchestrator.agents.approval import list_pending_approvals_cli
|
|
415
|
-
|
|
416
|
-
postgres_dsn = postgres_dsn or get_config().langgraph.postgres_dsn
|
|
417
|
-
|
|
418
|
-
pending = asyncio.run(
|
|
419
|
-
list_pending_approvals_cli(
|
|
420
|
-
checkpointer=checkpointer,
|
|
421
|
-
postgres_dsn=postgres_dsn,
|
|
422
|
-
)
|
|
423
|
-
)
|
|
424
|
-
|
|
425
|
-
if _cli_state.get("json_only"):
|
|
426
|
-
_emit_json({"pending_approvals": pending, "count": len(pending)})
|
|
427
|
-
return
|
|
428
|
-
|
|
429
|
-
if not pending:
|
|
430
|
-
console.print("[green]No pending approvals found.[/green]")
|
|
431
|
-
return
|
|
432
|
-
|
|
433
|
-
console.print(f"[bold]Pending Approvals ({len(pending)}):[/bold]\n")
|
|
434
|
-
|
|
435
|
-
table = Table()
|
|
436
|
-
table.add_column("Thread ID")
|
|
437
|
-
table.add_column("Repository")
|
|
438
|
-
table.add_column("Timestamp")
|
|
439
|
-
table.add_column("Findings")
|
|
440
|
-
table.add_column("Errors")
|
|
441
|
-
|
|
442
|
-
for item in pending:
|
|
443
|
-
table.add_row(
|
|
444
|
-
item["thread_id"],
|
|
445
|
-
item["repository"],
|
|
446
|
-
item["timestamp"],
|
|
447
|
-
str(item["agent_results_count"]),
|
|
448
|
-
str(item["errors_count"]),
|
|
449
|
-
)
|
|
450
|
-
|
|
451
|
-
console.print(table)
|
|
452
|
-
console.print("\n[dim]Use 'vds-audit approve <thread-id>' to approve or reject.[/dim]")
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
# ---------------------------------------------------------------------------
|
|
456
|
-
# approve
|
|
457
|
-
# ---------------------------------------------------------------------------
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
@app.command(name="approve")
|
|
461
|
-
def approve_audit(
|
|
462
|
-
thread_id: str = typer.Argument(..., help="Thread ID of the audit to approve"),
|
|
463
|
-
approved: bool = typer.Option(True, "--approve/--reject", help="Approval decision"),
|
|
464
|
-
feedback: str | None = typer.Option(None, "--feedback", "-f", help="Reviewer feedback"),
|
|
465
|
-
reviewer: str | None = typer.Option(None, "--reviewer", "-r", help="Reviewer identifier"),
|
|
466
|
-
checkpointer: str = typer.Option(
|
|
467
|
-
"postgres",
|
|
468
|
-
help="Checkpointer type: memory or postgres",
|
|
469
|
-
),
|
|
470
|
-
postgres_dsn: str | None = typer.Option(
|
|
471
|
-
None,
|
|
472
|
-
help="Postgres DSN for checkpointing (required if checkpointer=postgres)",
|
|
473
|
-
envvar="VDS_AUDIT_LANGGRAPH__POSTGRES_DSN",
|
|
474
|
-
),
|
|
475
|
-
) -> None:
|
|
476
|
-
"""Approve or reject a pending audit review.
|
|
477
|
-
|
|
478
|
-
Resumes the LangGraph workflow with the human decision and logs
|
|
479
|
-
the approval to the audit trail.
|
|
480
|
-
|
|
481
|
-
Example:
|
|
482
|
-
vds-audit approve audit-123 --approve --feedback "Looks good, proceed."
|
|
483
|
-
vds-audit approve audit-123 --reject --reviewer "john.doe"
|
|
484
|
-
"""
|
|
485
|
-
from vds_audit_orchestrator.agents.approval import process_approval_cli
|
|
486
|
-
|
|
487
|
-
postgres_dsn = postgres_dsn or get_config().langgraph.postgres_dsn
|
|
488
|
-
|
|
489
|
-
result = asyncio.run(
|
|
490
|
-
process_approval_cli(
|
|
491
|
-
thread_id=thread_id,
|
|
492
|
-
approved=approved,
|
|
493
|
-
feedback=feedback,
|
|
494
|
-
reviewer=reviewer,
|
|
495
|
-
checkpointer=checkpointer,
|
|
496
|
-
postgres_dsn=postgres_dsn,
|
|
497
|
-
)
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
if _cli_state.get("json_only"):
|
|
501
|
-
_emit_json({"approval": "processed" if approved else "rejected", "result": result})
|
|
502
|
-
return
|
|
503
|
-
|
|
504
|
-
action = "approved" if approved else "rejected"
|
|
505
|
-
console.print(f"[green]Audit {action} successfully.[/green]")
|
|
506
|
-
console.print(f"Thread ID: {thread_id}")
|
|
507
|
-
console.print(f"Final step: {result.get('step')}")
|
|
508
|
-
|
|
509
|
-
synthesis = result.get("synthesis")
|
|
510
|
-
if synthesis:
|
|
511
|
-
console.print(f"Total findings: {synthesis.get('total_findings', 0)}")
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
# ---------------------------------------------------------------------------
|
|
515
|
-
# list-materials
|
|
516
|
-
# ---------------------------------------------------------------------------
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
@app.command(name="list-materials")
|
|
520
|
-
def list_materials(
|
|
521
|
-
evidence_dir: Path | None = typer.Option(
|
|
522
|
-
None,
|
|
523
|
-
"--evidence-dir",
|
|
524
|
-
help="Optional evidence directory used for filesystem tree rendering and project scope filtering.",
|
|
525
|
-
),
|
|
526
|
-
tree: bool = typer.Option(
|
|
527
|
-
False,
|
|
528
|
-
"--tree",
|
|
529
|
-
help="Render a filesystem tree under --evidence-dir.",
|
|
530
|
-
),
|
|
531
|
-
tree_depth: int = typer.Option(
|
|
532
|
-
3,
|
|
533
|
-
"--tree-depth",
|
|
534
|
-
min=1,
|
|
535
|
-
help="Maximum tree depth when --tree is enabled.",
|
|
536
|
-
),
|
|
537
|
-
timestamps: bool = typer.Option(
|
|
538
|
-
False,
|
|
539
|
-
"--timestamps",
|
|
540
|
-
help="Include modified timestamps in tree output.",
|
|
541
|
-
),
|
|
542
|
-
state_dsn: str | None = typer.Option(
|
|
543
|
-
None,
|
|
544
|
-
"--state-dsn",
|
|
545
|
-
envvar=_STATE_DSN_ENV_VAR,
|
|
546
|
-
help=f"Load materials manifest from centralized state DSN (fallback: {_STATE_DSN_ENV_VAR}).",
|
|
547
|
-
),
|
|
548
|
-
):
|
|
549
|
-
"""List extracted materials and their status."""
|
|
550
|
-
try:
|
|
551
|
-
if tree and evidence_dir is None:
|
|
552
|
-
raise RuntimeError("--tree requires --evidence-dir.")
|
|
553
|
-
|
|
554
|
-
resolved_state_dsn = _require_consumer_state_dsn(state_dsn, command="list-materials")
|
|
555
|
-
data = _load_manifest_payload_from_state(resolved_state_dsn)
|
|
556
|
-
if not isinstance(data, dict):
|
|
557
|
-
raise RuntimeError("Centralized state payload is not a JSON object.")
|
|
558
|
-
projects_raw = data.get("projects")
|
|
559
|
-
if not isinstance(projects_raw, list):
|
|
560
|
-
raise RuntimeError("Centralized state payload is missing required 'projects' list.")
|
|
561
|
-
all_projects = [project for project in projects_raw if isinstance(project, dict)]
|
|
562
|
-
|
|
563
|
-
scope_root: Path | None = None
|
|
564
|
-
env_evidence_dir = str(os.getenv("VDS_AUDIT_EVIDENCE_DIR") or "").strip()
|
|
565
|
-
if evidence_dir is not None:
|
|
566
|
-
scope_root = evidence_dir.expanduser().resolve(strict=False)
|
|
567
|
-
elif env_evidence_dir:
|
|
568
|
-
scope_root = Path(env_evidence_dir).expanduser().resolve(strict=False)
|
|
569
|
-
scope_enabled = scope_root is not None
|
|
570
|
-
docs_scope_keys: set[str] = set()
|
|
571
|
-
if scope_enabled and scope_root is not None:
|
|
572
|
-
docs_root = scope_root / "docs"
|
|
573
|
-
if docs_root.exists() and docs_root.is_dir():
|
|
574
|
-
for child in docs_root.iterdir():
|
|
575
|
-
if child.is_dir():
|
|
576
|
-
normalized_child = _normalize_storage_key(child.name)
|
|
577
|
-
if normalized_child:
|
|
578
|
-
docs_scope_keys.add(normalized_child)
|
|
579
|
-
|
|
580
|
-
def _path_in_scope(path_text: str) -> bool:
|
|
581
|
-
if not scope_enabled or scope_root is None:
|
|
582
|
-
return True
|
|
583
|
-
normalized_path_text = path_text.strip()
|
|
584
|
-
if not normalized_path_text:
|
|
585
|
-
return False
|
|
586
|
-
candidate = Path(normalized_path_text).expanduser().resolve(strict=False)
|
|
587
|
-
try:
|
|
588
|
-
candidate.relative_to(scope_root)
|
|
589
|
-
except ValueError:
|
|
590
|
-
return False
|
|
591
|
-
return candidate.exists()
|
|
592
|
-
|
|
593
|
-
def _project_matches_evidence_scope(project: dict[str, Any]) -> bool:
|
|
594
|
-
if not scope_enabled or scope_root is None:
|
|
595
|
-
return True
|
|
596
|
-
for path_field in (
|
|
597
|
-
"materialized_content_root",
|
|
598
|
-
"corpus_path",
|
|
599
|
-
"tables_path",
|
|
600
|
-
"mentions_path",
|
|
601
|
-
"link_graph_path",
|
|
602
|
-
"manifest_path",
|
|
603
|
-
):
|
|
604
|
-
raw_path = str(project.get(path_field) or "").strip()
|
|
605
|
-
if raw_path and _path_in_scope(raw_path):
|
|
606
|
-
return True
|
|
607
|
-
|
|
608
|
-
for selector in (
|
|
609
|
-
str(project.get("project_storage_key") or "").strip(),
|
|
610
|
-
str(project.get("page_id") or "").strip(),
|
|
611
|
-
str(project.get("project_name") or "").strip(),
|
|
612
|
-
str(project.get("project_name_en") or "").strip(),
|
|
613
|
-
):
|
|
614
|
-
normalized_selector = _normalize_storage_key(selector) if selector else ""
|
|
615
|
-
if normalized_selector and normalized_selector in docs_scope_keys:
|
|
616
|
-
return True
|
|
617
|
-
|
|
618
|
-
return False
|
|
619
|
-
|
|
620
|
-
scoped_projects = [project for project in all_projects if _project_matches_evidence_scope(project)]
|
|
621
|
-
seen_project_keys: set[tuple[str, str, str]] = set()
|
|
622
|
-
projects: list[dict[str, Any]] = []
|
|
623
|
-
for project in scoped_projects:
|
|
624
|
-
dedupe_key = (
|
|
625
|
-
str(project.get("project_storage_key") or ""),
|
|
626
|
-
str(project.get("page_id") or ""),
|
|
627
|
-
str(project.get("project_name") or ""),
|
|
628
|
-
)
|
|
629
|
-
if dedupe_key in seen_project_keys:
|
|
630
|
-
continue
|
|
631
|
-
seen_project_keys.add(dedupe_key)
|
|
632
|
-
projects.append(project)
|
|
633
|
-
|
|
634
|
-
filtered_data = dict(data)
|
|
635
|
-
filtered_data["projects"] = projects
|
|
636
|
-
|
|
637
|
-
if _cli_state.get("json_only"):
|
|
638
|
-
_emit_json(filtered_data)
|
|
639
|
-
return
|
|
640
|
-
|
|
641
|
-
table = Table(title="Extracted Materials")
|
|
642
|
-
table.add_column("Project")
|
|
643
|
-
table.add_column("Page ID")
|
|
644
|
-
table.add_column("Docs")
|
|
645
|
-
table.add_column("Repos")
|
|
646
|
-
table.add_column("Quality")
|
|
647
|
-
table.add_column("Snapshot")
|
|
648
|
-
|
|
649
|
-
for proj in projects:
|
|
650
|
-
docs_count = len(proj.get("documents", []))
|
|
651
|
-
repos_count = len(proj.get("bitbucket_links", []))
|
|
652
|
-
linked_page_resolution = proj.get("linked_page_resolution") or {}
|
|
653
|
-
attachment_total = int(linked_page_resolution.get("attachment_total") or 0)
|
|
654
|
-
attachment_found = int(linked_page_resolution.get("attachments_found") or 0)
|
|
655
|
-
attachment_partial = int(linked_page_resolution.get("attachments_partial") or 0)
|
|
656
|
-
attachment_error = int(linked_page_resolution.get("attachments_error") or 0)
|
|
657
|
-
snapshot = proj.get("retrieval_snapshot") or {}
|
|
658
|
-
snapshot_key = str(snapshot.get("snapshot_key") or "")
|
|
659
|
-
docs_chunks = snapshot.get("docs_chunk_count")
|
|
660
|
-
code_chunks = snapshot.get("code_chunk_count")
|
|
661
|
-
quality_bits = []
|
|
662
|
-
if attachment_total:
|
|
663
|
-
quality_bits.append(f"att {attachment_found}/{attachment_total}")
|
|
664
|
-
if attachment_partial:
|
|
665
|
-
quality_bits.append(f"partial {attachment_partial}")
|
|
666
|
-
if attachment_error:
|
|
667
|
-
quality_bits.append(f"error {attachment_error}")
|
|
668
|
-
if docs_chunks is not None or code_chunks is not None:
|
|
669
|
-
quality_bits.append(f"chunks d={docs_chunks or 0} c={code_chunks or 0}")
|
|
670
|
-
quality_text = "; ".join(quality_bits) if quality_bits else "n/a"
|
|
671
|
-
snapshot_text = snapshot_key or str(proj.get("corpus_path") or "")
|
|
672
|
-
table.add_row(
|
|
673
|
-
proj.get("project_name", "Unknown"),
|
|
674
|
-
proj.get("page_id", ""),
|
|
675
|
-
str(docs_count),
|
|
676
|
-
str(repos_count),
|
|
677
|
-
quality_text,
|
|
678
|
-
snapshot_text,
|
|
679
|
-
)
|
|
680
|
-
|
|
681
|
-
console.print(table)
|
|
682
|
-
if tree and scope_root is not None:
|
|
683
|
-
rendered_root = Tree(str(scope_root))
|
|
684
|
-
|
|
685
|
-
def _render_tree(node: Tree, path: Path, *, depth: int) -> None:
|
|
686
|
-
if depth >= tree_depth:
|
|
687
|
-
return
|
|
688
|
-
try:
|
|
689
|
-
children = sorted(path.iterdir(), key=lambda child: (not child.is_dir(), child.name.casefold()))
|
|
690
|
-
except OSError as exc:
|
|
691
|
-
_log_hardened_path_error(
|
|
692
|
-
"list_materials_tree_iterdir_failed",
|
|
693
|
-
exc,
|
|
694
|
-
context=f"path={path}",
|
|
695
|
-
recovery_action="skip_tree_branch",
|
|
696
|
-
)
|
|
697
|
-
return
|
|
698
|
-
for child in children:
|
|
699
|
-
label = child.name
|
|
700
|
-
if timestamps:
|
|
701
|
-
try:
|
|
702
|
-
modified = datetime.fromtimestamp(child.stat().st_mtime, tz=UTC).strftime(
|
|
703
|
-
"%Y-%m-%d %H:%M:%S %Z"
|
|
704
|
-
)
|
|
705
|
-
label = f"{label} ({modified})"
|
|
706
|
-
except OSError as exc:
|
|
707
|
-
_log_hardened_path_error(
|
|
708
|
-
"list_materials_tree_stat_failed",
|
|
709
|
-
exc,
|
|
710
|
-
context=f"path={child}",
|
|
711
|
-
recovery_action="omit_timestamp_and_continue",
|
|
712
|
-
)
|
|
713
|
-
pass
|
|
714
|
-
child_node = node.add(label)
|
|
715
|
-
if child.is_dir():
|
|
716
|
-
_render_tree(child_node, child, depth=depth + 1)
|
|
717
|
-
|
|
718
|
-
_render_tree(rendered_root, scope_root, depth=0)
|
|
719
|
-
console.print(rendered_root)
|
|
720
|
-
|
|
721
|
-
except Exception as e:
|
|
722
|
-
message = f"Failed to read materials: {e}"
|
|
723
|
-
if _cli_state.get("json_only"):
|
|
724
|
-
_emit_json({"error": message})
|
|
725
|
-
else:
|
|
726
|
-
console.print(f"[red]{message}[/red]")
|
|
727
|
-
raise typer.Exit(1) from e
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
# ---------------------------------------------------------------------------
|
|
731
|
-
# upload-results
|
|
732
|
-
# ---------------------------------------------------------------------------
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
@app.command(name="upload-results")
|
|
736
|
-
def upload_results(
|
|
737
|
-
report_dir: Path = typer.Option(
|
|
738
|
-
...,
|
|
739
|
-
"--report-dir",
|
|
740
|
-
help="Directory containing audit reports",
|
|
741
|
-
exists=True,
|
|
742
|
-
),
|
|
743
|
-
parent_page: str = typer.Option(
|
|
744
|
-
...,
|
|
745
|
-
"--parent-page",
|
|
746
|
-
help="Confluence parent page URL or ID",
|
|
747
|
-
),
|
|
748
|
-
page_title: str = typer.Option(
|
|
749
|
-
...,
|
|
750
|
-
"--page-title",
|
|
751
|
-
help="Title for the audit result page",
|
|
752
|
-
),
|
|
753
|
-
timeout: int = typer.Option(
|
|
754
|
-
120,
|
|
755
|
-
"--timeout",
|
|
756
|
-
help="Timeout (seconds) for Confluence vds-cli calls",
|
|
757
|
-
),
|
|
758
|
-
retries: int = typer.Option(
|
|
759
|
-
2,
|
|
760
|
-
"--retries",
|
|
761
|
-
help="Number of retries for transient Confluence errors",
|
|
762
|
-
),
|
|
763
|
-
backoff: float = typer.Option(
|
|
764
|
-
1.0,
|
|
765
|
-
"--backoff",
|
|
766
|
-
help="Base backoff (seconds) for retries; exponential with attempt number",
|
|
767
|
-
),
|
|
768
|
-
include_attachments: bool = typer.Option(
|
|
769
|
-
False,
|
|
770
|
-
"--include-attachments/--no-include-attachments",
|
|
771
|
-
help="Upload report attachments (xlsx, json, sarif, audit-checklist.*). Default is page-only mode.",
|
|
772
|
-
),
|
|
773
|
-
thread_id: str | None = typer.Option(
|
|
774
|
-
None,
|
|
775
|
-
"--thread-id",
|
|
776
|
-
help="Workflow thread ID for provenance tracking",
|
|
777
|
-
),
|
|
778
|
-
publish_mode: str = typer.Option(
|
|
779
|
-
"update",
|
|
780
|
-
"--publish-mode",
|
|
781
|
-
help="Publish mode: 'update' (overwrite if exists) or 'create-only' (fail if exists)",
|
|
782
|
-
),
|
|
783
|
-
update_mode: str = typer.Option(
|
|
784
|
-
"full",
|
|
785
|
-
"--update-mode",
|
|
786
|
-
help="Update mode: 'full' or 'incremental' (delta/meta scoped updates with fallback)",
|
|
787
|
-
),
|
|
788
|
-
include_attachments_index: bool = typer.Option(
|
|
789
|
-
False,
|
|
790
|
-
"--attachments-index",
|
|
791
|
-
help="Add attachments index section with links to uploaded files",
|
|
792
|
-
),
|
|
793
|
-
priority_actions_limit: int = typer.Option(
|
|
794
|
-
10,
|
|
795
|
-
"--priority-actions-limit",
|
|
796
|
-
help="Max number of priority actions to display",
|
|
797
|
-
),
|
|
798
|
-
checklist_collapsed: bool = typer.Option(
|
|
799
|
-
False,
|
|
800
|
-
help="Start with full checklist details collapsed",
|
|
801
|
-
),
|
|
802
|
-
state_dsn: str | None = typer.Option(
|
|
803
|
-
None,
|
|
804
|
-
"--state-dsn",
|
|
805
|
-
envvar=_STATE_DSN_ENV_VAR,
|
|
806
|
-
help=f"Persist upload state metadata to backend DSN (required; fallback: {_STATE_DSN_ENV_VAR}).",
|
|
807
|
-
),
|
|
808
|
-
debug_artifacts: bool = typer.Option(
|
|
809
|
-
False,
|
|
810
|
-
"--debug-artifacts/--no-debug-artifacts",
|
|
811
|
-
help="Write optional debug artifacts: cache_trace.jsonl, sync_plan.json, state_diff.json.",
|
|
812
|
-
),
|
|
813
|
-
debug_artifacts_dir: Path | None = typer.Option(
|
|
814
|
-
None,
|
|
815
|
-
"--debug-artifacts-dir",
|
|
816
|
-
help="Directory for debug artifacts (defaults to --report-dir).",
|
|
817
|
-
),
|
|
818
|
-
project_storage_key: str | None = typer.Option(
|
|
819
|
-
None,
|
|
820
|
-
"--project-storage-key",
|
|
821
|
-
help="Explicit project storage key for deterministic hierarchy placement. Overrides page-title inference.",
|
|
822
|
-
),
|
|
823
|
-
repo_storage_key: str | None = typer.Option(
|
|
824
|
-
None,
|
|
825
|
-
"--repo-storage-key",
|
|
826
|
-
help="Explicit repo storage key for deterministic hierarchy placement. Overrides page-title inference.",
|
|
827
|
-
),
|
|
828
|
-
project_name_hint: str | None = typer.Option(
|
|
829
|
-
None,
|
|
830
|
-
"--project-name-hint",
|
|
831
|
-
help="Phase 154 (AC-154.7.5): Explicit project name for stable hierarchy node titles.",
|
|
832
|
-
),
|
|
833
|
-
) -> None:
|
|
834
|
-
"""Upload audit results to Confluence.
|
|
835
|
-
|
|
836
|
-
TSK-170B/343/344: Supports publish/update modes, attachments index, and upload counters.
|
|
837
|
-
TSK-940.69: Explicit --project-storage-key and --repo-storage-key flags for shared hierarchy.
|
|
838
|
-
"""
|
|
839
|
-
from vds_audit_orchestrator.publishers.confluence_publisher import (
|
|
840
|
-
ConfluencePublisher,
|
|
841
|
-
PublishMode,
|
|
842
|
-
UpdateMode,
|
|
843
|
-
)
|
|
844
|
-
|
|
845
|
-
# Parse publish mode
|
|
846
|
-
try:
|
|
847
|
-
mode = PublishMode(publish_mode.lower().replace("_", "-"))
|
|
848
|
-
except ValueError:
|
|
849
|
-
console.print(f"[red]Invalid publish mode: {publish_mode}. Use 'update' or 'create-only'.[/red]")
|
|
850
|
-
raise typer.Exit(1) from None
|
|
851
|
-
try:
|
|
852
|
-
resolved_update_mode = UpdateMode(update_mode.lower().replace("_", "-"))
|
|
853
|
-
except ValueError:
|
|
854
|
-
console.print(f"[red]Invalid update mode: {update_mode}. Use 'full' or 'incremental'.[/red]")
|
|
855
|
-
raise typer.Exit(1) from None
|
|
856
|
-
resolved_state_dsn = _require_producer_state_dsn(state_dsn, command="upload-results")
|
|
857
|
-
|
|
858
|
-
# TSK-940.69: Use explicit keys when provided, fall back to page-title inference.
|
|
859
|
-
inferred_project_storage_key = project_storage_key or _normalize_storage_key(page_title)
|
|
860
|
-
inferred_repo_storage_key = repo_storage_key or inferred_project_storage_key
|
|
861
|
-
|
|
862
|
-
# Phase 154 (AC-154.7.5): Resolve project_name_hint from CLI or storage key.
|
|
863
|
-
resolved_project_name_hint = project_name_hint or inferred_project_storage_key
|
|
864
|
-
|
|
865
|
-
async def _run_upload() -> dict[str, Any]:
|
|
866
|
-
client = ConfluenceCliClient(timeout=timeout, retries=retries)
|
|
867
|
-
publisher = ConfluencePublisher(client, retries=retries, backoff=backoff, state_dsn=resolved_state_dsn)
|
|
868
|
-
result = await publisher.upload_results(
|
|
869
|
-
report_dir=report_dir,
|
|
870
|
-
parent_page=parent_page,
|
|
871
|
-
page_title=page_title,
|
|
872
|
-
include_attachments=include_attachments,
|
|
873
|
-
thread_id=thread_id,
|
|
874
|
-
publish_mode=mode,
|
|
875
|
-
update_mode=resolved_update_mode,
|
|
876
|
-
include_attachments_index=include_attachments_index,
|
|
877
|
-
priority_actions_limit=priority_actions_limit,
|
|
878
|
-
checklist_collapsed=checklist_collapsed,
|
|
879
|
-
project_storage_key=inferred_project_storage_key,
|
|
880
|
-
repo_storage_key=inferred_repo_storage_key,
|
|
881
|
-
hierarchy_target="repo",
|
|
882
|
-
project_name_hint=resolved_project_name_hint,
|
|
883
|
-
)
|
|
884
|
-
readiness_extended = False
|
|
885
|
-
readiness_summary: dict[str, Any] | None = None
|
|
886
|
-
if (report_dir / "readiness-report.json").exists():
|
|
887
|
-
readiness_extended = await publisher.extend_run_page_readiness(
|
|
888
|
-
page_id=result.page_id,
|
|
889
|
-
title=page_title,
|
|
890
|
-
report_dir=report_dir,
|
|
891
|
-
)
|
|
892
|
-
try:
|
|
893
|
-
raw = json.loads((report_dir / "readiness-report.json").read_text())
|
|
894
|
-
readiness_summary = {
|
|
895
|
-
"readiness_score_pct": raw.get("readiness_score_pct"),
|
|
896
|
-
"classification": raw.get("classification"),
|
|
897
|
-
"confidence": raw.get("confidence"),
|
|
898
|
-
}
|
|
899
|
-
except (OSError, TypeError, ValueError) as exc:
|
|
900
|
-
_log_hardened_path_error(
|
|
901
|
-
"upload_results_readiness_summary_parse_failed",
|
|
902
|
-
exc,
|
|
903
|
-
context=f"report_dir={report_dir}",
|
|
904
|
-
recovery_action="set_readiness_summary_to_none",
|
|
905
|
-
)
|
|
906
|
-
readiness_summary = None
|
|
907
|
-
return {
|
|
908
|
-
"page_id": result.page_id,
|
|
909
|
-
"reused_existing_page": bool(getattr(result, "reused_existing_page", False)),
|
|
910
|
-
"attachments": result.attachments,
|
|
911
|
-
"attachments_index": result.attachments_index,
|
|
912
|
-
"update_mode": result.update_mode,
|
|
913
|
-
"effective_update_mode": result.effective_update_mode,
|
|
914
|
-
"fallback_reason": result.fallback_reason,
|
|
915
|
-
"counters": result.counters,
|
|
916
|
-
"project_root_page_id": getattr(result, "project_root_page_id", None),
|
|
917
|
-
"audit_root_page_id": getattr(result, "audit_root_page_id", None),
|
|
918
|
-
"repo_root_page_id": getattr(result, "repo_root_page_id", None),
|
|
919
|
-
"repo_run_page_id": getattr(result, "repo_run_page_id", None),
|
|
920
|
-
"project_analysis_root_page_id": getattr(result, "project_analysis_root_page_id", None),
|
|
921
|
-
"project_aggregate_page_id": getattr(result, "project_aggregate_page_id", None),
|
|
922
|
-
"sections_rendered": getattr(result, "sections_rendered", []),
|
|
923
|
-
"readiness_extended": readiness_extended,
|
|
924
|
-
"readiness_summary": readiness_summary,
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
try:
|
|
928
|
-
debug_trace = (
|
|
929
|
-
_DebugArtifactsCollector((debug_artifacts_dir or report_dir).resolve()) if debug_artifacts else None
|
|
930
|
-
)
|
|
931
|
-
upload_start = perf_counter()
|
|
932
|
-
result = asyncio.run(_run_upload())
|
|
933
|
-
upload_duration_ms = int((perf_counter() - upload_start) * 1000)
|
|
934
|
-
counters = result.get("counters") or {}
|
|
935
|
-
project_storage_key = inferred_project_storage_key
|
|
936
|
-
|
|
937
|
-
if result.get("readiness_summary"):
|
|
938
|
-
logger.info(
|
|
939
|
-
"upload_results_readiness_detected",
|
|
940
|
-
readiness_score_pct=result["readiness_summary"].get("readiness_score_pct"),
|
|
941
|
-
classification=result["readiness_summary"].get("classification"),
|
|
942
|
-
confidence=result["readiness_summary"].get("confidence"),
|
|
943
|
-
page_id=result.get("page_id"),
|
|
944
|
-
readiness_extended=result.get("readiness_extended"),
|
|
945
|
-
)
|
|
946
|
-
|
|
947
|
-
if debug_trace is not None:
|
|
948
|
-
debug_trace.add_sync_plan(
|
|
949
|
-
project_storage_key=project_storage_key,
|
|
950
|
-
resource_type="confluence_page",
|
|
951
|
-
resource_id=str(result.get("page_id") or parent_page),
|
|
952
|
-
decision="publish",
|
|
953
|
-
reason=f"publish_mode_{mode.value}",
|
|
954
|
-
)
|
|
955
|
-
if include_attachments:
|
|
956
|
-
debug_trace.add_sync_plan(
|
|
957
|
-
project_storage_key=project_storage_key,
|
|
958
|
-
resource_type="confluence_attachment",
|
|
959
|
-
resource_id=str(result.get("page_id") or parent_page),
|
|
960
|
-
decision="sync",
|
|
961
|
-
reason="attachments_enabled",
|
|
962
|
-
)
|
|
963
|
-
if include_attachments_index:
|
|
964
|
-
debug_trace.add_sync_plan(
|
|
965
|
-
project_storage_key=project_storage_key,
|
|
966
|
-
resource_type="confluence_page_section",
|
|
967
|
-
resource_id=str(result.get("page_id") or parent_page),
|
|
968
|
-
decision="update",
|
|
969
|
-
reason="attachments_index_enabled",
|
|
970
|
-
)
|
|
971
|
-
|
|
972
|
-
_emit_sync_decision(
|
|
973
|
-
logger,
|
|
974
|
-
trace=debug_trace,
|
|
975
|
-
command="upload-results",
|
|
976
|
-
project_storage_key=project_storage_key,
|
|
977
|
-
resource_type="confluence_page",
|
|
978
|
-
resource_id=str(result.get("page_id") or parent_page),
|
|
979
|
-
decision="published",
|
|
980
|
-
reason=f"effective_update_mode_{result.get('effective_update_mode')}",
|
|
981
|
-
token_before=None,
|
|
982
|
-
token_after=None,
|
|
983
|
-
duration_ms=upload_duration_ms,
|
|
984
|
-
bytes_downloaded=None,
|
|
985
|
-
cache_mode=None,
|
|
986
|
-
sync_policy=mode.value,
|
|
987
|
-
)
|
|
988
|
-
if include_attachments:
|
|
989
|
-
attachments_updated = int(counters.get("attachments_updated", len(result.get("attachments", []))))
|
|
990
|
-
attachments_skipped = int(counters.get("attachments_skipped", 0))
|
|
991
|
-
attachment_decision = "attachments_updated" if attachments_updated > 0 else "attachments_skipped"
|
|
992
|
-
attachment_reason = (
|
|
993
|
-
f"updated_{attachments_updated}_skipped_{attachments_skipped}"
|
|
994
|
-
if attachments_skipped > 0
|
|
995
|
-
else f"updated_{attachments_updated}"
|
|
996
|
-
)
|
|
997
|
-
_emit_sync_decision(
|
|
998
|
-
logger,
|
|
999
|
-
trace=debug_trace,
|
|
1000
|
-
command="upload-results",
|
|
1001
|
-
project_storage_key=project_storage_key,
|
|
1002
|
-
resource_type="confluence_attachment",
|
|
1003
|
-
resource_id=str(result.get("page_id") or parent_page),
|
|
1004
|
-
decision=attachment_decision,
|
|
1005
|
-
reason=attachment_reason,
|
|
1006
|
-
token_before=None,
|
|
1007
|
-
token_after=None,
|
|
1008
|
-
duration_ms=None,
|
|
1009
|
-
bytes_downloaded=None,
|
|
1010
|
-
cache_mode=None,
|
|
1011
|
-
sync_policy=result.get("effective_update_mode") or mode.value,
|
|
1012
|
-
)
|
|
1013
|
-
if include_attachments_index:
|
|
1014
|
-
attachments_index_count = len(result.get("attachments_index", []))
|
|
1015
|
-
_emit_sync_decision(
|
|
1016
|
-
logger,
|
|
1017
|
-
trace=debug_trace,
|
|
1018
|
-
command="upload-results",
|
|
1019
|
-
project_storage_key=project_storage_key,
|
|
1020
|
-
resource_type="confluence_page_section",
|
|
1021
|
-
resource_id=str(result.get("page_id") or parent_page),
|
|
1022
|
-
decision="attachments_index_updated" if attachments_index_count > 0 else "attachments_index_skipped",
|
|
1023
|
-
reason=f"attachments_index_count_{attachments_index_count}",
|
|
1024
|
-
token_before=None,
|
|
1025
|
-
token_after=None,
|
|
1026
|
-
duration_ms=None,
|
|
1027
|
-
bytes_downloaded=None,
|
|
1028
|
-
cache_mode=None,
|
|
1029
|
-
sync_policy=result.get("effective_update_mode") or mode.value,
|
|
1030
|
-
)
|
|
1031
|
-
|
|
1032
|
-
debug_paths = (
|
|
1033
|
-
debug_trace.write(
|
|
1034
|
-
command="upload-results",
|
|
1035
|
-
state_diff={
|
|
1036
|
-
"operation": "upload-results",
|
|
1037
|
-
"page_id": result.get("page_id"),
|
|
1038
|
-
"publish_mode": mode.value,
|
|
1039
|
-
"update_mode": result.get("update_mode"),
|
|
1040
|
-
"effective_update_mode": result.get("effective_update_mode"),
|
|
1041
|
-
"fallback_reason": result.get("fallback_reason"),
|
|
1042
|
-
"attachments_count": len(result.get("attachments", [])),
|
|
1043
|
-
"attachments_index_count": len(result.get("attachments_index", [])),
|
|
1044
|
-
"attachments_updated": int(counters.get("attachments_updated", 0)),
|
|
1045
|
-
"attachments_skipped": int(counters.get("attachments_skipped", 0)),
|
|
1046
|
-
"duration_ms": upload_duration_ms,
|
|
1047
|
-
},
|
|
1048
|
-
)
|
|
1049
|
-
if debug_trace is not None
|
|
1050
|
-
else None
|
|
1051
|
-
)
|
|
1052
|
-
|
|
1053
|
-
if thread_id:
|
|
1054
|
-
upload_run_parameters = _normalize_run_parameters(
|
|
1055
|
-
{
|
|
1056
|
-
"schema": "audit-run.parameters.v1",
|
|
1057
|
-
"command": "upload-results",
|
|
1058
|
-
"thread_id": thread_id,
|
|
1059
|
-
"report_dir": report_dir,
|
|
1060
|
-
"parent_page": parent_page,
|
|
1061
|
-
"page_title": page_title,
|
|
1062
|
-
"timeout": timeout,
|
|
1063
|
-
"retries": retries,
|
|
1064
|
-
"backoff": backoff,
|
|
1065
|
-
"include_attachments": include_attachments,
|
|
1066
|
-
"publish_mode": mode.value,
|
|
1067
|
-
"update_mode": resolved_update_mode.value,
|
|
1068
|
-
"include_attachments_index": include_attachments_index,
|
|
1069
|
-
"priority_actions_limit": priority_actions_limit,
|
|
1070
|
-
"checklist_collapsed": checklist_collapsed,
|
|
1071
|
-
"debug_artifacts": debug_artifacts,
|
|
1072
|
-
"debug_artifacts_dir": debug_artifacts_dir,
|
|
1073
|
-
"runtime_profile": _active_runtime_profile_name(),
|
|
1074
|
-
}
|
|
1075
|
-
)
|
|
1076
|
-
_persist_typed_run_terminal(
|
|
1077
|
-
state_dsn=resolved_state_dsn,
|
|
1078
|
-
run_id=thread_id,
|
|
1079
|
-
project_key=project_storage_key,
|
|
1080
|
-
repo_key=None,
|
|
1081
|
-
run_type="upload_results",
|
|
1082
|
-
terminal_status="completed",
|
|
1083
|
-
event_type="publish_completed",
|
|
1084
|
-
payload={
|
|
1085
|
-
"kind": "upload_results",
|
|
1086
|
-
"report_dir": str(report_dir),
|
|
1087
|
-
"parent_page": parent_page,
|
|
1088
|
-
"page_id": result.get("page_id"),
|
|
1089
|
-
"project_root_page_id": result.get("project_root_page_id"),
|
|
1090
|
-
"audit_root_page_id": result.get("audit_root_page_id"),
|
|
1091
|
-
"repo_root_page_id": result.get("repo_root_page_id"),
|
|
1092
|
-
"repo_run_page_id": result.get("repo_run_page_id"),
|
|
1093
|
-
"project_analysis_root_page_id": result.get("project_analysis_root_page_id"),
|
|
1094
|
-
"project_aggregate_page_id": result.get("project_aggregate_page_id"),
|
|
1095
|
-
"effective_update_mode": result.get("effective_update_mode"),
|
|
1096
|
-
"readiness_summary": result.get("readiness_summary"),
|
|
1097
|
-
"run_parameters": upload_run_parameters,
|
|
1098
|
-
},
|
|
1099
|
-
)
|
|
1100
|
-
if _cli_state.get("json_only"):
|
|
1101
|
-
if debug_paths is not None:
|
|
1102
|
-
result["debug_artifacts"] = debug_paths
|
|
1103
|
-
_emit_json(result)
|
|
1104
|
-
return
|
|
1105
|
-
console.print("[green]Upload complete.[/green]")
|
|
1106
|
-
console.print(f"Page ID: {result.get('page_id')}")
|
|
1107
|
-
console.print(f"Attachments: {len(result.get('attachments', []))}")
|
|
1108
|
-
console.print(
|
|
1109
|
-
f"Update mode: requested={result.get('update_mode')} effective={result.get('effective_update_mode')}"
|
|
1110
|
-
)
|
|
1111
|
-
if result.get("fallback_reason"):
|
|
1112
|
-
console.print(f"[yellow]Fallback reason:[/yellow] {result.get('fallback_reason')}")
|
|
1113
|
-
if isinstance(counters, dict):
|
|
1114
|
-
console.print(
|
|
1115
|
-
"Counters: "
|
|
1116
|
-
f"rows_changed={counters.get('rows_changed', 0)}, "
|
|
1117
|
-
f"rows_unchanged={counters.get('rows_unchanged', 0)}, "
|
|
1118
|
-
f"attachments_updated={counters.get('attachments_updated', 0)}, "
|
|
1119
|
-
f"attachments_skipped={counters.get('attachments_skipped', 0)}"
|
|
1120
|
-
)
|
|
1121
|
-
if result.get("attachments_index"):
|
|
1122
|
-
console.print(f"Attachments indexed: {len(result.get('attachments_index', []))}")
|
|
1123
|
-
if debug_paths is not None:
|
|
1124
|
-
console.print(f"Debug artifacts: {debug_paths['cache_trace_path']}")
|
|
1125
|
-
except ValueError as e:
|
|
1126
|
-
# TSK-170B: Handle create-only mode errors gracefully
|
|
1127
|
-
message = str(e)
|
|
1128
|
-
if _cli_state.get("json_only"):
|
|
1129
|
-
_emit_json({"error": message})
|
|
1130
|
-
else:
|
|
1131
|
-
console.print(f"[red]{message}[/red]")
|
|
1132
|
-
raise typer.Exit(1) from e
|
|
1133
|
-
except Exception as e:
|
|
1134
|
-
message = f"Upload failed: {e}"
|
|
1135
|
-
if _cli_state.get("json_only"):
|
|
1136
|
-
_emit_json({"error": message})
|
|
1137
|
-
else:
|
|
1138
|
-
console.print(f"[red]{message}[/red]")
|
|
1139
|
-
logger.exception("upload_failed")
|
|
1140
|
-
raise typer.Exit(1) from e
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
# ---------------------------------------------------------------------------
|
|
1144
|
-
# upload-project-results
|
|
1145
|
-
# ---------------------------------------------------------------------------
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
@app.command(name="upload-project-results")
|
|
1149
|
-
def upload_project_results(
|
|
1150
|
-
report_dir: Path = typer.Option(
|
|
1151
|
-
...,
|
|
1152
|
-
"--report-dir",
|
|
1153
|
-
help="Directory containing project readiness/aggregate reports",
|
|
1154
|
-
exists=True,
|
|
1155
|
-
),
|
|
1156
|
-
parent_page: str = typer.Option(
|
|
1157
|
-
...,
|
|
1158
|
-
"--parent-page",
|
|
1159
|
-
help="Confluence parent page URL or ID",
|
|
1160
|
-
),
|
|
1161
|
-
page_title: str = typer.Option(
|
|
1162
|
-
...,
|
|
1163
|
-
"--page-title",
|
|
1164
|
-
help="Title for the project aggregate page",
|
|
1165
|
-
),
|
|
1166
|
-
timeout: int = typer.Option(
|
|
1167
|
-
120,
|
|
1168
|
-
"--timeout",
|
|
1169
|
-
help="Timeout (seconds) for Confluence vds-cli calls",
|
|
1170
|
-
),
|
|
1171
|
-
retries: int = typer.Option(
|
|
1172
|
-
2,
|
|
1173
|
-
"--retries",
|
|
1174
|
-
help="Number of retries for transient Confluence errors",
|
|
1175
|
-
),
|
|
1176
|
-
backoff: float = typer.Option(
|
|
1177
|
-
1.0,
|
|
1178
|
-
"--backoff",
|
|
1179
|
-
help="Base backoff (seconds) for retries; exponential with attempt number",
|
|
1180
|
-
),
|
|
1181
|
-
include_attachments: bool = typer.Option(
|
|
1182
|
-
True,
|
|
1183
|
-
help="Upload project aggregate artifacts as attachments",
|
|
1184
|
-
),
|
|
1185
|
-
thread_id: str | None = typer.Option(
|
|
1186
|
-
None,
|
|
1187
|
-
"--thread-id",
|
|
1188
|
-
help="Workflow thread ID for provenance tracking",
|
|
1189
|
-
),
|
|
1190
|
-
project_storage_key: str | None = typer.Option(
|
|
1191
|
-
None,
|
|
1192
|
-
"--project-storage-key",
|
|
1193
|
-
help="Canonical project storage key for deterministic hierarchy path",
|
|
1194
|
-
),
|
|
1195
|
-
unified_hierarchy: bool = typer.Option(
|
|
1196
|
-
False,
|
|
1197
|
-
"--unified-hierarchy/--no-unified-hierarchy",
|
|
1198
|
-
help=(
|
|
1199
|
-
"TSK-940.71 (FR-282): Place aggregate page under the shared 'Project Audit' node "
|
|
1200
|
-
"alongside repo pages. Default places it under a separate 'Project Analysis' branch."
|
|
1201
|
-
),
|
|
1202
|
-
),
|
|
1203
|
-
state_dsn: str | None = typer.Option(
|
|
1204
|
-
None,
|
|
1205
|
-
"--state-dsn",
|
|
1206
|
-
envvar=_STATE_DSN_ENV_VAR,
|
|
1207
|
-
help=f"Persist upload state metadata to backend DSN (required; fallback: {_STATE_DSN_ENV_VAR}).",
|
|
1208
|
-
),
|
|
1209
|
-
project_name_hint: str | None = typer.Option(
|
|
1210
|
-
None,
|
|
1211
|
-
"--project-name-hint",
|
|
1212
|
-
help="Phase 154 (AC-154.7.5): Explicit project name for stable hierarchy node titles.",
|
|
1213
|
-
),
|
|
1214
|
-
) -> None:
|
|
1215
|
-
"""Upload project-level readiness aggregate to Confluence.
|
|
1216
|
-
|
|
1217
|
-
TSK-940.71 (FR-282): Use --unified-hierarchy to place the aggregate page
|
|
1218
|
-
under the shared 'Project Audit - {key}' node alongside repo pages,
|
|
1219
|
-
instead of the default separate 'Project Analysis - {key}' branch.
|
|
1220
|
-
"""
|
|
1221
|
-
from vds_audit_orchestrator.publishers.confluence_publisher import ConfluencePublisher
|
|
1222
|
-
|
|
1223
|
-
resolved_state_dsn = _require_producer_state_dsn(state_dsn, command="upload-project-results")
|
|
1224
|
-
inferred_project_key = project_storage_key or _normalize_storage_key(page_title)
|
|
1225
|
-
resolved_hierarchy_target = "repo" if unified_hierarchy else None
|
|
1226
|
-
# Phase 154 (AC-154.7.5): Resolve project_name_hint from CLI or storage key.
|
|
1227
|
-
resolved_project_name_hint = project_name_hint or inferred_project_key
|
|
1228
|
-
|
|
1229
|
-
async def _run_upload_project() -> dict[str, Any]:
|
|
1230
|
-
client = ConfluenceCliClient(timeout=timeout, retries=retries)
|
|
1231
|
-
publisher = ConfluencePublisher(client, retries=retries, backoff=backoff, state_dsn=resolved_state_dsn)
|
|
1232
|
-
result = await publisher.publish_project_aggregate(
|
|
1233
|
-
report_dir=report_dir,
|
|
1234
|
-
parent_page=parent_page,
|
|
1235
|
-
page_title=page_title,
|
|
1236
|
-
include_attachments=include_attachments,
|
|
1237
|
-
project_storage_key=inferred_project_key,
|
|
1238
|
-
hierarchy_target=resolved_hierarchy_target,
|
|
1239
|
-
project_name_hint=resolved_project_name_hint,
|
|
1240
|
-
)
|
|
1241
|
-
return {
|
|
1242
|
-
"page_id": result.page_id,
|
|
1243
|
-
"reused_existing_page": bool(getattr(result, "reused_existing_page", False)),
|
|
1244
|
-
"attachments": result.attachments,
|
|
1245
|
-
"project_root_page_id": getattr(result, "project_root_page_id", None),
|
|
1246
|
-
"project_analysis_root_page_id": getattr(result, "project_analysis_root_page_id", None),
|
|
1247
|
-
"project_aggregate_page_id": getattr(result, "project_aggregate_page_id", None),
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
try:
|
|
1251
|
-
result = asyncio.run(_run_upload_project())
|
|
1252
|
-
if thread_id:
|
|
1253
|
-
upload_project_run_parameters = _normalize_run_parameters(
|
|
1254
|
-
{
|
|
1255
|
-
"schema": "audit-run.parameters.v1",
|
|
1256
|
-
"command": "upload-project-results",
|
|
1257
|
-
"thread_id": thread_id,
|
|
1258
|
-
"report_dir": report_dir,
|
|
1259
|
-
"parent_page": parent_page,
|
|
1260
|
-
"page_title": page_title,
|
|
1261
|
-
"project_storage_key": inferred_project_key,
|
|
1262
|
-
"timeout": timeout,
|
|
1263
|
-
"retries": retries,
|
|
1264
|
-
"backoff": backoff,
|
|
1265
|
-
"include_attachments": include_attachments,
|
|
1266
|
-
"unified_hierarchy": unified_hierarchy,
|
|
1267
|
-
"runtime_profile": _active_runtime_profile_name(),
|
|
1268
|
-
}
|
|
1269
|
-
)
|
|
1270
|
-
_persist_typed_run_terminal(
|
|
1271
|
-
state_dsn=resolved_state_dsn,
|
|
1272
|
-
run_id=thread_id,
|
|
1273
|
-
project_key=inferred_project_key,
|
|
1274
|
-
repo_key=None,
|
|
1275
|
-
run_type="upload_project_results",
|
|
1276
|
-
terminal_status="completed",
|
|
1277
|
-
event_type="publish_completed",
|
|
1278
|
-
payload={
|
|
1279
|
-
"kind": "upload_project_results",
|
|
1280
|
-
"report_dir": str(report_dir),
|
|
1281
|
-
"parent_page": parent_page,
|
|
1282
|
-
"page_id": result.get("page_id"),
|
|
1283
|
-
"project_root_page_id": result.get("project_root_page_id"),
|
|
1284
|
-
"project_analysis_root_page_id": result.get("project_analysis_root_page_id"),
|
|
1285
|
-
"project_aggregate_page_id": result.get("project_aggregate_page_id"),
|
|
1286
|
-
"run_parameters": upload_project_run_parameters,
|
|
1287
|
-
},
|
|
1288
|
-
)
|
|
1289
|
-
if _cli_state.get("json_only"):
|
|
1290
|
-
_emit_json(result)
|
|
1291
|
-
return
|
|
1292
|
-
console.print("[green]Project aggregate upload complete.[/green]")
|
|
1293
|
-
console.print(f"Page ID: {result.get('page_id')}")
|
|
1294
|
-
console.print(f"Attachments: {len(result.get('attachments', []))}")
|
|
1295
|
-
except Exception as e:
|
|
1296
|
-
message = f"Project aggregate upload failed: {e}"
|
|
1297
|
-
error_context = {
|
|
1298
|
-
"exception": str(e),
|
|
1299
|
-
"exception_type": type(e).__name__,
|
|
1300
|
-
"thread_id": thread_id,
|
|
1301
|
-
"report_dir": str(report_dir),
|
|
1302
|
-
"parent_page": parent_page,
|
|
1303
|
-
"project_storage_key": inferred_project_key,
|
|
1304
|
-
}
|
|
1305
|
-
if _cli_state.get("json_only"):
|
|
1306
|
-
_emit_json({"error": message})
|
|
1307
|
-
else:
|
|
1308
|
-
console.print(f"[red]{message}[/red]")
|
|
1309
|
-
logger.exception("upload_project_results_failed", **error_context)
|
|
1310
|
-
raise typer.Exit(1) from e
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
# ---------------------------------------------------------------------------
|
|
1314
|
-
# publish-project-run
|
|
1315
|
-
# ---------------------------------------------------------------------------
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
def _materialize_project_publish_repo_dirs(
|
|
1319
|
-
*,
|
|
1320
|
-
report_dir: Path,
|
|
1321
|
-
include_existing_repos: bool = True,
|
|
1322
|
-
) -> tuple[Path, list[str]]:
|
|
1323
|
-
"""Return a repo root ready for repo-page publication.
|
|
1324
|
-
|
|
1325
|
-
Reuses existing `repos/<repo>/` directories when present. Otherwise, materializes
|
|
1326
|
-
minimal repo report directories from `project-repo-results.json` using the
|
|
1327
|
-
existing workflow helper. Returns `(repos_root, repo_keys)` and raises
|
|
1328
|
-
`ValueError` when no repo-page material can be produced.
|
|
1329
|
-
"""
|
|
1330
|
-
repos_root = report_dir / "repos"
|
|
1331
|
-
repo_dirs = []
|
|
1332
|
-
if include_existing_repos and repos_root.exists():
|
|
1333
|
-
repo_dirs = [item for item in repos_root.iterdir() if item.is_dir()]
|
|
1334
|
-
if repo_dirs:
|
|
1335
|
-
repo_keys = sorted(item.name for item in repo_dirs)
|
|
1336
|
-
return repos_root, repo_keys
|
|
1337
|
-
|
|
1338
|
-
project_repo_results_path = report_dir / "project-repo-results.json"
|
|
1339
|
-
if not project_repo_results_path.exists():
|
|
1340
|
-
raise ValueError(
|
|
1341
|
-
"Project run root does not contain repo report directories or project-repo-results.json; "
|
|
1342
|
-
"cannot publish repo pages from this artifact set."
|
|
1343
|
-
)
|
|
1344
|
-
|
|
1345
|
-
try:
|
|
1346
|
-
payload = json.loads(project_repo_results_path.read_text(encoding="utf-8"))
|
|
1347
|
-
except (OSError, TypeError, ValueError) as exc:
|
|
1348
|
-
raise ValueError(f"Failed to parse project-repo-results.json: {exc}") from exc
|
|
1349
|
-
|
|
1350
|
-
repo_results_payload = [item for item in payload if isinstance(item, dict)] if isinstance(payload, list) else []
|
|
1351
|
-
if not repo_results_payload:
|
|
1352
|
-
raise ValueError(
|
|
1353
|
-
"project-repo-results.json contains no repo payloads; cannot publish repo pages from this artifact set."
|
|
1354
|
-
)
|
|
1355
|
-
|
|
1356
|
-
from vds_audit_orchestrator import workflow_cmds as workflow_cmds_module
|
|
1357
|
-
|
|
1358
|
-
workflow_cmds_module._materialize_project_repo_report_dirs(
|
|
1359
|
-
repos_root=repos_root,
|
|
1360
|
-
repo_results_payload=repo_results_payload,
|
|
1361
|
-
)
|
|
1362
|
-
|
|
1363
|
-
repo_dirs = [item for item in repos_root.iterdir() if item.is_dir()] if repos_root.exists() else []
|
|
1364
|
-
if not repo_dirs:
|
|
1365
|
-
raise ValueError(
|
|
1366
|
-
"Repo report materialization completed without producing repo directories; "
|
|
1367
|
-
"cannot publish repo pages from this artifact set."
|
|
1368
|
-
)
|
|
1369
|
-
|
|
1370
|
-
repo_keys = sorted(item.name for item in repo_dirs)
|
|
1371
|
-
return repos_root, repo_keys
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
@app.command(name="publish-project-run")
|
|
1375
|
-
def publish_project_run(
|
|
1376
|
-
report_dir: Path | None = typer.Option(
|
|
1377
|
-
None,
|
|
1378
|
-
"--report-dir",
|
|
1379
|
-
help=(
|
|
1380
|
-
"Existing project run root containing aggregate artifacts and repo payloads. "
|
|
1381
|
-
"Required when --resume-run is not provided."
|
|
1382
|
-
),
|
|
1383
|
-
exists=False,
|
|
1384
|
-
),
|
|
1385
|
-
project_page: str = typer.Option(
|
|
1386
|
-
...,
|
|
1387
|
-
"--project-page",
|
|
1388
|
-
help="Canonical project page URL or ID used as the publication anchor",
|
|
1389
|
-
),
|
|
1390
|
-
project_storage_key: str = typer.Option(
|
|
1391
|
-
...,
|
|
1392
|
-
"--project-storage-key",
|
|
1393
|
-
help="Canonical project storage key",
|
|
1394
|
-
),
|
|
1395
|
-
project_title: str | None = typer.Option(
|
|
1396
|
-
None,
|
|
1397
|
-
"--project-title",
|
|
1398
|
-
help="Explicit project title override for repo-page titles",
|
|
1399
|
-
),
|
|
1400
|
-
aggregate_placement: str = typer.Option(
|
|
1401
|
-
"project",
|
|
1402
|
-
"--aggregate-placement",
|
|
1403
|
-
help="Aggregate placement: 'project' for separate Project Analysis branch or 'unified' under Project Audit",
|
|
1404
|
-
),
|
|
1405
|
-
include_attachments: bool = typer.Option(
|
|
1406
|
-
False,
|
|
1407
|
-
"--upload-attachments/--no-upload-attachments",
|
|
1408
|
-
help="Upload attachments for repo and aggregate pages",
|
|
1409
|
-
),
|
|
1410
|
-
thread_id: str | None = typer.Option(
|
|
1411
|
-
None,
|
|
1412
|
-
"--thread-id",
|
|
1413
|
-
help="Typed-run thread ID for provenance tracking",
|
|
1414
|
-
),
|
|
1415
|
-
state_dsn: str | None = typer.Option(
|
|
1416
|
-
None,
|
|
1417
|
-
"--state-dsn",
|
|
1418
|
-
envvar=_STATE_DSN_ENV_VAR,
|
|
1419
|
-
help=f"Persist publish state metadata to backend DSN (required; fallback: {_STATE_DSN_ENV_VAR}).",
|
|
1420
|
-
),
|
|
1421
|
-
resume_run: str | None = typer.Option(
|
|
1422
|
-
None,
|
|
1423
|
-
"--resume-run",
|
|
1424
|
-
help=(
|
|
1425
|
-
"TSK-147.16: Resume publication from a specific prior run by thread_id. "
|
|
1426
|
-
"Resolves report-dir from default layout. Ignored when --report-dir is also provided."
|
|
1427
|
-
),
|
|
1428
|
-
),
|
|
1429
|
-
) -> None:
|
|
1430
|
-
"""Publish repo pages and aggregate page from an existing project run root."""
|
|
1431
|
-
from vds_audit_orchestrator import workflow_cmds as _workflow_cmds_module
|
|
1432
|
-
from vds_audit_orchestrator.publishers.confluence_publisher import ConfluencePublisher
|
|
1433
|
-
|
|
1434
|
-
# TSK-147.16: Resolve report_dir from --resume-run when --report-dir is absent.
|
|
1435
|
-
if report_dir is None and resume_run is not None:
|
|
1436
|
-
report_dir = _workflow_cmds_module._default_workflow_project_run_root(
|
|
1437
|
-
project_storage_key=project_storage_key,
|
|
1438
|
-
thread_id=resume_run,
|
|
1439
|
-
report_dir=None,
|
|
1440
|
-
)
|
|
1441
|
-
if thread_id is None:
|
|
1442
|
-
thread_id = resume_run
|
|
1443
|
-
logger.info(
|
|
1444
|
-
"publish_project_run_resume_run_resolved",
|
|
1445
|
-
resume_run=resume_run,
|
|
1446
|
-
report_dir=str(report_dir),
|
|
1447
|
-
thread_id=thread_id,
|
|
1448
|
-
)
|
|
1449
|
-
elif report_dir is not None and resume_run is not None:
|
|
1450
|
-
logger.warning(
|
|
1451
|
-
"publish_project_run_resume_run_ignored",
|
|
1452
|
-
reason="--report-dir takes precedence over --resume-run when both are provided.",
|
|
1453
|
-
resume_run=resume_run,
|
|
1454
|
-
report_dir=str(report_dir),
|
|
1455
|
-
)
|
|
1456
|
-
elif report_dir is None:
|
|
1457
|
-
raise typer.BadParameter("--report-dir is required when --resume-run is not provided.")
|
|
1458
|
-
|
|
1459
|
-
if not report_dir.exists(): # type: ignore[union-attr]
|
|
1460
|
-
raise typer.BadParameter(f"--report-dir does not exist: {report_dir}")
|
|
1461
|
-
|
|
1462
|
-
resolved_state_dsn = _require_producer_state_dsn(state_dsn, command="publish-project-run")
|
|
1463
|
-
placement = aggregate_placement.strip().lower()
|
|
1464
|
-
if placement not in {"project", "unified"}:
|
|
1465
|
-
raise typer.BadParameter("--aggregate-placement must be 'project' or 'unified'.")
|
|
1466
|
-
|
|
1467
|
-
aggregate_payload = None
|
|
1468
|
-
aggregate_path = report_dir / "project-aggregate.json"
|
|
1469
|
-
if aggregate_path.exists():
|
|
1470
|
-
try:
|
|
1471
|
-
loaded_payload = json.loads(aggregate_path.read_text(encoding="utf-8"))
|
|
1472
|
-
aggregate_payload = loaded_payload if isinstance(loaded_payload, dict) else None
|
|
1473
|
-
except (OSError, TypeError, ValueError) as exc:
|
|
1474
|
-
raise ValueError(f"Failed to parse project-aggregate.json: {exc}") from exc
|
|
1475
|
-
resolved_project_title = (
|
|
1476
|
-
str(project_title or "")
|
|
1477
|
-
or str((aggregate_payload or {}).get("project_title") or "")
|
|
1478
|
-
or str((aggregate_payload or {}).get("project_name") or "")
|
|
1479
|
-
or str(project_storage_key)
|
|
1480
|
-
).strip()
|
|
1481
|
-
if not resolved_project_title:
|
|
1482
|
-
resolved_project_title = project_storage_key
|
|
1483
|
-
|
|
1484
|
-
try:
|
|
1485
|
-
repos_root, repo_keys = _materialize_project_publish_repo_dirs(report_dir=report_dir)
|
|
1486
|
-
except ValueError as exc:
|
|
1487
|
-
raise typer.BadParameter(str(exc)) from exc
|
|
1488
|
-
if not repo_keys:
|
|
1489
|
-
raise typer.BadParameter("No repo report directories are available for publish-project-run.")
|
|
1490
|
-
|
|
1491
|
-
async def _run_publish() -> dict[str, Any]:
|
|
1492
|
-
client = ConfluenceCliClient()
|
|
1493
|
-
publisher = ConfluencePublisher(client, state_dsn=resolved_state_dsn)
|
|
1494
|
-
repo_page_ids: dict[str, str] = {}
|
|
1495
|
-
for repo_key in repo_keys:
|
|
1496
|
-
repo_report_dir = repos_root / repo_key
|
|
1497
|
-
repo_report_dir / "workflow-summary.json"
|
|
1498
|
-
# Phase 154 (AC-154.7.3): Canonical repo title uses repo_key only.
|
|
1499
|
-
# Project context is encoded in the hierarchy node, not the repo page title.
|
|
1500
|
-
repo_title = f"Audit Results - {repo_key}"
|
|
1501
|
-
published = await publisher.upload_results(
|
|
1502
|
-
report_dir=repo_report_dir,
|
|
1503
|
-
parent_page=project_page,
|
|
1504
|
-
page_title=repo_title,
|
|
1505
|
-
include_attachments=include_attachments,
|
|
1506
|
-
thread_id=f"{thread_id}:{repo_key}" if thread_id else None,
|
|
1507
|
-
project_storage_key=project_storage_key,
|
|
1508
|
-
repo_storage_key=repo_key,
|
|
1509
|
-
hierarchy_target="repo",
|
|
1510
|
-
project_name_hint=resolved_project_title,
|
|
1511
|
-
)
|
|
1512
|
-
repo_page_ids[repo_key] = str(published.page_id)
|
|
1513
|
-
|
|
1514
|
-
aggregate_title = f"Project Analysis - {resolved_project_title}"
|
|
1515
|
-
aggregate_result = await publisher.publish_project_aggregate(
|
|
1516
|
-
report_dir=report_dir,
|
|
1517
|
-
parent_page=project_page,
|
|
1518
|
-
page_title=aggregate_title,
|
|
1519
|
-
include_attachments=include_attachments,
|
|
1520
|
-
project_storage_key=project_storage_key,
|
|
1521
|
-
hierarchy_target="repo" if placement == "unified" else None,
|
|
1522
|
-
project_name_hint=resolved_project_title,
|
|
1523
|
-
)
|
|
1524
|
-
return {
|
|
1525
|
-
"project_storage_key": project_storage_key,
|
|
1526
|
-
"project_page": project_page,
|
|
1527
|
-
"project_title": resolved_project_title,
|
|
1528
|
-
"repo_pages_published": len(repo_page_ids),
|
|
1529
|
-
"repo_page_ids": repo_page_ids,
|
|
1530
|
-
"aggregate_page_id": str(aggregate_result.page_id),
|
|
1531
|
-
"aggregate_placement": placement,
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
try:
|
|
1535
|
-
result = asyncio.run(_run_publish())
|
|
1536
|
-
if thread_id:
|
|
1537
|
-
publish_run_parameters = _normalize_run_parameters(
|
|
1538
|
-
{
|
|
1539
|
-
"schema": "audit-run.parameters.v1",
|
|
1540
|
-
"command": "publish-project-run",
|
|
1541
|
-
"thread_id": thread_id,
|
|
1542
|
-
"report_dir": report_dir,
|
|
1543
|
-
"project_page": project_page,
|
|
1544
|
-
"project_storage_key": project_storage_key,
|
|
1545
|
-
"project_title": resolved_project_title,
|
|
1546
|
-
"aggregate_placement": placement,
|
|
1547
|
-
"include_attachments": include_attachments,
|
|
1548
|
-
"runtime_profile": _active_runtime_profile_name(),
|
|
1549
|
-
}
|
|
1550
|
-
)
|
|
1551
|
-
_persist_typed_run_terminal(
|
|
1552
|
-
state_dsn=resolved_state_dsn,
|
|
1553
|
-
run_id=thread_id,
|
|
1554
|
-
project_key=project_storage_key,
|
|
1555
|
-
repo_key=None,
|
|
1556
|
-
run_type="publish_project_run",
|
|
1557
|
-
terminal_status="completed",
|
|
1558
|
-
event_type="publish_completed",
|
|
1559
|
-
payload={
|
|
1560
|
-
"kind": "publish_project_run",
|
|
1561
|
-
"report_dir": str(report_dir),
|
|
1562
|
-
"project_page": project_page,
|
|
1563
|
-
"project_storage_key": project_storage_key,
|
|
1564
|
-
"project_title": resolved_project_title,
|
|
1565
|
-
"repo_page_ids": result["repo_page_ids"],
|
|
1566
|
-
"aggregate_page_id": result["aggregate_page_id"],
|
|
1567
|
-
"aggregate_placement": placement,
|
|
1568
|
-
"run_parameters": publish_run_parameters,
|
|
1569
|
-
},
|
|
1570
|
-
)
|
|
1571
|
-
if _cli_state.get("json_only"):
|
|
1572
|
-
_emit_json(result)
|
|
1573
|
-
return
|
|
1574
|
-
console.print("[green]Project run publish complete.[/green]")
|
|
1575
|
-
console.print(f"Repo pages published: {result['repo_pages_published']}")
|
|
1576
|
-
console.print(f"Aggregate page ID: {result['aggregate_page_id']}")
|
|
1577
|
-
except Exception as exc:
|
|
1578
|
-
message = f"Project run publish failed: {exc}"
|
|
1579
|
-
if _cli_state.get("json_only"):
|
|
1580
|
-
_emit_json({"error": message})
|
|
1581
|
-
else:
|
|
1582
|
-
console.print(f"[red]{message}[/red]")
|
|
1583
|
-
logger.exception(
|
|
1584
|
-
"publish_project_run_failed",
|
|
1585
|
-
exception=str(exc),
|
|
1586
|
-
exception_type=type(exc).__name__,
|
|
1587
|
-
thread_id=thread_id,
|
|
1588
|
-
report_dir=str(report_dir),
|
|
1589
|
-
project_page=project_page,
|
|
1590
|
-
project_storage_key=project_storage_key,
|
|
1591
|
-
)
|
|
1592
|
-
raise typer.Exit(1) from exc
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
# ---------------------------------------------------------------------------
|
|
1596
|
-
# publish-system-doc helpers
|
|
1597
|
-
# ---------------------------------------------------------------------------
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
def _resolve_system_doc_parent_reference(
|
|
1601
|
-
*,
|
|
1602
|
-
parent_page: str | None,
|
|
1603
|
-
project_payload: dict[str, Any],
|
|
1604
|
-
registry_metadata: dict[str, Any],
|
|
1605
|
-
) -> tuple[str, str]:
|
|
1606
|
-
_ = project_payload
|
|
1607
|
-
if parent_page and str(parent_page).strip():
|
|
1608
|
-
return str(parent_page).strip(), "cli_override"
|
|
1609
|
-
|
|
1610
|
-
for key in ("registry_page_id", "registry_url"):
|
|
1611
|
-
candidate = str(registry_metadata.get(key) or "").strip()
|
|
1612
|
-
if candidate:
|
|
1613
|
-
return candidate, "state_registry"
|
|
1614
|
-
|
|
1615
|
-
raise typer.BadParameter(
|
|
1616
|
-
(
|
|
1617
|
-
"Unable to resolve registry root page from state. "
|
|
1618
|
-
"Provide --parent-page explicitly or run parse-registry first."
|
|
1619
|
-
),
|
|
1620
|
-
)
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
def _normalize_system_doc_sync_policy(raw_value: str) -> str:
|
|
1624
|
-
value = (raw_value or "if-changed").strip().lower()
|
|
1625
|
-
allowed = {"if-changed", "force", "dry-run"}
|
|
1626
|
-
if value not in allowed:
|
|
1627
|
-
raise typer.BadParameter("--sync-policy must be one of: if-changed, force, dry-run")
|
|
1628
|
-
return value
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
# ---------------------------------------------------------------------------
|
|
1632
|
-
# publish-system-doc
|
|
1633
|
-
# ---------------------------------------------------------------------------
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
@app.command(name="publish-system-doc")
|
|
1637
|
-
def publish_system_doc(
|
|
1638
|
-
project: str = typer.Option(
|
|
1639
|
-
...,
|
|
1640
|
-
"--project",
|
|
1641
|
-
help="Project identifier used to resolve state metadata (storage key, page ID, or project name).",
|
|
1642
|
-
),
|
|
1643
|
-
parent_page: str | None = typer.Option(
|
|
1644
|
-
None,
|
|
1645
|
-
"--parent-page",
|
|
1646
|
-
help="Optional explicit Confluence parent page URL or ID. Defaults to registry root from centralized state.",
|
|
1647
|
-
),
|
|
1648
|
-
page_title: str = typer.Option(
|
|
1649
|
-
"Audit System Documentation",
|
|
1650
|
-
"--page-title",
|
|
1651
|
-
help="System document page title.",
|
|
1652
|
-
),
|
|
1653
|
-
sync_policy: str = typer.Option(
|
|
1654
|
-
"if-changed",
|
|
1655
|
-
"--sync-policy",
|
|
1656
|
-
help="Sync policy: if-changed, force, or dry-run.",
|
|
1657
|
-
),
|
|
1658
|
-
timeout: int = typer.Option(
|
|
1659
|
-
120,
|
|
1660
|
-
"--timeout",
|
|
1661
|
-
help="Timeout (seconds) for Confluence calls.",
|
|
1662
|
-
),
|
|
1663
|
-
retries: int = typer.Option(
|
|
1664
|
-
2,
|
|
1665
|
-
"--retries",
|
|
1666
|
-
help="Number of retries for transient Confluence errors.",
|
|
1667
|
-
),
|
|
1668
|
-
backoff: float = typer.Option(
|
|
1669
|
-
1.0,
|
|
1670
|
-
"--backoff",
|
|
1671
|
-
help="Base backoff (seconds) for retries; exponential with attempt number.",
|
|
1672
|
-
),
|
|
1673
|
-
confluence_server: str | None = typer.Option(
|
|
1674
|
-
None,
|
|
1675
|
-
"--confluence-server",
|
|
1676
|
-
help="Confluence server key: internal|external. If omitted, inferred from parent page URL when possible.",
|
|
1677
|
-
),
|
|
1678
|
-
thread_id: str | None = typer.Option(
|
|
1679
|
-
None,
|
|
1680
|
-
"--thread-id",
|
|
1681
|
-
help="Optional run ID override; autogenerated when omitted.",
|
|
1682
|
-
),
|
|
1683
|
-
debug_artifacts: bool = typer.Option(
|
|
1684
|
-
False,
|
|
1685
|
-
"--debug-artifacts/--no-debug-artifacts",
|
|
1686
|
-
help="Write optional debug artifacts: cache_trace.jsonl, sync_plan.json, state_diff.json.",
|
|
1687
|
-
),
|
|
1688
|
-
debug_artifacts_dir: Path | None = typer.Option(
|
|
1689
|
-
None,
|
|
1690
|
-
"--debug-artifacts-dir",
|
|
1691
|
-
help="Directory for debug artifacts (defaults to current working directory).",
|
|
1692
|
-
),
|
|
1693
|
-
state_dsn: str | None = typer.Option(
|
|
1694
|
-
None,
|
|
1695
|
-
"--state-dsn",
|
|
1696
|
-
envvar=_STATE_DSN_ENV_VAR,
|
|
1697
|
-
help=f"Persist publish lineage to backend DSN (required; fallback: {_STATE_DSN_ENV_VAR}).",
|
|
1698
|
-
),
|
|
1699
|
-
) -> None:
|
|
1700
|
-
"""Publish the Phase 106 multi-page Audit System Documentation hierarchy."""
|
|
1701
|
-
|
|
1702
|
-
from vds_audit_orchestrator.publishers.confluence_publisher import ConfluencePublisher
|
|
1703
|
-
from vds_audit_orchestrator.publishers.hierarchy_publisher import publish_hierarchy
|
|
1704
|
-
|
|
1705
|
-
resolved_state_dsn = _require_producer_state_dsn(state_dsn, command="publish-system-doc")
|
|
1706
|
-
normalized_sync_policy = _normalize_system_doc_sync_policy(sync_policy)
|
|
1707
|
-
|
|
1708
|
-
_validate_required_credentials(command="publish-system-doc", require_confluence=True)
|
|
1709
|
-
|
|
1710
|
-
manifest_payload, projects = _resolve_parse_manifest_payload(
|
|
1711
|
-
state_dsn=resolved_state_dsn,
|
|
1712
|
-
command="publish-system-doc",
|
|
1713
|
-
)
|
|
1714
|
-
try:
|
|
1715
|
-
project_payload = select_project(projects, project)
|
|
1716
|
-
except (ValueError, LookupError) as exc:
|
|
1717
|
-
raise typer.BadParameter(str(exc)) from exc
|
|
1718
|
-
|
|
1719
|
-
project_storage_key = _project_storage_key_from_payload(project_payload)
|
|
1720
|
-
registry_metadata = _parse_registry_metadata(manifest_payload)
|
|
1721
|
-
requested_parent, parent_source = _resolve_system_doc_parent_reference(
|
|
1722
|
-
parent_page=parent_page,
|
|
1723
|
-
project_payload=project_payload,
|
|
1724
|
-
registry_metadata=registry_metadata,
|
|
1725
|
-
)
|
|
1726
|
-
inferred_server = _parse_confluence_ref(requested_parent).server if requested_parent else None
|
|
1727
|
-
resolved_server = confluence_server or inferred_server
|
|
1728
|
-
confluence = ConfluenceCliClient(server=resolved_server, timeout=timeout, retries=retries)
|
|
1729
|
-
resolved_parent_page_id = asyncio.run(_resolve_confluence_page_id(confluence, requested_parent))
|
|
1730
|
-
|
|
1731
|
-
effective_thread_id = (
|
|
1732
|
-
thread_id or f"publish-system-doc-{datetime.now(UTC).strftime('%Y%m%d%H%M%S')}-{uuid4().hex[:8]}"
|
|
1733
|
-
)
|
|
1734
|
-
debug_trace = _DebugArtifactsCollector((debug_artifacts_dir or Path.cwd()).resolve()) if debug_artifacts else None
|
|
1735
|
-
publish_start = perf_counter()
|
|
1736
|
-
|
|
1737
|
-
if debug_trace is not None:
|
|
1738
|
-
debug_trace.add_sync_plan(
|
|
1739
|
-
project_storage_key=project_storage_key,
|
|
1740
|
-
resource_type="confluence_page",
|
|
1741
|
-
resource_id=resolved_parent_page_id,
|
|
1742
|
-
decision="publish",
|
|
1743
|
-
reason=f"sync_policy_{normalized_sync_policy}",
|
|
1744
|
-
)
|
|
1745
|
-
|
|
1746
|
-
try:
|
|
1747
|
-
publisher = ConfluencePublisher(confluence, retries=retries, backoff=backoff, state_dsn=resolved_state_dsn)
|
|
1748
|
-
hierarchy_result = asyncio.run(
|
|
1749
|
-
publish_hierarchy(
|
|
1750
|
-
publisher,
|
|
1751
|
-
parent_page_id=resolved_parent_page_id,
|
|
1752
|
-
page_title=page_title,
|
|
1753
|
-
project_storage_key=project_storage_key,
|
|
1754
|
-
state_dsn=resolved_state_dsn,
|
|
1755
|
-
sync_policy=normalized_sync_policy,
|
|
1756
|
-
source_root=Path.cwd(),
|
|
1757
|
-
)
|
|
1758
|
-
)
|
|
1759
|
-
result = {
|
|
1760
|
-
"page_id": hierarchy_result.root_page_id,
|
|
1761
|
-
"parent_page_id": resolved_parent_page_id,
|
|
1762
|
-
"decision": hierarchy_result.decision,
|
|
1763
|
-
"reason": hierarchy_result.reason,
|
|
1764
|
-
"token_before": None,
|
|
1765
|
-
"token_after": hierarchy_result.root_content_hash,
|
|
1766
|
-
"source_fingerprint": None,
|
|
1767
|
-
"rendered_hash": hierarchy_result.root_content_hash,
|
|
1768
|
-
"remote_managed_hash": None,
|
|
1769
|
-
"updated_at": None,
|
|
1770
|
-
"dry_run": hierarchy_result.dry_run,
|
|
1771
|
-
"child_results": [dataclasses.asdict(item) for item in hierarchy_result.child_results],
|
|
1772
|
-
"hierarchy": True,
|
|
1773
|
-
"root_page_id": hierarchy_result.root_page_id,
|
|
1774
|
-
"root_title": hierarchy_result.root_title,
|
|
1775
|
-
"live_data_populated": hierarchy_result.live_data_populated,
|
|
1776
|
-
}
|
|
1777
|
-
duration_ms = int((perf_counter() - publish_start) * 1000)
|
|
1778
|
-
decision = str(result.get("decision") or "").strip() or ("published" if result.get("page_id") else "no-op")
|
|
1779
|
-
reason = str(result.get("reason") or "").strip() or "publisher_response"
|
|
1780
|
-
|
|
1781
|
-
_emit_sync_decision(
|
|
1782
|
-
logger,
|
|
1783
|
-
trace=debug_trace,
|
|
1784
|
-
command="publish-system-doc",
|
|
1785
|
-
project_storage_key=project_storage_key,
|
|
1786
|
-
resource_type="confluence_page",
|
|
1787
|
-
resource_id=str(result.get("page_id") or resolved_parent_page_id),
|
|
1788
|
-
decision=decision,
|
|
1789
|
-
reason=reason,
|
|
1790
|
-
token_before=result.get("token_before"),
|
|
1791
|
-
token_after=result.get("token_after"),
|
|
1792
|
-
duration_ms=duration_ms,
|
|
1793
|
-
bytes_downloaded=None,
|
|
1794
|
-
cache_mode=None,
|
|
1795
|
-
sync_policy=normalized_sync_policy,
|
|
1796
|
-
)
|
|
1797
|
-
|
|
1798
|
-
publish_payload: dict[str, Any] = {
|
|
1799
|
-
"kind": "publish_system_doc",
|
|
1800
|
-
"thread_id": effective_thread_id,
|
|
1801
|
-
"project_storage_key": project_storage_key,
|
|
1802
|
-
"page_title": page_title,
|
|
1803
|
-
"requested_parent": requested_parent,
|
|
1804
|
-
"resolved_parent_page_id": resolved_parent_page_id,
|
|
1805
|
-
"parent_source": parent_source,
|
|
1806
|
-
"page_id": result.get("page_id"),
|
|
1807
|
-
"sync_policy": normalized_sync_policy,
|
|
1808
|
-
"decision": decision,
|
|
1809
|
-
"reason": reason,
|
|
1810
|
-
"token_before": result.get("token_before"),
|
|
1811
|
-
"token_after": result.get("token_after"),
|
|
1812
|
-
"source_fingerprint": result.get("source_fingerprint"),
|
|
1813
|
-
"rendered_hash": result.get("rendered_hash"),
|
|
1814
|
-
"remote_managed_hash": result.get("remote_managed_hash"),
|
|
1815
|
-
"updated_at": result.get("updated_at"),
|
|
1816
|
-
"duration_ms": duration_ms,
|
|
1817
|
-
"hierarchy": True,
|
|
1818
|
-
"child_results": result.get("child_results"),
|
|
1819
|
-
"root_page_id": result.get("root_page_id"),
|
|
1820
|
-
"live_data_populated": result.get("live_data_populated"),
|
|
1821
|
-
}
|
|
1822
|
-
_persist_typed_run_terminal(
|
|
1823
|
-
state_dsn=resolved_state_dsn,
|
|
1824
|
-
run_id=effective_thread_id,
|
|
1825
|
-
project_key=project_storage_key,
|
|
1826
|
-
repo_key=None,
|
|
1827
|
-
run_type="publish_system_doc",
|
|
1828
|
-
terminal_status="completed",
|
|
1829
|
-
event_type="publish_completed",
|
|
1830
|
-
payload=publish_payload,
|
|
1831
|
-
)
|
|
1832
|
-
|
|
1833
|
-
output: dict[str, Any] = {
|
|
1834
|
-
"command": "publish-system-doc",
|
|
1835
|
-
"thread_id": effective_thread_id,
|
|
1836
|
-
"registry": registry_metadata,
|
|
1837
|
-
"project": {
|
|
1838
|
-
"project_storage_key": project_storage_key,
|
|
1839
|
-
"project_name": project_payload.get("project_name"),
|
|
1840
|
-
"project_name_en": project_payload.get("project_name_en"),
|
|
1841
|
-
"page_id": project_payload.get("page_id"),
|
|
1842
|
-
},
|
|
1843
|
-
"parent": {
|
|
1844
|
-
"requested": requested_parent,
|
|
1845
|
-
"resolved_page_id": resolved_parent_page_id,
|
|
1846
|
-
"source": parent_source,
|
|
1847
|
-
},
|
|
1848
|
-
"sync": {
|
|
1849
|
-
"policy": normalized_sync_policy,
|
|
1850
|
-
"decision": decision,
|
|
1851
|
-
"reason": reason,
|
|
1852
|
-
"token_before": result.get("token_before"),
|
|
1853
|
-
"token_after": result.get("token_after"),
|
|
1854
|
-
"source_fingerprint": result.get("source_fingerprint"),
|
|
1855
|
-
"rendered_hash": result.get("rendered_hash"),
|
|
1856
|
-
"remote_managed_hash": result.get("remote_managed_hash"),
|
|
1857
|
-
"hierarchy": True,
|
|
1858
|
-
},
|
|
1859
|
-
"publish": {
|
|
1860
|
-
"page_id": result.get("page_id"),
|
|
1861
|
-
"parent_page_id": result.get("parent_page_id") or resolved_parent_page_id,
|
|
1862
|
-
"updated_at": result.get("updated_at"),
|
|
1863
|
-
"dry_run": bool(result.get("dry_run")),
|
|
1864
|
-
"root_page_id": result.get("root_page_id"),
|
|
1865
|
-
"child_results": result.get("child_results"),
|
|
1866
|
-
"live_data_populated": result.get("live_data_populated"),
|
|
1867
|
-
},
|
|
1868
|
-
"duration_ms": duration_ms,
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
debug_paths = (
|
|
1872
|
-
debug_trace.write(
|
|
1873
|
-
command="publish-system-doc",
|
|
1874
|
-
state_diff={
|
|
1875
|
-
"operation": "publish-system-doc",
|
|
1876
|
-
"thread_id": effective_thread_id,
|
|
1877
|
-
"project_storage_key": project_storage_key,
|
|
1878
|
-
"resolved_parent_page_id": resolved_parent_page_id,
|
|
1879
|
-
"page_id": result.get("page_id"),
|
|
1880
|
-
"sync_policy": normalized_sync_policy,
|
|
1881
|
-
"decision": decision,
|
|
1882
|
-
"reason": reason,
|
|
1883
|
-
"token_before": result.get("token_before"),
|
|
1884
|
-
"token_after": result.get("token_after"),
|
|
1885
|
-
"source_fingerprint": result.get("source_fingerprint"),
|
|
1886
|
-
"rendered_hash": result.get("rendered_hash"),
|
|
1887
|
-
"remote_managed_hash": result.get("remote_managed_hash"),
|
|
1888
|
-
"duration_ms": duration_ms,
|
|
1889
|
-
},
|
|
1890
|
-
)
|
|
1891
|
-
if debug_trace is not None
|
|
1892
|
-
else None
|
|
1893
|
-
)
|
|
1894
|
-
if debug_paths is not None:
|
|
1895
|
-
output["debug_artifacts"] = debug_paths
|
|
1896
|
-
|
|
1897
|
-
if _cli_state.get("json_only"):
|
|
1898
|
-
_emit_json(output)
|
|
1899
|
-
return
|
|
1900
|
-
|
|
1901
|
-
console.print("[green]System document publish complete.[/green]")
|
|
1902
|
-
console.print(f"Thread ID: {effective_thread_id}")
|
|
1903
|
-
console.print(f"Project: {project_storage_key}")
|
|
1904
|
-
console.print(f"Parent page: {resolved_parent_page_id} ({parent_source})")
|
|
1905
|
-
console.print(f"Root page ID: {result.get('root_page_id')}")
|
|
1906
|
-
console.print(f"Sync: policy={normalized_sync_policy}")
|
|
1907
|
-
console.print(f"Decision: {decision} ({reason})")
|
|
1908
|
-
if debug_paths is not None:
|
|
1909
|
-
console.print(f"Debug artifacts: {debug_paths['cache_trace_path']}")
|
|
1910
|
-
except Exception as exc:
|
|
1911
|
-
try:
|
|
1912
|
-
_persist_typed_run_terminal(
|
|
1913
|
-
state_dsn=resolved_state_dsn,
|
|
1914
|
-
run_id=effective_thread_id,
|
|
1915
|
-
project_key=project_storage_key,
|
|
1916
|
-
repo_key=None,
|
|
1917
|
-
run_type="publish_system_doc",
|
|
1918
|
-
terminal_status="failed",
|
|
1919
|
-
event_type="run_failed",
|
|
1920
|
-
payload={
|
|
1921
|
-
"kind": "publish_system_doc",
|
|
1922
|
-
"thread_id": effective_thread_id,
|
|
1923
|
-
"project_storage_key": project_storage_key,
|
|
1924
|
-
"sync_policy": normalized_sync_policy,
|
|
1925
|
-
"error": str(exc),
|
|
1926
|
-
},
|
|
1927
|
-
)
|
|
1928
|
-
except Exception:
|
|
1929
|
-
logger.exception("publish_system_doc_failed_to_persist_terminal")
|
|
1930
|
-
|
|
1931
|
-
message = f"publish-system-doc failed: {exc}"
|
|
1932
|
-
if _cli_state.get("json_only"):
|
|
1933
|
-
_emit_json({"error": message, "thread_id": effective_thread_id})
|
|
1934
|
-
else:
|
|
1935
|
-
console.print(f"[red]{message}[/red]")
|
|
1936
|
-
logger.exception("publish_system_doc_failed")
|
|
1937
|
-
raise typer.Exit(1) from exc
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
# ---------------------------------------------------------------------------
|
|
1941
|
-
# approval-history
|
|
1942
|
-
# ---------------------------------------------------------------------------
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
@app.command(name="approval-history")
|
|
1946
|
-
def approval_history(
|
|
1947
|
-
thread_id: str = typer.Argument(..., help="Thread ID to look up"),
|
|
1948
|
-
) -> None:
|
|
1949
|
-
"""Get the approval decision history for a thread.
|
|
1950
|
-
|
|
1951
|
-
Retrieves the audit log entry for a previous approval decision.
|
|
1952
|
-
|
|
1953
|
-
Example:
|
|
1954
|
-
vds-audit approval-history audit-123
|
|
1955
|
-
"""
|
|
1956
|
-
from vds_audit_orchestrator.agents.approval import get_approval_history_cli
|
|
1957
|
-
|
|
1958
|
-
decision = get_approval_history_cli(thread_id)
|
|
1959
|
-
|
|
1960
|
-
if _cli_state.get("json_only"):
|
|
1961
|
-
if decision:
|
|
1962
|
-
_emit_json({"decision": decision})
|
|
1963
|
-
else:
|
|
1964
|
-
_emit_json({"decision": None, "message": "No decision found for this thread"})
|
|
1965
|
-
return
|
|
1966
|
-
|
|
1967
|
-
if not decision:
|
|
1968
|
-
console.print(f"[yellow]No approval decision found for thread: {thread_id}[/yellow]")
|
|
1969
|
-
raise typer.Exit(1)
|
|
1970
|
-
|
|
1971
|
-
console.print(f"[bold]Approval Decision for {thread_id}[/bold]\n")
|
|
1972
|
-
console.print(f"Status: {decision['status']}")
|
|
1973
|
-
console.print(f"Approved: {decision['approved']}")
|
|
1974
|
-
console.print(f"Decided at: {decision['decided_at']}")
|
|
1975
|
-
if decision.get("reviewer"):
|
|
1976
|
-
console.print(f"Reviewer: {decision['reviewer']}")
|
|
1977
|
-
if decision.get("feedback"):
|
|
1978
|
-
console.print(f"Feedback: {decision['feedback']}")
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
# ---------------------------------------------------------------------------
|
|
1982
|
-
# _load_baseline_scores (helper for audit command)
|
|
1983
|
-
# ---------------------------------------------------------------------------
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
def _load_baseline_scores(path: Path) -> dict[str, float]:
|
|
1987
|
-
"""Load baseline scores from a prior JSON report."""
|
|
1988
|
-
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
1989
|
-
repos = raw.get("repositories")
|
|
1990
|
-
if not isinstance(repos, list):
|
|
1991
|
-
return {}
|
|
1992
|
-
scores: dict[str, float] = {}
|
|
1993
|
-
for repo in repos:
|
|
1994
|
-
if not isinstance(repo, dict):
|
|
1995
|
-
continue
|
|
1996
|
-
name = repo.get("repo_name") or repo.get("repository_name")
|
|
1997
|
-
score = repo.get("total_score") or repo.get("overall_score")
|
|
1998
|
-
if name and isinstance(score, (int, float)):
|
|
1999
|
-
scores[str(name)] = float(score)
|
|
2000
|
-
return scores
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
# ---------------------------------------------------------------------------
|
|
2004
|
-
# status
|
|
2005
|
-
# ---------------------------------------------------------------------------
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
@app.command()
|
|
2009
|
-
def status() -> None:
|
|
2010
|
-
"""Check orchestrator status."""
|
|
2011
|
-
config = get_config()
|
|
2012
|
-
llm = config.llm
|
|
2013
|
-
status_payload: dict[str, Any] = {
|
|
2014
|
-
"status": "ok",
|
|
2015
|
-
"service": APP_NAME,
|
|
2016
|
-
"runtime_profile": _cli_state.get("runtime_profile", {}),
|
|
2017
|
-
"config": {
|
|
2018
|
-
"llm_enabled": bool(llm.enabled or config.features.llm_enabled),
|
|
2019
|
-
"llm_protocol": llm.protocol.value,
|
|
2020
|
-
"agent_runtime_mode": llm.agent_runtime_mode.value,
|
|
2021
|
-
"model_simple": llm.model_simple,
|
|
2022
|
-
"model_standard": llm.model_standard,
|
|
2023
|
-
"model_complex": llm.model_complex,
|
|
2024
|
-
"llm_base_url": llm.base_url,
|
|
2025
|
-
"active_profile": os.getenv("VDS_AUDIT_ACTIVE_PROFILE") or None,
|
|
2026
|
-
"state_dsn_set": bool((os.getenv(_STATE_DSN_ENV_VAR) or "").strip()),
|
|
2027
|
-
"langgraph_postgres_dsn_set": bool((os.getenv("VDS_AUDIT_LANGGRAPH__POSTGRES_DSN") or "").strip()),
|
|
2028
|
-
},
|
|
2029
|
-
}
|
|
2030
|
-
|
|
2031
|
-
if _cli_state.get("json_only"):
|
|
2032
|
-
_emit_json(status_payload)
|
|
2033
|
-
return
|
|
2034
|
-
|
|
2035
|
-
console.print(f"[green]{APP_NAME} is operational.[/green]")
|
|
2036
|
-
console.print(
|
|
2037
|
-
f"LLM protocol: [bold]{llm.protocol.value}[/bold] "
|
|
2038
|
-
f"(model standard: [bold]{llm.model_standard}[/bold], profile: [bold]{status_payload['config']['active_profile'] or 'none'}[/bold])"
|
|
2039
|
-
)
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
# ---------------------------------------------------------------------------
|
|
2043
|
-
# list-checks
|
|
2044
|
-
# ---------------------------------------------------------------------------
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
@app.command(name="list-checks")
|
|
2048
|
-
def list_checks(
|
|
2049
|
-
category: str | None = typer.Option(None, "--category", "-c", help="Filter by category"),
|
|
2050
|
-
detailed: bool = typer.Option(False, "--detailed", "-d", help="Show detailed check information"),
|
|
2051
|
-
) -> None:
|
|
2052
|
-
"""List available audit checks with descriptions and configuration options."""
|
|
2053
|
-
if category:
|
|
2054
|
-
checks = CheckRegistry.list_checks_by_category(category)
|
|
2055
|
-
if not checks:
|
|
2056
|
-
console.print(f"[yellow]No checks found in category '{category}'[/yellow]")
|
|
2057
|
-
console.print(f"Available categories: {', '.join(CheckRegistry.get_categories())}")
|
|
2058
|
-
raise typer.Exit(1)
|
|
2059
|
-
else:
|
|
2060
|
-
checks = CheckRegistry.list_checks()
|
|
2061
|
-
|
|
2062
|
-
if _cli_state.get("json_only"):
|
|
2063
|
-
result: dict[str, Any] = {"checks": [], "count": len(checks)}
|
|
2064
|
-
for check_name in checks:
|
|
2065
|
-
check_metadata = CheckRegistry.get_metadata(check_name)
|
|
2066
|
-
if check_metadata:
|
|
2067
|
-
check_data = check_metadata.to_dict()
|
|
2068
|
-
check_data["registered"] = check_name in CheckRegistry._checks
|
|
2069
|
-
result["checks"].append(check_data)
|
|
2070
|
-
else:
|
|
2071
|
-
result["checks"].append(
|
|
2072
|
-
{
|
|
2073
|
-
"check_type": check_name,
|
|
2074
|
-
"registered": True,
|
|
2075
|
-
}
|
|
2076
|
-
)
|
|
2077
|
-
if category:
|
|
2078
|
-
result["category"] = category
|
|
2079
|
-
result["summary"] = CheckRegistry.get_summary()
|
|
2080
|
-
_emit_json(result)
|
|
2081
|
-
return
|
|
2082
|
-
|
|
2083
|
-
# Summary table
|
|
2084
|
-
summary = CheckRegistry.get_summary()
|
|
2085
|
-
console.print(f"[bold]Available Checks ({len(checks)}):[/bold]")
|
|
2086
|
-
console.print(f"Categories: {', '.join(summary['categories'])}\n")
|
|
2087
|
-
|
|
2088
|
-
# Group by category
|
|
2089
|
-
categories = CheckRegistry.get_categories()
|
|
2090
|
-
for cat in categories:
|
|
2091
|
-
cat_checks = CheckRegistry.list_checks_by_category(cat)
|
|
2092
|
-
if category and cat != category:
|
|
2093
|
-
continue
|
|
2094
|
-
if not cat_checks:
|
|
2095
|
-
continue
|
|
2096
|
-
|
|
2097
|
-
console.print(f"[bold cyan]{cat.upper()}[/bold cyan] ({len(cat_checks)} checks)")
|
|
2098
|
-
|
|
2099
|
-
if detailed:
|
|
2100
|
-
for check_name in cat_checks:
|
|
2101
|
-
check_metadata = CheckRegistry.get_metadata(check_name)
|
|
2102
|
-
if check_metadata:
|
|
2103
|
-
# Severity color
|
|
2104
|
-
severity_colors = {
|
|
2105
|
-
"Critical": "red",
|
|
2106
|
-
"High": "bright_red",
|
|
2107
|
-
"Medium": "yellow",
|
|
2108
|
-
"Low": "blue",
|
|
2109
|
-
"Info": "dim",
|
|
2110
|
-
}
|
|
2111
|
-
sev_color = severity_colors.get(check_metadata.default_severity.value, "white")
|
|
2112
|
-
|
|
2113
|
-
console.print(f" [bold green]{check_name}[/bold green]")
|
|
2114
|
-
console.print(f" Name: {check_metadata.name}")
|
|
2115
|
-
console.print(f" Description: {check_metadata.description}")
|
|
2116
|
-
console.print(f" Severity: [{sev_color}]{check_metadata.default_severity.value}[/{sev_color}]")
|
|
2117
|
-
if check_metadata.tags:
|
|
2118
|
-
console.print(f" Tags: {', '.join(check_metadata.tags)}")
|
|
2119
|
-
|
|
2120
|
-
# Config schema
|
|
2121
|
-
if check_metadata.config_schema:
|
|
2122
|
-
console.print(" Configuration:")
|
|
2123
|
-
for param, schema in check_metadata.config_schema.items():
|
|
2124
|
-
required = "[red]*[/red]" if schema.get("required") else ""
|
|
2125
|
-
console.print(f" - {param}{required} ({schema.get('type', 'any')})")
|
|
2126
|
-
if schema.get("description"):
|
|
2127
|
-
console.print(f" {schema['description']}")
|
|
2128
|
-
console.print()
|
|
2129
|
-
else:
|
|
2130
|
-
for check_name in cat_checks:
|
|
2131
|
-
check_metadata = CheckRegistry.get_metadata(check_name)
|
|
2132
|
-
desc = check_metadata.description if check_metadata else ""
|
|
2133
|
-
console.print(f" [bold green]{check_name}[/bold green]: {desc}")
|
|
2134
|
-
console.print()
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
# ---------------------------------------------------------------------------
|
|
2138
|
-
# Template validation helpers
|
|
2139
|
-
# ---------------------------------------------------------------------------
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
@dataclass
|
|
2143
|
-
class TemplateValidationResult:
|
|
2144
|
-
"""Result of template validation."""
|
|
2145
|
-
|
|
2146
|
-
valid: bool = False
|
|
2147
|
-
template_name: str | None = None
|
|
2148
|
-
template_version: str | None = None
|
|
2149
|
-
section_count: int = 0
|
|
2150
|
-
check_count: int = 0
|
|
2151
|
-
errors: list[str] = field(default_factory=list)
|
|
2152
|
-
warnings: list[str] = field(default_factory=list)
|
|
2153
|
-
|
|
2154
|
-
def to_dict(self) -> dict[str, Any]:
|
|
2155
|
-
"""Convert to dictionary for JSON output."""
|
|
2156
|
-
return {
|
|
2157
|
-
"valid": self.valid,
|
|
2158
|
-
"template_name": self.template_name,
|
|
2159
|
-
"template_version": self.template_version,
|
|
2160
|
-
"section_count": self.section_count,
|
|
2161
|
-
"check_count": self.check_count,
|
|
2162
|
-
"errors": self.errors,
|
|
2163
|
-
"warnings": self.warnings,
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
def _validate_template_weights(template: Any) -> tuple[list[str], list[str]]:
|
|
2168
|
-
"""Validate template weights and return errors/warnings."""
|
|
2169
|
-
errors: list[str] = []
|
|
2170
|
-
warnings: list[str] = []
|
|
2171
|
-
|
|
2172
|
-
if not template.sections:
|
|
2173
|
-
warnings.append("Template has no sections defined")
|
|
2174
|
-
return errors, warnings
|
|
2175
|
-
|
|
2176
|
-
# Check section weights sum to ~100
|
|
2177
|
-
total_section_weight = sum(s.weight for s in template.sections)
|
|
2178
|
-
if abs(total_section_weight - 100.0) > 0.1:
|
|
2179
|
-
warnings.append(f"Section weights sum to {total_section_weight:.1f}, expected 100.0")
|
|
2180
|
-
|
|
2181
|
-
# Check internal section weights
|
|
2182
|
-
for section in template.sections:
|
|
2183
|
-
if not section.checks:
|
|
2184
|
-
warnings.append(f"Section '{section.name}' has no checks defined")
|
|
2185
|
-
continue
|
|
2186
|
-
|
|
2187
|
-
weights = [c.weight for c in section.checks if c.weight is not None]
|
|
2188
|
-
if weights:
|
|
2189
|
-
section_total = sum(weights)
|
|
2190
|
-
if abs(section_total - 100.0) > 0.1:
|
|
2191
|
-
warnings.append(f"Section '{section.name}' check weights sum to {section_total:.1f}, expected 100.0")
|
|
2192
|
-
|
|
2193
|
-
return errors, warnings
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
def _load_template(
|
|
2197
|
-
template: Path | None,
|
|
2198
|
-
template_url: str | None,
|
|
2199
|
-
template_sheet: str | None,
|
|
2200
|
-
template_gid: int | None,
|
|
2201
|
-
template_table_index: int | None,
|
|
2202
|
-
template_map: str | None,
|
|
2203
|
-
gs_auth_method: str | None,
|
|
2204
|
-
gs_credentials_path: Path | None,
|
|
2205
|
-
gs_oauth_credentials_path: Path | None,
|
|
2206
|
-
gs_api_key: str | None,
|
|
2207
|
-
) -> Any:
|
|
2208
|
-
"""Load template from Excel or Google Sheets."""
|
|
2209
|
-
if template and template_url:
|
|
2210
|
-
raise ValueError("Provide either --template or --template-url (not both)")
|
|
2211
|
-
|
|
2212
|
-
if template:
|
|
2213
|
-
loader = ExcelLoader()
|
|
2214
|
-
return loader.load(template)
|
|
2215
|
-
elif template_url:
|
|
2216
|
-
# Resolve mapping path (supports both file paths and bundled mapping names)
|
|
2217
|
-
mapping_path = resolve_mapping_path(template_map) if template_map else None
|
|
2218
|
-
mapping = load_mapping_file(mapping_path) if mapping_path else None
|
|
2219
|
-
return load_template_from_google_sheet(
|
|
2220
|
-
spreadsheet_url=template_url,
|
|
2221
|
-
sheet_name=template_sheet,
|
|
2222
|
-
sheet_gid=template_gid,
|
|
2223
|
-
doc_table_index=template_table_index,
|
|
2224
|
-
template_map=mapping,
|
|
2225
|
-
auth_method=gs_auth_method,
|
|
2226
|
-
credentials_path=gs_credentials_path,
|
|
2227
|
-
oauth_credentials_path=gs_oauth_credentials_path,
|
|
2228
|
-
api_key=gs_api_key,
|
|
2229
|
-
)
|
|
2230
|
-
else:
|
|
2231
|
-
raise ValueError("Must provide either --template or --template-url")
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
# ---------------------------------------------------------------------------
|
|
2235
|
-
# validate-template
|
|
2236
|
-
# ---------------------------------------------------------------------------
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
@app.command(name="validate-template")
|
|
2240
|
-
def validate_template(
|
|
2241
|
-
template: Path | None = typer.Option(None, "--template", "-t", help="Path to Excel template", exists=True),
|
|
2242
|
-
template_url: str | None = typer.Option(
|
|
2243
|
-
None,
|
|
2244
|
-
"--template-url",
|
|
2245
|
-
"--google-sheet",
|
|
2246
|
-
"-g",
|
|
2247
|
-
help="Google Sheet URL for rubric-style template",
|
|
2248
|
-
),
|
|
2249
|
-
template_sheet: str | None = typer.Option(
|
|
2250
|
-
None,
|
|
2251
|
-
"--template-sheet",
|
|
2252
|
-
"--sheet-name",
|
|
2253
|
-
help="Sheet name in Google Sheet",
|
|
2254
|
-
),
|
|
2255
|
-
template_gid: int | None = typer.Option(
|
|
2256
|
-
None,
|
|
2257
|
-
"--template-gid",
|
|
2258
|
-
"--sheet-gid",
|
|
2259
|
-
help="Sheet GID in Google Sheet",
|
|
2260
|
-
),
|
|
2261
|
-
template_table_index: int | None = typer.Option(
|
|
2262
|
-
None,
|
|
2263
|
-
"--template-table-index",
|
|
2264
|
-
help="Google Docs table index (0-based)",
|
|
2265
|
-
),
|
|
2266
|
-
template_map: Path | None = typer.Option(
|
|
2267
|
-
None,
|
|
2268
|
-
"--template-map",
|
|
2269
|
-
help="Optional JSON mapping file for rubric-style sheets",
|
|
2270
|
-
exists=True,
|
|
2271
|
-
),
|
|
2272
|
-
gs_auth_method: str | None = typer.Option(None, "--gs-auth-method", help="Google Sheets auth method"),
|
|
2273
|
-
gs_credentials_path: Path | None = typer.Option(
|
|
2274
|
-
None, "--gs-credentials-path", help="Google Sheets service account credentials path"
|
|
2275
|
-
),
|
|
2276
|
-
gs_oauth_credentials_path: Path | None = typer.Option(
|
|
2277
|
-
None, "--gs-oauth-credentials-path", help="Google Sheets OAuth credentials path"
|
|
2278
|
-
),
|
|
2279
|
-
gs_api_key: str | None = typer.Option(None, "--gs-api-key", help="Google Sheets API key"),
|
|
2280
|
-
) -> None:
|
|
2281
|
-
"""Validate an audit template without running an audit.
|
|
2282
|
-
|
|
2283
|
-
Checks template syntax, weight sums, and check definitions.
|
|
2284
|
-
Use this command to verify templates before running audits.
|
|
2285
|
-
"""
|
|
2286
|
-
json_only = _cli_state.get("json_only", False)
|
|
2287
|
-
result = TemplateValidationResult()
|
|
2288
|
-
|
|
2289
|
-
# Validate input arguments
|
|
2290
|
-
if template and template_url:
|
|
2291
|
-
error_msg = "Provide either --template or --template-url (not both)"
|
|
2292
|
-
if json_only:
|
|
2293
|
-
result.errors.append(error_msg)
|
|
2294
|
-
_emit_json(result.to_dict())
|
|
2295
|
-
else:
|
|
2296
|
-
console.print(f"[red]Error:[/red] {error_msg}")
|
|
2297
|
-
raise typer.Exit(1)
|
|
2298
|
-
|
|
2299
|
-
if not template and not template_url:
|
|
2300
|
-
error_msg = "Must provide either --template or --template-url"
|
|
2301
|
-
if json_only:
|
|
2302
|
-
result.errors.append(error_msg)
|
|
2303
|
-
_emit_json(result.to_dict())
|
|
2304
|
-
else:
|
|
2305
|
-
console.print(f"[red]Error:[/red] {error_msg}")
|
|
2306
|
-
raise typer.Exit(1)
|
|
2307
|
-
|
|
2308
|
-
# Load template with progress indicator
|
|
2309
|
-
try:
|
|
2310
|
-
if not json_only:
|
|
2311
|
-
with Progress(
|
|
2312
|
-
SpinnerColumn(),
|
|
2313
|
-
TextColumn("[progress.description]{task.description}"),
|
|
2314
|
-
transient=True,
|
|
2315
|
-
) as progress:
|
|
2316
|
-
progress.add_task("Loading template...", total=None)
|
|
2317
|
-
audit_template = _load_template(
|
|
2318
|
-
template=template,
|
|
2319
|
-
template_url=template_url,
|
|
2320
|
-
template_sheet=template_sheet,
|
|
2321
|
-
template_gid=template_gid,
|
|
2322
|
-
template_table_index=template_table_index,
|
|
2323
|
-
template_map=str(template_map) if template_map else None,
|
|
2324
|
-
gs_auth_method=gs_auth_method,
|
|
2325
|
-
gs_credentials_path=gs_credentials_path,
|
|
2326
|
-
gs_oauth_credentials_path=gs_oauth_credentials_path,
|
|
2327
|
-
gs_api_key=gs_api_key,
|
|
2328
|
-
)
|
|
2329
|
-
else:
|
|
2330
|
-
audit_template = _load_template(
|
|
2331
|
-
template=template,
|
|
2332
|
-
template_url=template_url,
|
|
2333
|
-
template_sheet=template_sheet,
|
|
2334
|
-
template_gid=template_gid,
|
|
2335
|
-
template_table_index=template_table_index,
|
|
2336
|
-
template_map=str(template_map) if template_map else None,
|
|
2337
|
-
gs_auth_method=gs_auth_method,
|
|
2338
|
-
gs_credentials_path=gs_credentials_path,
|
|
2339
|
-
gs_oauth_credentials_path=gs_oauth_credentials_path,
|
|
2340
|
-
gs_api_key=gs_api_key,
|
|
2341
|
-
)
|
|
2342
|
-
|
|
2343
|
-
except (OSError, RuntimeError, TypeError, ValueError) as e:
|
|
2344
|
-
_log_hardened_path_error(
|
|
2345
|
-
"validate_template_load_failed",
|
|
2346
|
-
e,
|
|
2347
|
-
context=f"template={template}, template_url={template_url}",
|
|
2348
|
-
recovery_action="emit_cli_error_and_exit",
|
|
2349
|
-
level="exception",
|
|
2350
|
-
)
|
|
2351
|
-
error_msg = f"Failed to load template: {e}"
|
|
2352
|
-
if json_only:
|
|
2353
|
-
result.errors.append(error_msg)
|
|
2354
|
-
_emit_json(result.to_dict())
|
|
2355
|
-
else:
|
|
2356
|
-
console.print(f"[red]Error:[/red] {error_msg}")
|
|
2357
|
-
logger.exception("Template load failed")
|
|
2358
|
-
raise typer.Exit(1) from e
|
|
2359
|
-
|
|
2360
|
-
# Populate result metadata
|
|
2361
|
-
result.template_name = getattr(audit_template, "name", None)
|
|
2362
|
-
result.template_version = getattr(audit_template, "version", None)
|
|
2363
|
-
result.section_count = len(audit_template.sections) if audit_template.sections else 0
|
|
2364
|
-
result.check_count = sum(len(s.checks) for s in audit_template.sections) if audit_template.sections else 0
|
|
2365
|
-
|
|
2366
|
-
# Run validator
|
|
2367
|
-
validator = TemplateValidator()
|
|
2368
|
-
try:
|
|
2369
|
-
validator.validate(audit_template)
|
|
2370
|
-
except TemplateValidationError as e:
|
|
2371
|
-
result.errors.append(str(e))
|
|
2372
|
-
|
|
2373
|
-
# Check weight sums
|
|
2374
|
-
weight_errors, weight_warnings = _validate_template_weights(audit_template)
|
|
2375
|
-
result.errors.extend(weight_errors)
|
|
2376
|
-
result.warnings.extend(weight_warnings)
|
|
2377
|
-
|
|
2378
|
-
# Determine validity
|
|
2379
|
-
result.valid = len(result.errors) == 0
|
|
2380
|
-
|
|
2381
|
-
# Output results
|
|
2382
|
-
if json_only:
|
|
2383
|
-
_emit_json(result.to_dict())
|
|
2384
|
-
else:
|
|
2385
|
-
if result.valid:
|
|
2386
|
-
console.print("[green]Template is valid.[/green]")
|
|
2387
|
-
else:
|
|
2388
|
-
console.print("[red]Template validation failed.[/red]")
|
|
2389
|
-
|
|
2390
|
-
# Template info table
|
|
2391
|
-
info_table = Table(title="Template Info", show_header=False)
|
|
2392
|
-
info_table.add_column("Property", style="bold")
|
|
2393
|
-
info_table.add_column("Value")
|
|
2394
|
-
info_table.add_row("Name", result.template_name or "(not set)")
|
|
2395
|
-
info_table.add_row("Version", result.template_version or "(not set)")
|
|
2396
|
-
info_table.add_row("Sections", str(result.section_count))
|
|
2397
|
-
info_table.add_row("Checks", str(result.check_count))
|
|
2398
|
-
console.print(info_table)
|
|
2399
|
-
|
|
2400
|
-
# Errors
|
|
2401
|
-
if result.errors:
|
|
2402
|
-
console.print("\n[bold red]Errors:[/bold red]")
|
|
2403
|
-
for error in result.errors:
|
|
2404
|
-
console.print(f" [red]x[/red] {error}")
|
|
2405
|
-
|
|
2406
|
-
# Warnings
|
|
2407
|
-
if result.warnings:
|
|
2408
|
-
console.print("\n[bold yellow]Warnings:[/bold yellow]")
|
|
2409
|
-
for warning in result.warnings:
|
|
2410
|
-
console.print(f" [yellow]![/yellow] {warning}")
|
|
2411
|
-
|
|
2412
|
-
if not result.valid:
|
|
2413
|
-
raise typer.Exit(1)
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
# ---------------------------------------------------------------------------
|
|
2417
|
-
# query-checklist
|
|
2418
|
-
# ---------------------------------------------------------------------------
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
def _query_checklist_page_id(
|
|
2422
|
-
*,
|
|
2423
|
-
checklist_page: str | None,
|
|
2424
|
-
project_storage_key: str | None,
|
|
2425
|
-
checklist_profile: str | None,
|
|
2426
|
-
state_dsn: str | None,
|
|
2427
|
-
) -> tuple[str, dict[str, Any]]:
|
|
2428
|
-
if checklist_page:
|
|
2429
|
-
ref = _parse_confluence_ref(checklist_page)
|
|
2430
|
-
page_id = ref.page_id or checklist_page.strip()
|
|
2431
|
-
return page_id, {"source": "explicit_page", "checklist_profile": checklist_profile}
|
|
2432
|
-
|
|
2433
|
-
resolved_state_dsn = _require_consumer_state_dsn(state_dsn, command="query-checklist")
|
|
2434
|
-
manifest_payload = _load_manifest_payload_from_state(resolved_state_dsn)
|
|
2435
|
-
projects = manifest_payload.get("projects") if isinstance(manifest_payload, dict) else []
|
|
2436
|
-
if not isinstance(projects, list):
|
|
2437
|
-
raise typer.BadParameter("Centralized state payload is missing 'projects' entries.")
|
|
2438
|
-
if not project_storage_key:
|
|
2439
|
-
raise typer.BadParameter("Provide either --checklist-page or --project-storage-key.")
|
|
2440
|
-
project_payload = select_project([p for p in projects if isinstance(p, dict)], project_storage_key)
|
|
2441
|
-
repo_type = None
|
|
2442
|
-
bitbucket_entries = project_payload.get("bitbucket")
|
|
2443
|
-
if isinstance(bitbucket_entries, list) and bitbucket_entries:
|
|
2444
|
-
first_repo = bitbucket_entries[0] if isinstance(bitbucket_entries[0], dict) else {}
|
|
2445
|
-
repo_type = str(first_repo.get("repo_type") or "").strip() or None
|
|
2446
|
-
bootstrap = resolve_checklist_profile(
|
|
2447
|
-
explicit_page_id=None,
|
|
2448
|
-
explicit_profile=checklist_profile,
|
|
2449
|
-
repo_type=repo_type,
|
|
2450
|
-
state_dsn=resolved_state_dsn,
|
|
2451
|
-
state_registry_loader=_load_manifest_payload_from_state,
|
|
2452
|
-
)
|
|
2453
|
-
return bootstrap.page_id, {
|
|
2454
|
-
"source": bootstrap.source,
|
|
2455
|
-
"checklist_profile": bootstrap.profile_id,
|
|
2456
|
-
"project_storage_key": project_storage_key,
|
|
2457
|
-
}
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
@app.command(name="query-checklist")
|
|
2461
|
-
def query_checklist(
|
|
2462
|
-
checklist_page: str | None = typer.Option(None, "--checklist-page", help="Explicit checklist page ID or URL."),
|
|
2463
|
-
project_storage_key: str | None = typer.Option(
|
|
2464
|
-
None, "--project-storage-key", help="Project storage key used for checklist bootstrap resolution."
|
|
2465
|
-
),
|
|
2466
|
-
checklist_profile: str | None = typer.Option(
|
|
2467
|
-
None, "--checklist-profile", help="Optional checklist profile identifier for bootstrap resolution."
|
|
2468
|
-
),
|
|
2469
|
-
target: str | None = typer.Option(
|
|
2470
|
-
None,
|
|
2471
|
-
"--target",
|
|
2472
|
-
help="Reuse workflow-style row selector semantics (row index, check id, row key, or content query). Canonical CL-* check-id matching is best-effort because source checklist pages may only expose parser-local ids unless canonical aliases are present.",
|
|
2473
|
-
),
|
|
2474
|
-
category: str | None = typer.Option(None, "--category", help="Filter rows by requirement_category or section_id."),
|
|
2475
|
-
summary_only: bool = typer.Option(False, "--summary-only", help="Return checklist summary without row payloads."),
|
|
2476
|
-
state_dsn: str | None = typer.Option(
|
|
2477
|
-
None, "--state-dsn", envvar=_STATE_DSN_ENV_VAR, help=f"Centralized state DSN (fallback: {_STATE_DSN_ENV_VAR})."
|
|
2478
|
-
),
|
|
2479
|
-
confluence_server: str | None = typer.Option(
|
|
2480
|
-
None,
|
|
2481
|
-
"--confluence-server",
|
|
2482
|
-
help="Confluence server key: internal|external. If omitted, inferred from page URL when possible.",
|
|
2483
|
-
),
|
|
2484
|
-
) -> None:
|
|
2485
|
-
"""Query source checklist content directly without running workflow evaluation."""
|
|
2486
|
-
if not checklist_page and not project_storage_key:
|
|
2487
|
-
raise typer.BadParameter("Provide either --checklist-page or --project-storage-key.")
|
|
2488
|
-
|
|
2489
|
-
inferred_server = _parse_confluence_ref(checklist_page).server if checklist_page else None
|
|
2490
|
-
client = ConfluenceCliClient.for_registry_discovery(server=confluence_server or inferred_server)
|
|
2491
|
-
page_id, query_meta = _query_checklist_page_id(
|
|
2492
|
-
checklist_page=checklist_page,
|
|
2493
|
-
project_storage_key=project_storage_key,
|
|
2494
|
-
checklist_profile=checklist_profile,
|
|
2495
|
-
state_dsn=state_dsn,
|
|
2496
|
-
)
|
|
2497
|
-
parser = ChecklistParser(client)
|
|
2498
|
-
template = asyncio.run(parser.parse_checklist(page_id))
|
|
2499
|
-
if template is None:
|
|
2500
|
-
raise typer.BadParameter(f"Checklist page '{page_id}' could not be parsed.")
|
|
2501
|
-
|
|
2502
|
-
result = query_checklist_template(
|
|
2503
|
-
template,
|
|
2504
|
-
filters=ChecklistQueryFilters(target=target, category=category, summary_only=summary_only),
|
|
2505
|
-
)
|
|
2506
|
-
payload = {
|
|
2507
|
-
"checklist_page_id": page_id,
|
|
2508
|
-
"template_name": result.template_name,
|
|
2509
|
-
"template_version": result.template_version,
|
|
2510
|
-
"total_rows": result.total_rows,
|
|
2511
|
-
"returned_rows": result.returned_rows,
|
|
2512
|
-
"target": result.target,
|
|
2513
|
-
"category": result.category,
|
|
2514
|
-
"query_meta": query_meta,
|
|
2515
|
-
"rows": [row.__dict__ for row in result.rows],
|
|
2516
|
-
}
|
|
2517
|
-
if _cli_state.get("json_only"):
|
|
2518
|
-
_emit_json(payload)
|
|
2519
|
-
return
|
|
2520
|
-
|
|
2521
|
-
console.print(f"[bold]Checklist:[/bold] {result.template_name} (page {page_id})")
|
|
2522
|
-
console.print(f"Rows returned: {result.returned_rows} / {result.total_rows}")
|
|
2523
|
-
if category:
|
|
2524
|
-
console.print(f"Category filter: {category}")
|
|
2525
|
-
if target:
|
|
2526
|
-
console.print(f"Target filter: {target}")
|
|
2527
|
-
if summary_only:
|
|
2528
|
-
return
|
|
2529
|
-
for row in result.rows:
|
|
2530
|
-
console.print(
|
|
2531
|
-
f"- [{row.row_index + 1}] {row.row_id} | {row.check_id or '-'} | {row.requirement_category or row.section_id or '-'} | {row.description}"
|
|
2532
|
-
)
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
# ---------------------------------------------------------------------------
|
|
2536
|
-
# validate-checklist
|
|
2537
|
-
# ---------------------------------------------------------------------------
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
@app.command(name="validate-checklist")
|
|
2541
|
-
def validate_checklist(
|
|
2542
|
-
checklist_json: Path | None = typer.Option(
|
|
2543
|
-
None,
|
|
2544
|
-
"--checklist-json",
|
|
2545
|
-
help="Path to audit-checklist.json. Defaults to <report-dir>/audit-checklist.json.",
|
|
2546
|
-
),
|
|
2547
|
-
report_dir: Path = typer.Option(
|
|
2548
|
-
Path(),
|
|
2549
|
-
"--report-dir",
|
|
2550
|
-
help="Directory that contains checklist artifacts.",
|
|
2551
|
-
),
|
|
2552
|
-
checklist_xlsx: Path | None = typer.Option(
|
|
2553
|
-
None,
|
|
2554
|
-
"--checklist-xlsx",
|
|
2555
|
-
help="Path to audit-checklist.xlsx. Defaults to <report-dir>/audit-checklist.xlsx.",
|
|
2556
|
-
),
|
|
2557
|
-
require_xlsx: bool = typer.Option(
|
|
2558
|
-
False,
|
|
2559
|
-
"--require-xlsx/--no-require-xlsx",
|
|
2560
|
-
help="Require <report-dir>/audit-checklist.xlsx to exist.",
|
|
2561
|
-
),
|
|
2562
|
-
emit_metadata: bool = typer.Option(
|
|
2563
|
-
True,
|
|
2564
|
-
"--emit-metadata/--no-emit-metadata",
|
|
2565
|
-
help="Write deterministic validation metadata to <report-dir>/audit-checklist.validation.json.",
|
|
2566
|
-
),
|
|
2567
|
-
) -> None:
|
|
2568
|
-
"""Validate canonical checklist artifacts (audit-checklist.json/xlsx)."""
|
|
2569
|
-
json_only = _cli_state.get("json_only", False)
|
|
2570
|
-
resolved_report_dir = report_dir.resolve()
|
|
2571
|
-
resolved_report_dir.mkdir(parents=True, exist_ok=True)
|
|
2572
|
-
resolved_checklist_json = (
|
|
2573
|
-
checklist_json.resolve() if checklist_json else (resolved_report_dir / "audit-checklist.json")
|
|
2574
|
-
)
|
|
2575
|
-
resolved_checklist_xlsx = (
|
|
2576
|
-
checklist_xlsx.resolve() if checklist_xlsx else (resolved_report_dir / "audit-checklist.xlsx")
|
|
2577
|
-
)
|
|
2578
|
-
|
|
2579
|
-
errors: list[str] = []
|
|
2580
|
-
warnings_list: list[str] = []
|
|
2581
|
-
checklist: AuditChecklist | None = None
|
|
2582
|
-
|
|
2583
|
-
if not resolved_checklist_json.exists():
|
|
2584
|
-
errors.append(f"Checklist JSON not found: {resolved_checklist_json}")
|
|
2585
|
-
else:
|
|
2586
|
-
try:
|
|
2587
|
-
checklist = AuditChecklist.model_validate_json(resolved_checklist_json.read_text(encoding="utf-8"))
|
|
2588
|
-
except (OSError, TypeError, ValueError) as exc:
|
|
2589
|
-
_log_hardened_path_error(
|
|
2590
|
-
"validate_checklist_json_parse_failed",
|
|
2591
|
-
exc,
|
|
2592
|
-
context=f"checklist_json={resolved_checklist_json}",
|
|
2593
|
-
recovery_action="append_validation_error_and_continue",
|
|
2594
|
-
)
|
|
2595
|
-
errors.append(f"Invalid checklist JSON schema/content: {exc}")
|
|
2596
|
-
|
|
2597
|
-
if checklist is not None and not checklist.validate_row_count():
|
|
2598
|
-
errors.append(
|
|
2599
|
-
f"Row count mismatch: template_row_count={checklist.template_row_count}, actual_rows={len(checklist.rows)}"
|
|
2600
|
-
)
|
|
2601
|
-
|
|
2602
|
-
if require_xlsx and not resolved_checklist_xlsx.exists():
|
|
2603
|
-
errors.append(f"Checklist XLSX not found: {resolved_checklist_xlsx}")
|
|
2604
|
-
elif not require_xlsx and not resolved_checklist_xlsx.exists():
|
|
2605
|
-
warnings_list.append(f"Checklist XLSX not found (optional): {resolved_checklist_xlsx}")
|
|
2606
|
-
elif resolved_checklist_xlsx.exists():
|
|
2607
|
-
from vds_audit_orchestrator.validators.checklist_validator import ChecklistValidator
|
|
2608
|
-
|
|
2609
|
-
xlsx_validation = ChecklistValidator().validate_excel(resolved_checklist_xlsx)
|
|
2610
|
-
if not xlsx_validation.valid:
|
|
2611
|
-
for item in xlsx_validation.errors:
|
|
2612
|
-
loc = f" row={item.row}" if item.row else ""
|
|
2613
|
-
col = f" column={item.column}" if item.column else ""
|
|
2614
|
-
errors.append(f"[{item.code}]{loc}{col} {item.message}")
|
|
2615
|
-
|
|
2616
|
-
payload: dict[str, Any] = {
|
|
2617
|
-
"valid": len(errors) == 0,
|
|
2618
|
-
"checklist_json": str(resolved_checklist_json),
|
|
2619
|
-
"checklist_xlsx": str(resolved_checklist_xlsx),
|
|
2620
|
-
"require_xlsx": require_xlsx,
|
|
2621
|
-
"errors": errors,
|
|
2622
|
-
"warnings": warnings_list,
|
|
2623
|
-
"validated_at": datetime.now(UTC).isoformat(),
|
|
2624
|
-
}
|
|
2625
|
-
if checklist is not None:
|
|
2626
|
-
payload["summary"] = {
|
|
2627
|
-
"template_name": checklist.template_name,
|
|
2628
|
-
"template_version": checklist.template_version,
|
|
2629
|
-
"template_row_count": checklist.template_row_count,
|
|
2630
|
-
"total_rows": checklist.total_rows,
|
|
2631
|
-
"overall_score": checklist.overall_score,
|
|
2632
|
-
"overall_score_1_5": checklist.overall_score_1_5,
|
|
2633
|
-
"status_counts": {
|
|
2634
|
-
"pass": checklist.pass_count,
|
|
2635
|
-
"partial": checklist.partial_count,
|
|
2636
|
-
"fail": checklist.fail_count,
|
|
2637
|
-
"na": checklist.na_count,
|
|
2638
|
-
"error": checklist.error_count,
|
|
2639
|
-
},
|
|
2640
|
-
"stability_flags": checklist.stability_flags,
|
|
2641
|
-
}
|
|
2642
|
-
if resolved_checklist_xlsx.exists():
|
|
2643
|
-
payload["xlsx_validated"] = True
|
|
2644
|
-
|
|
2645
|
-
if emit_metadata:
|
|
2646
|
-
metadata_path = resolved_report_dir / "audit-checklist.validation.json"
|
|
2647
|
-
metadata_path.write_text(json.dumps(payload, indent=2, ensure_ascii=False, default=str), encoding="utf-8")
|
|
2648
|
-
payload["metadata_path"] = str(metadata_path)
|
|
2649
|
-
|
|
2650
|
-
if json_only:
|
|
2651
|
-
_emit_json(payload)
|
|
2652
|
-
else:
|
|
2653
|
-
if payload["valid"]:
|
|
2654
|
-
console.print("[green]Checklist validation passed.[/green]")
|
|
2655
|
-
else:
|
|
2656
|
-
console.print("[red]Checklist validation failed.[/red]")
|
|
2657
|
-
if payload.get("errors"):
|
|
2658
|
-
for err in errors:
|
|
2659
|
-
console.print(f"[red]- {err}[/red]")
|
|
2660
|
-
if payload.get("warnings"):
|
|
2661
|
-
for warning in warnings_list:
|
|
2662
|
-
console.print(f"[yellow]- {warning}[/yellow]")
|
|
2663
|
-
|
|
2664
|
-
if errors:
|
|
2665
|
-
raise typer.Exit(1)
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
# ---------------------------------------------------------------------------
|
|
2669
|
-
# validate-spec-sync helpers
|
|
2670
|
-
# ---------------------------------------------------------------------------
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
def _has_required_spec_sync_files(specs_dir: Path) -> bool:
|
|
2674
|
-
candidate = specs_dir.resolve()
|
|
2675
|
-
has_separated = all((candidate / name).exists() for name in ("requirements.md", "design.md", "tasks.md"))
|
|
2676
|
-
has_unified = (candidate / "spec.md").exists()
|
|
2677
|
-
return candidate.exists() and (has_separated or has_unified)
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
def _resolve_validate_spec_sync_paths(specs_dir: Path, agents_file: Path) -> tuple[Path, Path]:
|
|
2681
|
-
"""Resolve spec + AGENTS paths with parent-directory auto-discovery for defaults."""
|
|
2682
|
-
default_specs_dir = Path(".kiro/specs/audit-orchestrator")
|
|
2683
|
-
default_agents_file = Path("AGENTS.md")
|
|
2684
|
-
cwd = Path.cwd().resolve()
|
|
2685
|
-
|
|
2686
|
-
if specs_dir == default_specs_dir:
|
|
2687
|
-
direct_specs_dir = (cwd / specs_dir).resolve()
|
|
2688
|
-
if _has_required_spec_sync_files(direct_specs_dir):
|
|
2689
|
-
resolved_specs_dir = direct_specs_dir
|
|
2690
|
-
else:
|
|
2691
|
-
resolved_specs_dir = direct_specs_dir
|
|
2692
|
-
for parent in (cwd, *cwd.parents):
|
|
2693
|
-
for root_name in (".gpt-5.4", ".kiro"):
|
|
2694
|
-
candidate = (parent / root_name / "specs" / "audit-orchestrator").resolve()
|
|
2695
|
-
if _has_required_spec_sync_files(candidate):
|
|
2696
|
-
resolved_specs_dir = candidate
|
|
2697
|
-
break
|
|
2698
|
-
if _has_required_spec_sync_files(resolved_specs_dir):
|
|
2699
|
-
break
|
|
2700
|
-
else:
|
|
2701
|
-
resolved_specs_dir = specs_dir.resolve()
|
|
2702
|
-
|
|
2703
|
-
# Always try direct resolution first, then parent-directory walk for known
|
|
2704
|
-
# agent-file names (AGENTS.md, CLAUDE.md, copilot-instructions.md).
|
|
2705
|
-
# This handles the current default (AGENTS.md) plus legacy explicit filenames
|
|
2706
|
-
# when CWD is a subdirectory of the repo root.
|
|
2707
|
-
_KNOWN_AGENTS_FILENAMES: tuple[str, ...] = ("AGENTS.md", "CLAUDE.md", ".github/copilot-instructions.md")
|
|
2708
|
-
direct_agents = (cwd / agents_file).resolve()
|
|
2709
|
-
if direct_agents.exists():
|
|
2710
|
-
resolved_agents_file = direct_agents
|
|
2711
|
-
else:
|
|
2712
|
-
resolved_agents_file = direct_agents
|
|
2713
|
-
# Walk parent directories looking for the file by its basename or known variants
|
|
2714
|
-
search_names: list[str] = [agents_file.name]
|
|
2715
|
-
if agents_file == default_agents_file:
|
|
2716
|
-
# Default mode: also try other known filenames
|
|
2717
|
-
search_names = list(dict.fromkeys([agents_file.name, *_KNOWN_AGENTS_FILENAMES]))
|
|
2718
|
-
for parent in (cwd, *cwd.parents):
|
|
2719
|
-
for filename in search_names:
|
|
2720
|
-
candidate = (parent / filename).resolve()
|
|
2721
|
-
if candidate.exists():
|
|
2722
|
-
resolved_agents_file = candidate
|
|
2723
|
-
break
|
|
2724
|
-
if resolved_agents_file.exists():
|
|
2725
|
-
break
|
|
2726
|
-
|
|
2727
|
-
return resolved_specs_dir, resolved_agents_file
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
# ---------------------------------------------------------------------------
|
|
2731
|
-
# validate-spec-sync
|
|
2732
|
-
# ---------------------------------------------------------------------------
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
@app.command(name="validate-spec-sync")
|
|
2736
|
-
def validate_spec_sync_command(
|
|
2737
|
-
specs_dir: Path = typer.Option(
|
|
2738
|
-
Path(".kiro/specs/audit-orchestrator"),
|
|
2739
|
-
"--specs-dir",
|
|
2740
|
-
help="Directory containing requirements.md, design.md, and tasks.md.",
|
|
2741
|
-
),
|
|
2742
|
-
agents_file: Path = typer.Option(
|
|
2743
|
-
Path("AGENTS.md"),
|
|
2744
|
-
"--agents-file",
|
|
2745
|
-
help="Path to AGENTS.md to validate against the spec files.",
|
|
2746
|
-
),
|
|
2747
|
-
) -> None:
|
|
2748
|
-
"""Validate spec sync across the triplet headers, baseline count, and agent instructions file."""
|
|
2749
|
-
resolved_specs_dir, resolved_agents = _resolve_validate_spec_sync_paths(specs_dir, agents_file)
|
|
2750
|
-
requirements_path = (resolved_specs_dir / "requirements.md").resolve()
|
|
2751
|
-
design_path = (resolved_specs_dir / "design.md").resolve()
|
|
2752
|
-
tasks_path = (resolved_specs_dir / "tasks.md").resolve()
|
|
2753
|
-
spec_path = (resolved_specs_dir / "spec.md").resolve()
|
|
2754
|
-
|
|
2755
|
-
if spec_path.exists():
|
|
2756
|
-
result = validate_spec_sync(
|
|
2757
|
-
spec_path=spec_path,
|
|
2758
|
-
agents_path=resolved_agents,
|
|
2759
|
-
)
|
|
2760
|
-
else:
|
|
2761
|
-
result = validate_spec_sync(
|
|
2762
|
-
requirements_path=requirements_path,
|
|
2763
|
-
design_path=design_path,
|
|
2764
|
-
tasks_path=tasks_path,
|
|
2765
|
-
agents_path=resolved_agents,
|
|
2766
|
-
)
|
|
2767
|
-
payload = result.to_payload()
|
|
2768
|
-
|
|
2769
|
-
if _cli_state.get("json_only"):
|
|
2770
|
-
_emit_json(payload)
|
|
2771
|
-
elif payload["valid"]:
|
|
2772
|
-
console.print("[green]Spec sync validation passed.[/green]")
|
|
2773
|
-
else:
|
|
2774
|
-
console.print("[red]Spec sync validation failed.[/red]")
|
|
2775
|
-
for error in payload["errors"]:
|
|
2776
|
-
console.print(f"[red]- {error}[/red]")
|
|
2777
|
-
|
|
2778
|
-
if not payload["valid"]:
|
|
2779
|
-
raise typer.Exit(1)
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
# ---------------------------------------------------------------------------
|
|
2783
|
-
# Template analysis helpers
|
|
2784
|
-
# ---------------------------------------------------------------------------
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
def _build_template_criteria(audit_template: Any) -> list[dict[str, Any]]:
|
|
2788
|
-
criteria: list[dict[str, Any]] = []
|
|
2789
|
-
for section in audit_template.sections:
|
|
2790
|
-
for check in section.checks:
|
|
2791
|
-
criteria.append(
|
|
2792
|
-
{
|
|
2793
|
-
"rule": check.id,
|
|
2794
|
-
"group": section.name,
|
|
2795
|
-
"check_text": check.description,
|
|
2796
|
-
"notes": check.remediation_steps or "",
|
|
2797
|
-
}
|
|
2798
|
-
)
|
|
2799
|
-
return criteria
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
def _expected_evidence_text(reqs: list[EvidenceRequirement]) -> str:
|
|
2803
|
-
parts = []
|
|
2804
|
-
for req in reqs:
|
|
2805
|
-
label = req.source
|
|
2806
|
-
if req.path_pattern:
|
|
2807
|
-
label = f"{label}:{req.path_pattern}"
|
|
2808
|
-
elif req.page_title_pattern:
|
|
2809
|
-
label = f"{label}:{req.page_title_pattern}"
|
|
2810
|
-
parts.append(label)
|
|
2811
|
-
return ", ".join(parts) if parts else "evidence required"
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
def _map_requirement_dict(req: dict[str, Any]) -> EvidenceRequirement:
|
|
2815
|
-
return EvidenceRequirement(
|
|
2816
|
-
source=req.get("source", "git_file"),
|
|
2817
|
-
path_pattern=req.get("path_pattern"),
|
|
2818
|
-
page_title_pattern=req.get("page_title_pattern"),
|
|
2819
|
-
required_sections=req.get("required_sections", []),
|
|
2820
|
-
required_keywords=[],
|
|
2821
|
-
min_content_length=0,
|
|
2822
|
-
max_age_days=None,
|
|
2823
|
-
weight=1.0,
|
|
2824
|
-
critical=bool(req.get("critical", False)),
|
|
2825
|
-
)
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
def _matches_requirement(req: EvidenceRequirement, evidence: Evidence) -> bool:
|
|
2829
|
-
if req.source and evidence.source != req.source:
|
|
2830
|
-
return False
|
|
2831
|
-
if req.path_pattern:
|
|
2832
|
-
return fnmatch(evidence.source_id, req.path_pattern)
|
|
2833
|
-
if req.page_title_pattern:
|
|
2834
|
-
needle = req.page_title_pattern.lower()
|
|
2835
|
-
return needle in (evidence.title or "").lower() or needle in (evidence.source_id or "").lower()
|
|
2836
|
-
return True
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
def _analysis_results_to_requirements(
|
|
2840
|
-
analysis_results: list[dict[str, Any]],
|
|
2841
|
-
) -> tuple[dict[str, list[EvidenceRequirement]], list[dict[str, Any]], list[EvidenceRequirement]]:
|
|
2842
|
-
per_check_requirements: dict[str, list[EvidenceRequirement]] = {}
|
|
2843
|
-
checks: list[dict[str, Any]] = []
|
|
2844
|
-
all_requirements: list[EvidenceRequirement] = []
|
|
2845
|
-
|
|
2846
|
-
for item in analysis_results:
|
|
2847
|
-
if not isinstance(item, dict):
|
|
2848
|
-
continue
|
|
2849
|
-
check_id = item.get("rule") or item.get("check_id") or ""
|
|
2850
|
-
if not check_id:
|
|
2851
|
-
continue
|
|
2852
|
-
reqs = [_map_requirement_dict(req) for req in item.get("evidence_requirements", []) if isinstance(req, dict)]
|
|
2853
|
-
per_check_requirements[check_id] = reqs
|
|
2854
|
-
all_requirements.extend(reqs)
|
|
2855
|
-
checks.append(
|
|
2856
|
-
{
|
|
2857
|
-
"id": check_id,
|
|
2858
|
-
"criterion": item.get("check_text", ""),
|
|
2859
|
-
"expected_evidence": _expected_evidence_text(reqs),
|
|
2860
|
-
}
|
|
2861
|
-
)
|
|
2862
|
-
|
|
2863
|
-
return per_check_requirements, checks, all_requirements
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
def _analysis_results_to_check_configs(
|
|
2867
|
-
analysis_results: list[dict[str, Any]],
|
|
2868
|
-
repo_name: str | None = None,
|
|
2869
|
-
) -> list[dict[str, Any]]:
|
|
2870
|
-
def _first_requirement(reqs: list[dict[str, Any]], source: str) -> dict[str, Any] | None:
|
|
2871
|
-
for req in reqs:
|
|
2872
|
-
if isinstance(req, dict) and req.get("source") == source:
|
|
2873
|
-
return req
|
|
2874
|
-
return None
|
|
2875
|
-
|
|
2876
|
-
configs: list[dict[str, Any]] = []
|
|
2877
|
-
for item in analysis_results:
|
|
2878
|
-
if not isinstance(item, dict):
|
|
2879
|
-
continue
|
|
2880
|
-
check_id = item.get("rule") or item.get("check_id") or ""
|
|
2881
|
-
if not check_id:
|
|
2882
|
-
continue
|
|
2883
|
-
check_type = item.get("check_type") or "llm_content_evaluation"
|
|
2884
|
-
reqs = item.get("evidence_requirements", [])
|
|
2885
|
-
if not isinstance(reqs, list):
|
|
2886
|
-
reqs = []
|
|
2887
|
-
|
|
2888
|
-
check_config: dict[str, Any] = {}
|
|
2889
|
-
if check_type in {"git_file_exists", "git_file_content"}:
|
|
2890
|
-
req = _first_requirement(reqs, "git_file")
|
|
2891
|
-
if req:
|
|
2892
|
-
path_pattern = req.get("path_pattern")
|
|
2893
|
-
if path_pattern:
|
|
2894
|
-
check_config["path"] = path_pattern
|
|
2895
|
-
if check_type == "git_file_content":
|
|
2896
|
-
sections = req.get("required_sections") or []
|
|
2897
|
-
if sections:
|
|
2898
|
-
check_config["contains"] = sections[0]
|
|
2899
|
-
elif check_type == "confluence_page_exists":
|
|
2900
|
-
req = _first_requirement(reqs, "confluence_page")
|
|
2901
|
-
if req:
|
|
2902
|
-
title = req.get("page_title_pattern") or req.get("path_pattern")
|
|
2903
|
-
if title:
|
|
2904
|
-
check_config["title"] = title
|
|
2905
|
-
elif check_type.startswith("sonarqube") and repo_name:
|
|
2906
|
-
check_config["project_key"] = repo_name
|
|
2907
|
-
|
|
2908
|
-
configs.append(
|
|
2909
|
-
{
|
|
2910
|
-
"check_id": check_id,
|
|
2911
|
-
"check_type": check_type,
|
|
2912
|
-
"check_config": check_config,
|
|
2913
|
-
"applicability": item.get("applicability"),
|
|
2914
|
-
"gap_severity_if_missing": item.get("gap_severity_if_missing"),
|
|
2915
|
-
"gap_category": item.get("gap_category"),
|
|
2916
|
-
"evidence_requirements": reqs,
|
|
2917
|
-
"notes": item.get("notes") or "",
|
|
2918
|
-
"check_text": item.get("check_text") or "",
|
|
2919
|
-
}
|
|
2920
|
-
)
|
|
2921
|
-
return configs
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
# ---------------------------------------------------------------------------
|
|
2925
|
-
# analyze-template
|
|
2926
|
-
# ---------------------------------------------------------------------------
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
@app.command(name="analyze-template")
|
|
2930
|
-
def analyze_template(
|
|
2931
|
-
template: Path | None = typer.Option(None, "--template", "-t", help="Path to Excel template", exists=True),
|
|
2932
|
-
template_url: str | None = typer.Option(
|
|
2933
|
-
None,
|
|
2934
|
-
"--template-url",
|
|
2935
|
-
"--google-sheet",
|
|
2936
|
-
"-g",
|
|
2937
|
-
help="Google Sheet or Doc URL for rubric-style template",
|
|
2938
|
-
),
|
|
2939
|
-
template_sheet: str | None = typer.Option(
|
|
2940
|
-
None,
|
|
2941
|
-
"--template-sheet",
|
|
2942
|
-
"--sheet-name",
|
|
2943
|
-
help="Sheet name in Google Sheet",
|
|
2944
|
-
),
|
|
2945
|
-
template_gid: int | None = typer.Option(
|
|
2946
|
-
None,
|
|
2947
|
-
"--template-gid",
|
|
2948
|
-
"--sheet-gid",
|
|
2949
|
-
help="Sheet GID in Google Sheet",
|
|
2950
|
-
),
|
|
2951
|
-
template_table_index: int | None = typer.Option(
|
|
2952
|
-
None,
|
|
2953
|
-
"--template-table-index",
|
|
2954
|
-
help="Table index for Google Docs templates (0-based)",
|
|
2955
|
-
),
|
|
2956
|
-
template_map: Path | None = typer.Option(
|
|
2957
|
-
None,
|
|
2958
|
-
"--template-map",
|
|
2959
|
-
help="Optional JSON mapping file for rubric-style sheets",
|
|
2960
|
-
exists=True,
|
|
2961
|
-
),
|
|
2962
|
-
project_profile: Path | None = typer.Option(None, "--project-profile", help="Project profile file path"),
|
|
2963
|
-
profile_map: Path | None = typer.Option(None, "--profile-map", help="Profile map rules file path"),
|
|
2964
|
-
repo_path: Path | None = typer.Option(
|
|
2965
|
-
None,
|
|
2966
|
-
"--repo-path",
|
|
2967
|
-
help="Repository path for profile detection (defaults to CWD)",
|
|
2968
|
-
),
|
|
2969
|
-
llm_enabled: bool | None = typer.Option(
|
|
2970
|
-
None,
|
|
2971
|
-
"--llm/--no-llm",
|
|
2972
|
-
help="Enable or disable LLM analysis for template extraction",
|
|
2973
|
-
),
|
|
2974
|
-
llm_model: str | None = typer.Option(None, "--llm-model", help="Override LLM model name"),
|
|
2975
|
-
output: Path = typer.Option(
|
|
2976
|
-
Path("template_analysis.json"),
|
|
2977
|
-
"--output",
|
|
2978
|
-
"-o",
|
|
2979
|
-
help="Output path for template analysis JSON",
|
|
2980
|
-
),
|
|
2981
|
-
check_config_output: Path | None = typer.Option(
|
|
2982
|
-
None,
|
|
2983
|
-
"--check-config-output",
|
|
2984
|
-
help="Optional output path for generated check config JSON",
|
|
2985
|
-
),
|
|
2986
|
-
gs_auth_method: str | None = typer.Option(None, "--gs-auth-method", help="Google Sheets auth method"),
|
|
2987
|
-
gs_credentials_path: Path | None = typer.Option(
|
|
2988
|
-
None, "--gs-credentials-path", help="Google Sheets service account credentials path"
|
|
2989
|
-
),
|
|
2990
|
-
gs_oauth_credentials_path: Path | None = typer.Option(
|
|
2991
|
-
None, "--gs-oauth-credentials-path", help="Google Sheets OAuth credentials path"
|
|
2992
|
-
),
|
|
2993
|
-
gs_api_key: str | None = typer.Option(None, "--gs-api-key", help="Google Sheets API key"),
|
|
2994
|
-
) -> None:
|
|
2995
|
-
"""Analyze a template and extract evidence requirements."""
|
|
2996
|
-
try:
|
|
2997
|
-
audit_template = _load_template(
|
|
2998
|
-
template=template,
|
|
2999
|
-
template_url=template_url,
|
|
3000
|
-
template_sheet=template_sheet,
|
|
3001
|
-
template_gid=template_gid,
|
|
3002
|
-
template_table_index=template_table_index,
|
|
3003
|
-
template_map=str(template_map) if template_map else None,
|
|
3004
|
-
gs_auth_method=gs_auth_method,
|
|
3005
|
-
gs_credentials_path=gs_credentials_path,
|
|
3006
|
-
gs_oauth_credentials_path=gs_oauth_credentials_path,
|
|
3007
|
-
gs_api_key=gs_api_key,
|
|
3008
|
-
)
|
|
3009
|
-
except (OSError, RuntimeError, TypeError, ValueError) as exc:
|
|
3010
|
-
_log_hardened_path_error(
|
|
3011
|
-
"analyze_template_load_failed",
|
|
3012
|
-
exc,
|
|
3013
|
-
context=f"template={template}, template_url={template_url}",
|
|
3014
|
-
recovery_action="emit_cli_error_and_exit",
|
|
3015
|
-
level="exception",
|
|
3016
|
-
)
|
|
3017
|
-
message = f"Failed to load template: {exc}"
|
|
3018
|
-
if _cli_state.get("json_only"):
|
|
3019
|
-
_emit_json({"error": message})
|
|
3020
|
-
else:
|
|
3021
|
-
console.print(f"[red]{message}[/red]")
|
|
3022
|
-
raise typer.Exit(1) from exc
|
|
3023
|
-
|
|
3024
|
-
repo_root = repo_path.resolve() if repo_path else Path.cwd()
|
|
3025
|
-
if project_profile:
|
|
3026
|
-
profile_resolution = resolve_profile(
|
|
3027
|
-
repo_path=repo_root,
|
|
3028
|
-
project_id=repo_root.name,
|
|
3029
|
-
cli_profile=project_profile,
|
|
3030
|
-
profile_map_path=profile_map,
|
|
3031
|
-
)
|
|
3032
|
-
profile_obj = profile_resolution.profile
|
|
3033
|
-
else:
|
|
3034
|
-
profile_obj = default_profile(project_id=repo_root.name, name=repo_root.name)
|
|
3035
|
-
|
|
3036
|
-
provider = LLMProvider()
|
|
3037
|
-
use_llm = provider.enabled if llm_enabled is None else llm_enabled
|
|
3038
|
-
llm_client = None
|
|
3039
|
-
model_name = llm_model
|
|
3040
|
-
if use_llm:
|
|
3041
|
-
try:
|
|
3042
|
-
llm_client = provider.get_async_client()
|
|
3043
|
-
if not model_name:
|
|
3044
|
-
model_name = provider.get_model("complex")
|
|
3045
|
-
except (OSError, RuntimeError, TypeError, ValueError, LookupError, ImportError) as exc:
|
|
3046
|
-
_log_hardened_path_error(
|
|
3047
|
-
"template_llm_unavailable",
|
|
3048
|
-
exc,
|
|
3049
|
-
context=f"llm_enabled={use_llm}, model_name={model_name}",
|
|
3050
|
-
recovery_action="continue_without_template_llm",
|
|
3051
|
-
)
|
|
3052
|
-
|
|
3053
|
-
criteria = _build_template_criteria(audit_template)
|
|
3054
|
-
|
|
3055
|
-
analyzer = TemplateAnalyzer(llm_client=llm_client, model=model_name)
|
|
3056
|
-
try:
|
|
3057
|
-
results = asyncio.run(analyzer.analyze_template(criteria, profile_obj))
|
|
3058
|
-
except (OSError, RuntimeError, TypeError, ValueError) as exc:
|
|
3059
|
-
_log_hardened_path_error(
|
|
3060
|
-
"analyze_template_execution_failed",
|
|
3061
|
-
exc,
|
|
3062
|
-
context=f"repo_path={repo_root}",
|
|
3063
|
-
recovery_action="emit_cli_error_and_exit",
|
|
3064
|
-
level="exception",
|
|
3065
|
-
)
|
|
3066
|
-
message = f"Template analysis failed: {exc}"
|
|
3067
|
-
if _cli_state.get("json_only"):
|
|
3068
|
-
_emit_json({"error": message})
|
|
3069
|
-
else:
|
|
3070
|
-
console.print(f"[red]{message}[/red]")
|
|
3071
|
-
raise typer.Exit(1) from exc
|
|
3072
|
-
|
|
3073
|
-
payload = {
|
|
3074
|
-
"template": {
|
|
3075
|
-
"name": getattr(audit_template, "name", None),
|
|
3076
|
-
"version": getattr(audit_template, "version", None),
|
|
3077
|
-
},
|
|
3078
|
-
"profile": profile_obj.to_dict() if isinstance(profile_obj, ProjectProfile) else profile_obj,
|
|
3079
|
-
"criteria_count": len(results),
|
|
3080
|
-
"results": [r.model_dump() for r in results],
|
|
3081
|
-
}
|
|
3082
|
-
output_path = output.resolve()
|
|
3083
|
-
output_path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
|
|
3084
|
-
check_config_path: Path | None = None
|
|
3085
|
-
if check_config_output is not None:
|
|
3086
|
-
check_configs = _analysis_results_to_check_configs([r.model_dump() for r in results], repo_root.name)
|
|
3087
|
-
check_config_payload = {
|
|
3088
|
-
"template": payload["template"],
|
|
3089
|
-
"profile": payload["profile"],
|
|
3090
|
-
"criteria_count": payload["criteria_count"],
|
|
3091
|
-
"check_configs": check_configs,
|
|
3092
|
-
}
|
|
3093
|
-
check_config_path = check_config_output.resolve()
|
|
3094
|
-
check_config_path.write_text(
|
|
3095
|
-
json.dumps(check_config_payload, indent=2, sort_keys=True),
|
|
3096
|
-
encoding="utf-8",
|
|
3097
|
-
)
|
|
3098
|
-
|
|
3099
|
-
if _cli_state.get("json_only"):
|
|
3100
|
-
_emit_json(payload)
|
|
3101
|
-
return
|
|
3102
|
-
|
|
3103
|
-
console.print(f"[green]Template analysis written to:[/green] {output_path}")
|
|
3104
|
-
if check_config_path is not None:
|
|
3105
|
-
console.print(f"[green]Check config output written to:[/green] {check_config_path}")
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
# ---------------------------------------------------------------------------
|
|
3109
|
-
# analyze-gaps
|
|
3110
|
-
# ---------------------------------------------------------------------------
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
@app.command(name="analyze-gaps")
|
|
3114
|
-
def analyze_gaps(
|
|
3115
|
-
repo_path: Path = typer.Argument(..., help="Repository path to analyze"),
|
|
3116
|
-
template_analysis: Path | None = typer.Option(
|
|
3117
|
-
None,
|
|
3118
|
-
"--template-analysis",
|
|
3119
|
-
help="Path to template analysis JSON (from analyze-template)",
|
|
3120
|
-
exists=True,
|
|
3121
|
-
),
|
|
3122
|
-
template: Path | None = typer.Option(None, "--template", "-t", help="Path to Excel template", exists=True),
|
|
3123
|
-
template_url: str | None = typer.Option(
|
|
3124
|
-
None,
|
|
3125
|
-
"--template-url",
|
|
3126
|
-
"--google-sheet",
|
|
3127
|
-
"-g",
|
|
3128
|
-
help="Google Sheet or Doc URL for rubric-style template",
|
|
3129
|
-
),
|
|
3130
|
-
template_sheet: str | None = typer.Option(
|
|
3131
|
-
None,
|
|
3132
|
-
"--template-sheet",
|
|
3133
|
-
"--sheet-name",
|
|
3134
|
-
help="Sheet name in Google Sheet",
|
|
3135
|
-
),
|
|
3136
|
-
template_gid: int | None = typer.Option(
|
|
3137
|
-
None,
|
|
3138
|
-
"--template-gid",
|
|
3139
|
-
"--sheet-gid",
|
|
3140
|
-
help="Sheet GID in Google Sheet",
|
|
3141
|
-
),
|
|
3142
|
-
template_table_index: int | None = typer.Option(
|
|
3143
|
-
None,
|
|
3144
|
-
"--template-table-index",
|
|
3145
|
-
help="Table index for Google Docs templates (0-based)",
|
|
3146
|
-
),
|
|
3147
|
-
template_map: Path | None = typer.Option(
|
|
3148
|
-
None,
|
|
3149
|
-
"--template-map",
|
|
3150
|
-
help="Optional JSON mapping file for rubric-style sheets",
|
|
3151
|
-
exists=True,
|
|
3152
|
-
),
|
|
3153
|
-
project_profile: Path | None = typer.Option(None, "--project-profile", help="Project profile file path"),
|
|
3154
|
-
profile_map: Path | None = typer.Option(None, "--profile-map", help="Profile map rules file path"),
|
|
3155
|
-
llm_enabled: bool | None = typer.Option(
|
|
3156
|
-
None,
|
|
3157
|
-
"--llm/--no-llm",
|
|
3158
|
-
help="Enable or disable LLM analysis for gap evaluation",
|
|
3159
|
-
),
|
|
3160
|
-
llm_model: str | None = typer.Option(None, "--llm-model", help="Override LLM model name"),
|
|
3161
|
-
output: Path = typer.Option(
|
|
3162
|
-
Path("gap_analysis.json"),
|
|
3163
|
-
"--output",
|
|
3164
|
-
"-o",
|
|
3165
|
-
help="Output path for gap analysis JSON",
|
|
3166
|
-
),
|
|
3167
|
-
gs_auth_method: str | None = typer.Option(None, "--gs-auth-method", help="Google Sheets auth method"),
|
|
3168
|
-
gs_credentials_path: Path | None = typer.Option(
|
|
3169
|
-
None, "--gs-credentials-path", help="Google Sheets service account credentials path"
|
|
3170
|
-
),
|
|
3171
|
-
gs_oauth_credentials_path: Path | None = typer.Option(
|
|
3172
|
-
None, "--gs-oauth-credentials-path", help="Google Sheets OAuth credentials path"
|
|
3173
|
-
),
|
|
3174
|
-
gs_api_key: str | None = typer.Option(None, "--gs-api-key", help="Google Sheets API key"),
|
|
3175
|
-
) -> None:
|
|
3176
|
-
"""Analyze gaps for a repository using template analysis + evidence collection."""
|
|
3177
|
-
repo_root = repo_path.resolve()
|
|
3178
|
-
if project_profile:
|
|
3179
|
-
profile_resolution = resolve_profile(
|
|
3180
|
-
repo_path=repo_root,
|
|
3181
|
-
project_id=repo_root.name,
|
|
3182
|
-
cli_profile=project_profile,
|
|
3183
|
-
profile_map_path=profile_map,
|
|
3184
|
-
)
|
|
3185
|
-
profile_obj = profile_resolution.profile
|
|
3186
|
-
else:
|
|
3187
|
-
profile_obj = default_profile(project_id=repo_root.name, name=repo_root.name)
|
|
3188
|
-
|
|
3189
|
-
analysis_payload: dict[str, Any] = {}
|
|
3190
|
-
if template_analysis:
|
|
3191
|
-
analysis_payload = json.loads(template_analysis.read_text(encoding="utf-8"))
|
|
3192
|
-
else:
|
|
3193
|
-
if not template and not template_url:
|
|
3194
|
-
message = "Provide --template-analysis or a template source (--template/--template-url)."
|
|
3195
|
-
if _cli_state.get("json_only"):
|
|
3196
|
-
_emit_json({"error": message})
|
|
3197
|
-
else:
|
|
3198
|
-
console.print(f"[red]{message}[/red]")
|
|
3199
|
-
raise typer.Exit(1)
|
|
3200
|
-
|
|
3201
|
-
audit_template = _load_template(
|
|
3202
|
-
template=template,
|
|
3203
|
-
template_url=template_url,
|
|
3204
|
-
template_sheet=template_sheet,
|
|
3205
|
-
template_gid=template_gid,
|
|
3206
|
-
template_table_index=template_table_index,
|
|
3207
|
-
template_map=str(template_map) if template_map else None,
|
|
3208
|
-
gs_auth_method=gs_auth_method,
|
|
3209
|
-
gs_credentials_path=gs_credentials_path,
|
|
3210
|
-
gs_oauth_credentials_path=gs_oauth_credentials_path,
|
|
3211
|
-
gs_api_key=gs_api_key,
|
|
3212
|
-
)
|
|
3213
|
-
|
|
3214
|
-
provider = LLMProvider()
|
|
3215
|
-
use_llm = provider.enabled if llm_enabled is None else llm_enabled
|
|
3216
|
-
llm_client = None
|
|
3217
|
-
model_name = llm_model
|
|
3218
|
-
if use_llm:
|
|
3219
|
-
try:
|
|
3220
|
-
llm_client = provider.get_client()
|
|
3221
|
-
if not model_name:
|
|
3222
|
-
model_name = provider.get_model("complex")
|
|
3223
|
-
except (OSError, RuntimeError, TypeError, ValueError, LookupError, ImportError) as exc:
|
|
3224
|
-
_log_hardened_path_error(
|
|
3225
|
-
"gap_llm_unavailable",
|
|
3226
|
-
exc,
|
|
3227
|
-
context=f"llm_enabled={use_llm}, model_name={model_name}",
|
|
3228
|
-
recovery_action="continue_without_gap_llm",
|
|
3229
|
-
)
|
|
3230
|
-
|
|
3231
|
-
criteria = _build_template_criteria(audit_template)
|
|
3232
|
-
|
|
3233
|
-
analyzer = TemplateAnalyzer(llm_client=llm_client, model=model_name)
|
|
3234
|
-
results = asyncio.run(analyzer.analyze_template(criteria, profile_obj))
|
|
3235
|
-
analysis_payload = {
|
|
3236
|
-
"template": {
|
|
3237
|
-
"name": getattr(audit_template, "name", None),
|
|
3238
|
-
"version": getattr(audit_template, "version", None),
|
|
3239
|
-
},
|
|
3240
|
-
"profile": profile_obj.to_dict() if isinstance(profile_obj, ProjectProfile) else profile_obj,
|
|
3241
|
-
"criteria_count": len(results),
|
|
3242
|
-
"results": [r.model_dump() for r in results],
|
|
3243
|
-
}
|
|
3244
|
-
|
|
3245
|
-
analysis_results = analysis_payload.get("results", [])
|
|
3246
|
-
if not isinstance(analysis_results, list) or not analysis_results:
|
|
3247
|
-
message = "Template analysis results are missing or empty."
|
|
3248
|
-
if _cli_state.get("json_only"):
|
|
3249
|
-
_emit_json({"error": message})
|
|
3250
|
-
else:
|
|
3251
|
-
console.print(f"[red]{message}[/red]")
|
|
3252
|
-
raise typer.Exit(1)
|
|
3253
|
-
|
|
3254
|
-
per_check_requirements, checks, all_requirements = _analysis_results_to_requirements(analysis_results)
|
|
3255
|
-
|
|
3256
|
-
orchestrator = EvidenceOrchestrator(repo_path=repo_root)
|
|
3257
|
-
bundle = asyncio.run(orchestrator.collect_bundle(all_requirements))
|
|
3258
|
-
evidence_map: dict[str, list[Evidence]] = {}
|
|
3259
|
-
for check in checks:
|
|
3260
|
-
check_id = check.get("id", "")
|
|
3261
|
-
reqs = per_check_requirements.get(check_id, [])
|
|
3262
|
-
matched: list[Evidence] = []
|
|
3263
|
-
for evidence in bundle.all_evidence:
|
|
3264
|
-
if any(_matches_requirement(req, evidence) for req in reqs):
|
|
3265
|
-
matched.append(evidence)
|
|
3266
|
-
evidence_map[check_id] = matched
|
|
3267
|
-
|
|
3268
|
-
provider = LLMProvider()
|
|
3269
|
-
use_llm = provider.enabled if llm_enabled is None else llm_enabled
|
|
3270
|
-
gap_llm_client: Any | None = None
|
|
3271
|
-
model_name = llm_model
|
|
3272
|
-
if use_llm:
|
|
3273
|
-
try:
|
|
3274
|
-
gap_llm_client = provider.get_async_client()
|
|
3275
|
-
if not model_name:
|
|
3276
|
-
model_name = provider.get_model("complex")
|
|
3277
|
-
except (OSError, RuntimeError, TypeError, ValueError, LookupError, ImportError) as exc:
|
|
3278
|
-
_log_hardened_path_error(
|
|
3279
|
-
"gap_llm_unavailable",
|
|
3280
|
-
exc,
|
|
3281
|
-
context=f"llm_enabled={use_llm}, model_name={model_name}",
|
|
3282
|
-
recovery_action="continue_without_gap_llm",
|
|
3283
|
-
)
|
|
3284
|
-
|
|
3285
|
-
gap_analyzer = GapAnalyzer(llm_client=gap_llm_client, model=model_name)
|
|
3286
|
-
gap_results_raw = asyncio.run(gap_analyzer.analyze_batch(checks, evidence_map, profile_obj))
|
|
3287
|
-
gap_results = [r for r in gap_results_raw if not isinstance(r, dict)]
|
|
3288
|
-
gaps = [gap for result in gap_results for gap in result.gaps]
|
|
3289
|
-
aligned = [practice for result in gap_results for practice in result.aligned_practices]
|
|
3290
|
-
avg_score = sum(result.score for result in gap_results) / len(gap_results) if gap_results else 0.0
|
|
3291
|
-
overall_score = avg_score * 10.0
|
|
3292
|
-
maturity = MaturityLevel.from_score(avg_score).value
|
|
3293
|
-
|
|
3294
|
-
# Convert LLM findings to domain models
|
|
3295
|
-
from vds_audit_orchestrator.models.gaps import (
|
|
3296
|
-
AlignedPractice,
|
|
3297
|
-
BestPracticeLevel,
|
|
3298
|
-
Gap,
|
|
3299
|
-
GapCategory,
|
|
3300
|
-
GapSeverity,
|
|
3301
|
-
)
|
|
3302
|
-
|
|
3303
|
-
domain_gaps: list[Gap] = []
|
|
3304
|
-
for gap in gaps:
|
|
3305
|
-
# Map string severity/category to Enums with fallback
|
|
3306
|
-
try:
|
|
3307
|
-
sev = GapSeverity(gap.severity.capitalize())
|
|
3308
|
-
except ValueError:
|
|
3309
|
-
sev = GapSeverity.MEDIUM
|
|
3310
|
-
|
|
3311
|
-
try:
|
|
3312
|
-
cat = GapCategory(gap.category.capitalize())
|
|
3313
|
-
except ValueError:
|
|
3314
|
-
cat = GapCategory.COMPLIANCE
|
|
3315
|
-
|
|
3316
|
-
domain_gaps.append(
|
|
3317
|
-
Gap(
|
|
3318
|
-
check_id="llm_gap_analysis",
|
|
3319
|
-
criterion="Repo-level Gap Analysis",
|
|
3320
|
-
description=gap.description,
|
|
3321
|
-
severity=sev,
|
|
3322
|
-
category=cat,
|
|
3323
|
-
expected_evidence="Gap analysis reasoning",
|
|
3324
|
-
actual_status="Identified by LLM",
|
|
3325
|
-
impact_description=f"Gap in {gap.category} affecting {gap.severity} severity",
|
|
3326
|
-
recommendation=gap.recommendation,
|
|
3327
|
-
remediation_effort=gap.remediation_effort,
|
|
3328
|
-
remediation_steps=gap.remediation_steps,
|
|
3329
|
-
)
|
|
3330
|
-
)
|
|
3331
|
-
|
|
3332
|
-
domain_aligned: list[AlignedPractice] = []
|
|
3333
|
-
for practice in aligned:
|
|
3334
|
-
try:
|
|
3335
|
-
level = BestPracticeLevel(practice.best_practice_level.capitalize())
|
|
3336
|
-
except ValueError:
|
|
3337
|
-
level = BestPracticeLevel.GOOD
|
|
3338
|
-
|
|
3339
|
-
domain_aligned.append(
|
|
3340
|
-
AlignedPractice(
|
|
3341
|
-
check_id="llm_gap_analysis",
|
|
3342
|
-
criterion="Repo-level Gap Analysis",
|
|
3343
|
-
description=practice.description,
|
|
3344
|
-
best_practice_level=level,
|
|
3345
|
-
notable_aspects=practice.notable_aspects,
|
|
3346
|
-
)
|
|
3347
|
-
)
|
|
3348
|
-
|
|
3349
|
-
repo_gap = RepoGapAnalysisResult(
|
|
3350
|
-
repository=repo_root.name,
|
|
3351
|
-
gaps=domain_gaps,
|
|
3352
|
-
aligned_practices=domain_aligned,
|
|
3353
|
-
overall_score=overall_score,
|
|
3354
|
-
maturity_level=maturity,
|
|
3355
|
-
)
|
|
3356
|
-
payload = repo_gap.model_dump(mode="json")
|
|
3357
|
-
output_path = output.resolve()
|
|
3358
|
-
output_path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
|
|
3359
|
-
|
|
3360
|
-
if _cli_state.get("json_only"):
|
|
3361
|
-
_emit_json(payload)
|
|
3362
|
-
return
|
|
3363
|
-
|
|
3364
|
-
console.print(f"[green]Gap analysis written to:[/green] {output_path}")
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
# ---------------------------------------------------------------------------
|
|
3368
|
-
# export-template
|
|
3369
|
-
# ---------------------------------------------------------------------------
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
@app.command(name="export-template")
|
|
3373
|
-
def export_template(
|
|
3374
|
-
template_url: str | None = typer.Option(
|
|
3375
|
-
None,
|
|
3376
|
-
"--template-url",
|
|
3377
|
-
"--google-sheet",
|
|
3378
|
-
"-g",
|
|
3379
|
-
help="Google Sheet or Doc URL for rubric-style template",
|
|
3380
|
-
),
|
|
3381
|
-
template_sheet: str | None = typer.Option(
|
|
3382
|
-
None,
|
|
3383
|
-
"--template-sheet",
|
|
3384
|
-
"--sheet-name",
|
|
3385
|
-
help="Sheet name in Google Sheet",
|
|
3386
|
-
),
|
|
3387
|
-
template_gid: int | None = typer.Option(
|
|
3388
|
-
None,
|
|
3389
|
-
"--template-gid",
|
|
3390
|
-
"--sheet-gid",
|
|
3391
|
-
help="Sheet GID in Google Sheet",
|
|
3392
|
-
),
|
|
3393
|
-
template_table_index: int | None = typer.Option(
|
|
3394
|
-
None,
|
|
3395
|
-
"--template-table-index",
|
|
3396
|
-
help="Table index for Google Docs templates (0-based)",
|
|
3397
|
-
),
|
|
3398
|
-
template_map: Path | None = typer.Option(
|
|
3399
|
-
None,
|
|
3400
|
-
"--template-map",
|
|
3401
|
-
help="Path to mapping JSON for rubric-style template",
|
|
3402
|
-
exists=True,
|
|
3403
|
-
),
|
|
3404
|
-
output: Path = typer.Option(
|
|
3405
|
-
Path("audit_template.xlsx"),
|
|
3406
|
-
"--output",
|
|
3407
|
-
"-o",
|
|
3408
|
-
help="Output Excel path for the exported template",
|
|
3409
|
-
),
|
|
3410
|
-
gs_auth_method: str | None = typer.Option(None, "--gs-auth-method", help="Google Sheets auth method"),
|
|
3411
|
-
gs_credentials_path: Path | None = typer.Option(
|
|
3412
|
-
None, "--gs-credentials-path", help="Google Sheets credentials path"
|
|
3413
|
-
),
|
|
3414
|
-
gs_oauth_credentials_path: Path | None = typer.Option(
|
|
3415
|
-
None, "--gs-oauth-credentials-path", help="Google Sheets OAuth credentials path"
|
|
3416
|
-
),
|
|
3417
|
-
gs_api_key: str | None = typer.Option(None, "--gs-api-key", help="Google Sheets API key"),
|
|
3418
|
-
) -> None:
|
|
3419
|
-
"""Export a Google Sheet or Doc template to an Excel workbook."""
|
|
3420
|
-
if not template_url:
|
|
3421
|
-
message = "Must provide --template-url to export a Google template"
|
|
3422
|
-
if _cli_state.get("json_only"):
|
|
3423
|
-
_emit_json({"error": message})
|
|
3424
|
-
else:
|
|
3425
|
-
console.print(f"[red]{message}[/red]")
|
|
3426
|
-
raise typer.Exit(1)
|
|
3427
|
-
|
|
3428
|
-
try:
|
|
3429
|
-
mapping_path = resolve_mapping_path(template_map) if template_map else None
|
|
3430
|
-
mapping = load_mapping_file(mapping_path) if mapping_path else None
|
|
3431
|
-
export_template_from_google_source(
|
|
3432
|
-
output_path=output.resolve(),
|
|
3433
|
-
template_url=template_url,
|
|
3434
|
-
sheet_name=template_sheet,
|
|
3435
|
-
sheet_gid=template_gid,
|
|
3436
|
-
doc_table_index=template_table_index,
|
|
3437
|
-
template_map=mapping,
|
|
3438
|
-
auth_method=gs_auth_method,
|
|
3439
|
-
credentials_path=gs_credentials_path,
|
|
3440
|
-
oauth_credentials_path=gs_oauth_credentials_path,
|
|
3441
|
-
api_key=gs_api_key,
|
|
3442
|
-
)
|
|
3443
|
-
except (OSError, RuntimeError, TypeError, ValueError) as exc:
|
|
3444
|
-
_log_hardened_path_error(
|
|
3445
|
-
"export_template_failed",
|
|
3446
|
-
exc,
|
|
3447
|
-
context=f"template_url={template_url}, output={output}",
|
|
3448
|
-
recovery_action="emit_cli_error_and_exit",
|
|
3449
|
-
level="exception",
|
|
3450
|
-
)
|
|
3451
|
-
message = f"Failed to export template: {exc}"
|
|
3452
|
-
if _cli_state.get("json_only"):
|
|
3453
|
-
_emit_json({"error": message})
|
|
3454
|
-
else:
|
|
3455
|
-
console.print(f"[red]{message}[/red]")
|
|
3456
|
-
raise typer.Exit(1) from exc
|
|
3457
|
-
except (LookupError, ImportError) as exc:
|
|
3458
|
-
_log_hardened_path_error(
|
|
3459
|
-
"export_template_failed_unexpected",
|
|
3460
|
-
exc,
|
|
3461
|
-
context=f"template_url={template_url}, output={output}",
|
|
3462
|
-
recovery_action="emit_cli_error_and_exit",
|
|
3463
|
-
level="exception",
|
|
3464
|
-
)
|
|
3465
|
-
message = f"Failed to export template: {exc}"
|
|
3466
|
-
if _cli_state.get("json_only"):
|
|
3467
|
-
_emit_json({"error": message})
|
|
3468
|
-
else:
|
|
3469
|
-
console.print(f"[red]{message}[/red]")
|
|
3470
|
-
raise typer.Exit(1) from exc
|
|
3471
|
-
|
|
3472
|
-
payload = {"output": str(output.resolve()), "source": template_url}
|
|
3473
|
-
if _cli_state.get("json_only"):
|
|
3474
|
-
_emit_json(payload)
|
|
3475
|
-
else:
|
|
3476
|
-
console.print(f"[green]Template exported to:[/green] {output.resolve()}")
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
# ---------------------------------------------------------------------------
|
|
3480
|
-
# Audit helpers
|
|
3481
|
-
# ---------------------------------------------------------------------------
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
async def _run_gap_analysis_for_repo(
|
|
3485
|
-
*,
|
|
3486
|
-
repo_path: Path,
|
|
3487
|
-
audit_template: Any,
|
|
3488
|
-
profile_obj: ProjectProfile,
|
|
3489
|
-
llm_client: Any | None,
|
|
3490
|
-
llm_model: str | None,
|
|
3491
|
-
) -> RepoGapAnalysisResult | None:
|
|
3492
|
-
criteria = _build_template_criteria(audit_template)
|
|
3493
|
-
analyzer = TemplateAnalyzer(llm_client=llm_client, model=llm_model)
|
|
3494
|
-
results = await analyzer.analyze_template(criteria, profile_obj)
|
|
3495
|
-
analysis_results = [r.model_dump() for r in results]
|
|
3496
|
-
if not analysis_results:
|
|
3497
|
-
return None
|
|
3498
|
-
|
|
3499
|
-
per_check_requirements, checks, all_requirements = _analysis_results_to_requirements(analysis_results)
|
|
3500
|
-
orchestrator = EvidenceOrchestrator(repo_path=repo_path)
|
|
3501
|
-
bundle = await orchestrator.collect_bundle(all_requirements, profile=profile_obj)
|
|
3502
|
-
|
|
3503
|
-
evidence_map: dict[str, list[Evidence]] = {}
|
|
3504
|
-
for check in checks:
|
|
3505
|
-
check_id = check.get("id", "")
|
|
3506
|
-
reqs = per_check_requirements.get(check_id, [])
|
|
3507
|
-
matched: list[Evidence] = []
|
|
3508
|
-
for evidence in bundle.all_evidence:
|
|
3509
|
-
if any(_matches_requirement(req, evidence) for req in reqs):
|
|
3510
|
-
matched.append(evidence)
|
|
3511
|
-
evidence_map[check_id] = matched
|
|
3512
|
-
|
|
3513
|
-
gap_analyzer = GapAnalyzer(llm_client=llm_client, model=llm_model)
|
|
3514
|
-
gap_results_raw = await gap_analyzer.analyze_batch(checks, evidence_map, profile_obj)
|
|
3515
|
-
gap_results = [r for r in gap_results_raw if not isinstance(r, dict)]
|
|
3516
|
-
gaps = [gap for result in gap_results for gap in result.gaps]
|
|
3517
|
-
aligned = [practice for result in gap_results for practice in result.aligned_practices]
|
|
3518
|
-
avg_score = sum(result.score for result in gap_results) / len(gap_results) if gap_results else 0.0
|
|
3519
|
-
overall_score = avg_score * 10.0
|
|
3520
|
-
maturity = MaturityLevel.from_score(avg_score).value
|
|
3521
|
-
|
|
3522
|
-
return RepoGapAnalysisResult(
|
|
3523
|
-
repository=repo_path.name,
|
|
3524
|
-
gaps=gaps, # type: ignore[arg-type]
|
|
3525
|
-
aligned_practices=aligned, # type: ignore[arg-type]
|
|
3526
|
-
overall_score=overall_score,
|
|
3527
|
-
maturity_level=maturity,
|
|
3528
|
-
)
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
def _format_duration(seconds: float) -> str:
|
|
3532
|
-
"""Format duration in human-readable format."""
|
|
3533
|
-
if seconds < 60:
|
|
3534
|
-
return f"{seconds:.1f}s"
|
|
3535
|
-
elif seconds < 3600:
|
|
3536
|
-
mins = int(seconds // 60)
|
|
3537
|
-
secs = int(seconds % 60)
|
|
3538
|
-
return f"{mins}m {secs}s"
|
|
3539
|
-
else:
|
|
3540
|
-
hours = int(seconds // 3600)
|
|
3541
|
-
mins = int((seconds % 3600) // 60)
|
|
3542
|
-
return f"{hours}h {mins}m"
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
def _dry_run_output(audit_template: Any, repos: list[Path], profile_obj: ProjectProfile | None) -> None:
|
|
3546
|
-
"""Output dry-run preview information."""
|
|
3547
|
-
template_name = getattr(audit_template, "name", None)
|
|
3548
|
-
template_version = getattr(audit_template, "version", None)
|
|
3549
|
-
section_count = len(audit_template.sections) if audit_template.sections else 0
|
|
3550
|
-
check_count = sum(len(s.checks) for s in audit_template.sections) if audit_template.sections else 0
|
|
3551
|
-
|
|
3552
|
-
dry_run_result = {
|
|
3553
|
-
"dry_run": True,
|
|
3554
|
-
"template": {
|
|
3555
|
-
"name": template_name,
|
|
3556
|
-
"version": template_version,
|
|
3557
|
-
"section_count": section_count,
|
|
3558
|
-
"check_count": check_count,
|
|
3559
|
-
},
|
|
3560
|
-
"repositories": [{"name": repo.name, "path": str(repo.resolve())} for repo in repos],
|
|
3561
|
-
"repository_count": len(repos),
|
|
3562
|
-
}
|
|
3563
|
-
|
|
3564
|
-
if profile_obj:
|
|
3565
|
-
dry_run_result["profile"] = profile_obj.model_dump(mode="json")
|
|
3566
|
-
|
|
3567
|
-
if _cli_state.get("json_only"):
|
|
3568
|
-
_emit_json(dry_run_result)
|
|
3569
|
-
return
|
|
3570
|
-
|
|
3571
|
-
console.print("[bold cyan]Dry-run mode:[/bold cyan] No checks will be executed.\n")
|
|
3572
|
-
|
|
3573
|
-
info_table = Table(title="Template Info", show_header=False)
|
|
3574
|
-
info_table.add_column("Property", style="bold")
|
|
3575
|
-
info_table.add_column("Value")
|
|
3576
|
-
info_table.add_row("Name", template_name or "(not set)")
|
|
3577
|
-
info_table.add_row("Version", template_version or "(not set)")
|
|
3578
|
-
info_table.add_row("Sections", str(section_count))
|
|
3579
|
-
info_table.add_row("Checks", str(check_count))
|
|
3580
|
-
console.print(info_table)
|
|
3581
|
-
|
|
3582
|
-
if audit_template.sections:
|
|
3583
|
-
console.print("\n[bold]Sections:[/bold]")
|
|
3584
|
-
for section in audit_template.sections:
|
|
3585
|
-
checks_count = len(section.checks) if section.checks else 0
|
|
3586
|
-
console.print(f" - {section.name}: {checks_count} checks ({section.weight:.1f}%)")
|
|
3587
|
-
|
|
3588
|
-
if profile_obj:
|
|
3589
|
-
console.print("\n[bold]Detected Profile:[/bold]")
|
|
3590
|
-
console.print(f" Domain: {profile_obj.domain.value}")
|
|
3591
|
-
console.print(f" Type: {profile_obj.project_type.value}")
|
|
3592
|
-
console.print(f" Risk: {profile_obj.risk_level.value}")
|
|
3593
|
-
console.print(" [dim]Source: Profile Detector (confidence: N/A)[/dim]")
|
|
3594
|
-
|
|
3595
|
-
console.print()
|
|
3596
|
-
repo_table = Table(title=f"Repositories to Audit ({len(repos)})")
|
|
3597
|
-
repo_table.add_column("#", style="dim")
|
|
3598
|
-
repo_table.add_column("Repository")
|
|
3599
|
-
repo_table.add_column("Path")
|
|
3600
|
-
for idx, repo in enumerate(repos, 1):
|
|
3601
|
-
repo_table.add_row(str(idx), repo.name, str(repo.resolve()))
|
|
3602
|
-
console.print(repo_table)
|
|
3603
|
-
|
|
3604
|
-
estimated_time = len(repos) * check_count * 0.5
|
|
3605
|
-
console.print(f"\n[green]Would audit {len(repos)} repositories with {check_count} checks.[/green]")
|
|
3606
|
-
console.print(f"[dim]Estimated time: ~{_format_duration(estimated_time)}[/dim]")
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
def _suggest_fix_for_template_error(error: Exception) -> None:
|
|
3610
|
-
"""Provide actionable guidance for template errors."""
|
|
3611
|
-
error_str = str(error).lower()
|
|
3612
|
-
|
|
3613
|
-
if "section weights" in error_str:
|
|
3614
|
-
console.print("\n[dim]To fix: Ensure section weights in your template sum to 100.0[/dim]")
|
|
3615
|
-
elif "check" in error_str and "not found" in error_str:
|
|
3616
|
-
console.print("\n[dim]To fix: Use 'vds-audit-orchestrator list-checks' to see available check types[/dim]")
|
|
3617
|
-
elif "file" in error_str or "sheet" in error_str:
|
|
3618
|
-
console.print("\n[dim]To fix: Verify the template file path and format[/dim]")
|
|
3619
|
-
else:
|
|
3620
|
-
console.print("\n[dim]To fix: Use 'vds-audit-orchestrator validate-template' to check your template[/dim]")
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
def _suggest_fix_for_audit_error(error: Exception) -> None:
|
|
3624
|
-
"""Provide actionable guidance for audit errors."""
|
|
3625
|
-
error_str = str(error).lower()
|
|
3626
|
-
|
|
3627
|
-
if "permission" in error_str or "access" in error_str:
|
|
3628
|
-
console.print("\n[dim]To fix: Check file/directory permissions and access rights[/dim]")
|
|
3629
|
-
elif "template" in error_str:
|
|
3630
|
-
console.print("\n[dim]To fix: Validate your template with 'vds-audit-orchestrator validate-template'[/dim]")
|
|
3631
|
-
elif "network" in error_str or "connection" in error_str:
|
|
3632
|
-
console.print("\n[dim]To fix: Check network connectivity and API credentials[/dim]")
|
|
3633
|
-
else:
|
|
3634
|
-
console.print("\n[dim]To debug: Run with --verbose flag for more details[/dim]")
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
def _validate_checkpoint(
|
|
3638
|
-
checkpoint: AuditCheckpoint,
|
|
3639
|
-
repo: Path,
|
|
3640
|
-
template: Any,
|
|
3641
|
-
) -> None:
|
|
3642
|
-
errors: list[str] = []
|
|
3643
|
-
if checkpoint.template_name and checkpoint.template_name != getattr(template, "name", ""):
|
|
3644
|
-
errors.append(
|
|
3645
|
-
f"template name mismatch (checkpoint={checkpoint.template_name}, template={getattr(template, 'name', '')})"
|
|
3646
|
-
)
|
|
3647
|
-
tpl_version = getattr(template, "version", "")
|
|
3648
|
-
if checkpoint.template_version and checkpoint.template_version != tpl_version:
|
|
3649
|
-
errors.append(f"template version mismatch (checkpoint={checkpoint.template_version}, template={tpl_version})")
|
|
3650
|
-
repo_name = checkpoint.metadata.get("repo_name")
|
|
3651
|
-
if repo_name and repo_name != repo.name:
|
|
3652
|
-
errors.append(f"repository mismatch (checkpoint={repo_name}, repo={repo.name})")
|
|
3653
|
-
if errors:
|
|
3654
|
-
raise ValueError("Checkpoint is incompatible: " + "; ".join(errors))
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
def _create_checkpoint(repo: Path, template: Any, started_at: datetime) -> AuditCheckpoint:
|
|
3658
|
-
return AuditCheckpoint(
|
|
3659
|
-
audit_id=str(uuid4()),
|
|
3660
|
-
template_name=getattr(template, "name", "unknown"),
|
|
3661
|
-
template_version=getattr(template, "version", "unknown"),
|
|
3662
|
-
metadata={
|
|
3663
|
-
"repo_name": repo.name,
|
|
3664
|
-
"repo_path": str(repo),
|
|
3665
|
-
"started_at": started_at.isoformat(),
|
|
3666
|
-
},
|
|
3667
|
-
)
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
# ---------------------------------------------------------------------------
|
|
3671
|
-
# audit
|
|
3672
|
-
# ---------------------------------------------------------------------------
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
@app.command()
|
|
3676
|
-
def audit(
|
|
3677
|
-
repo_path: Path = typer.Argument(..., help="Path to repository or directory of repositories", exists=True),
|
|
3678
|
-
template: Path | None = typer.Option(None, "--template", "-t", help="Path to Excel template", exists=True),
|
|
3679
|
-
template_url: str | None = typer.Option(
|
|
3680
|
-
None,
|
|
3681
|
-
"--template-url",
|
|
3682
|
-
"--google-sheet",
|
|
3683
|
-
"-g",
|
|
3684
|
-
help="Google Sheet URL for rubric-style template",
|
|
3685
|
-
),
|
|
3686
|
-
template_sheet: str | None = typer.Option(
|
|
3687
|
-
None,
|
|
3688
|
-
"--template-sheet",
|
|
3689
|
-
"--sheet-name",
|
|
3690
|
-
help="Sheet name in Google Sheet",
|
|
3691
|
-
),
|
|
3692
|
-
template_gid: int | None = typer.Option(
|
|
3693
|
-
None,
|
|
3694
|
-
"--template-gid",
|
|
3695
|
-
"--sheet-gid",
|
|
3696
|
-
help="Sheet GID in Google Sheet",
|
|
3697
|
-
),
|
|
3698
|
-
template_table_index: int | None = typer.Option(
|
|
3699
|
-
None,
|
|
3700
|
-
"--template-table-index",
|
|
3701
|
-
help="Google Docs table index (0-based)",
|
|
3702
|
-
),
|
|
3703
|
-
template_map: Path | None = typer.Option(
|
|
3704
|
-
None,
|
|
3705
|
-
"--template-map",
|
|
3706
|
-
help="Optional JSON mapping file for rubric-style sheets",
|
|
3707
|
-
exists=True,
|
|
3708
|
-
),
|
|
3709
|
-
gs_auth_method: str | None = typer.Option(None, "--gs-auth-method", help="Google Sheets auth method"),
|
|
3710
|
-
gs_credentials_path: Path | None = typer.Option(
|
|
3711
|
-
None, "--gs-credentials-path", help="Google Sheets service account credentials path"
|
|
3712
|
-
),
|
|
3713
|
-
gs_oauth_credentials_path: Path | None = typer.Option(
|
|
3714
|
-
None, "--gs-oauth-credentials-path", help="Google Sheets OAuth credentials path"
|
|
3715
|
-
),
|
|
3716
|
-
gs_api_key: str | None = typer.Option(None, "--gs-api-key", help="Google Sheets API key"),
|
|
3717
|
-
project_profile: Path | None = typer.Option(
|
|
3718
|
-
None,
|
|
3719
|
-
"--project-profile",
|
|
3720
|
-
help="Explicit project profile file",
|
|
3721
|
-
exists=True,
|
|
3722
|
-
),
|
|
3723
|
-
profile_map: Path | None = typer.Option(None, "--profile-map", help="Profile map rules file", exists=True),
|
|
3724
|
-
adaptive_weights: bool = typer.Option(
|
|
3725
|
-
True, "--adaptive-weights/--no-adaptive-weights", help="Enable adaptive weighting"
|
|
3726
|
-
),
|
|
3727
|
-
gap_analysis: bool = typer.Option(
|
|
3728
|
-
False,
|
|
3729
|
-
"--gap-analysis/--no-gap-analysis",
|
|
3730
|
-
help="Run evidence gap analysis and include results in reports",
|
|
3731
|
-
),
|
|
3732
|
-
gap_llm: bool | None = typer.Option(
|
|
3733
|
-
None,
|
|
3734
|
-
"--gap-llm/--no-gap-llm",
|
|
3735
|
-
help="Enable or disable LLM usage for gap analysis",
|
|
3736
|
-
),
|
|
3737
|
-
gap_llm_model: str | None = typer.Option(
|
|
3738
|
-
None,
|
|
3739
|
-
"--gap-llm-model",
|
|
3740
|
-
help="Override LLM model name for gap analysis",
|
|
3741
|
-
),
|
|
3742
|
-
section_packs: bool = typer.Option(
|
|
3743
|
-
True, "--section-packs/--no-section-packs", help="Enable section pack inclusion/exclusion"
|
|
3744
|
-
),
|
|
3745
|
-
section_pack: list[str] | None = typer.Option(
|
|
3746
|
-
None, "--section-pack", help="Section pack names to apply (repeatable)"
|
|
3747
|
-
),
|
|
3748
|
-
baseline: Path | None = typer.Option(
|
|
3749
|
-
None, "--baseline", help="Baseline JSON report for trend indicators", exists=True
|
|
3750
|
-
),
|
|
3751
|
-
resume: Path | None = typer.Option(None, "--resume", help="Resume from checkpoint file", exists=True),
|
|
3752
|
-
checkpoint: Path | None = typer.Option(None, "--checkpoint", help="Enable checkpointing to specified file"),
|
|
3753
|
-
output: Path | None = typer.Option(None, "--output", "-o", help="Output report path"),
|
|
3754
|
-
concurrency: int = typer.Option(5, "--concurrency", "-c", help="Check concurrency per repo"),
|
|
3755
|
-
dry_run: bool = typer.Option(False, "--dry-run", help="Preview repositories without running checks"),
|
|
3756
|
-
) -> None:
|
|
3757
|
-
"""Run audit against repositories."""
|
|
3758
|
-
ctx = _get_cli_ctx()
|
|
3759
|
-
started_at = datetime.now(UTC)
|
|
3760
|
-
|
|
3761
|
-
# 1. Load Template
|
|
3762
|
-
try:
|
|
3763
|
-
validator = TemplateValidator()
|
|
3764
|
-
|
|
3765
|
-
if template and template_url:
|
|
3766
|
-
raise ValueError("Provide either --template or --template-url (not both)")
|
|
3767
|
-
|
|
3768
|
-
if template:
|
|
3769
|
-
loader = ExcelLoader()
|
|
3770
|
-
audit_template = loader.load(template)
|
|
3771
|
-
elif template_url:
|
|
3772
|
-
mapping_path = resolve_mapping_path(str(template_map) if template_map else None)
|
|
3773
|
-
mapping = load_mapping_file(mapping_path) if mapping_path else None
|
|
3774
|
-
audit_template = load_template_from_google_sheet(
|
|
3775
|
-
spreadsheet_url=template_url,
|
|
3776
|
-
sheet_name=template_sheet,
|
|
3777
|
-
sheet_gid=template_gid,
|
|
3778
|
-
doc_table_index=template_table_index,
|
|
3779
|
-
template_map=mapping,
|
|
3780
|
-
auth_method=gs_auth_method,
|
|
3781
|
-
credentials_path=gs_credentials_path,
|
|
3782
|
-
oauth_credentials_path=gs_oauth_credentials_path,
|
|
3783
|
-
api_key=gs_api_key,
|
|
3784
|
-
)
|
|
3785
|
-
else:
|
|
3786
|
-
raise ValueError("Must provide either --template or --template-url")
|
|
3787
|
-
|
|
3788
|
-
validator.validate(audit_template)
|
|
3789
|
-
except Exception as e:
|
|
3790
|
-
if _cli_state["json_only"]:
|
|
3791
|
-
_emit_json({"error": str(e)})
|
|
3792
|
-
else:
|
|
3793
|
-
console.print(f"[red]Failed to load template:[/red] {e}")
|
|
3794
|
-
_suggest_fix_for_template_error(e)
|
|
3795
|
-
raise typer.Exit(1) from e
|
|
3796
|
-
|
|
3797
|
-
# 2. Discover Repositories
|
|
3798
|
-
repos = []
|
|
3799
|
-
if (repo_path / ".git").exists():
|
|
3800
|
-
repos = [repo_path]
|
|
3801
|
-
else:
|
|
3802
|
-
repos = [p for p in repo_path.iterdir() if p.is_dir() and (p / ".git").exists()]
|
|
3803
|
-
|
|
3804
|
-
if not repos:
|
|
3805
|
-
if _cli_state["json_only"]:
|
|
3806
|
-
_emit_json({"error": "No repositories found", "guidance": "Ensure the path contains git repositories"})
|
|
3807
|
-
else:
|
|
3808
|
-
console.print("[yellow]No git repositories found.[/yellow]")
|
|
3809
|
-
console.print(
|
|
3810
|
-
"[dim]Hint: Provide a path to a git repository or a directory containing git repositories.[/dim]"
|
|
3811
|
-
)
|
|
3812
|
-
raise typer.Exit(1)
|
|
3813
|
-
|
|
3814
|
-
flags = get_config().features
|
|
3815
|
-
checkpoint_enabled = flags.is_enabled(FeatureFlag.CHECKPOINT_ENABLED)
|
|
3816
|
-
adaptive_weighting_enabled = flags.is_enabled(FeatureFlag.ADAPTIVE_WEIGHTING_ENABLED)
|
|
3817
|
-
if adaptive_weights and not adaptive_weighting_enabled:
|
|
3818
|
-
message = "Adaptive weighting is disabled via feature flags (VDS_AUDIT_ADAPTIVE_WEIGHTING_ENABLED)."
|
|
3819
|
-
if _cli_state["json_only"]:
|
|
3820
|
-
_emit_json({"error": message})
|
|
3821
|
-
else:
|
|
3822
|
-
console.print(f"[red]{message}[/red]")
|
|
3823
|
-
raise typer.Exit(1)
|
|
3824
|
-
if (resume or checkpoint) and not checkpoint_enabled:
|
|
3825
|
-
message = "Checkpoint/resume is disabled via feature flags (VDS_AUDIT_CHECKPOINT_ENABLED)."
|
|
3826
|
-
if _cli_state["json_only"]:
|
|
3827
|
-
_emit_json({"error": message})
|
|
3828
|
-
else:
|
|
3829
|
-
console.print(f"[red]{message}[/red]")
|
|
3830
|
-
raise typer.Exit(1)
|
|
3831
|
-
|
|
3832
|
-
if dry_run and (resume or checkpoint):
|
|
3833
|
-
message = "Checkpoint/resume cannot be used with --dry-run."
|
|
3834
|
-
if _cli_state["json_only"]:
|
|
3835
|
-
_emit_json({"error": message})
|
|
3836
|
-
else:
|
|
3837
|
-
console.print(f"[red]{message}[/red]")
|
|
3838
|
-
raise typer.Exit(1)
|
|
3839
|
-
|
|
3840
|
-
baseline_scores: dict[str, float] = {}
|
|
3841
|
-
if baseline:
|
|
3842
|
-
try:
|
|
3843
|
-
baseline_scores = _load_baseline_scores(baseline)
|
|
3844
|
-
except (OSError, RuntimeError, TypeError, ValueError, LookupError, ImportError) as exc:
|
|
3845
|
-
_log_hardened_path_error(
|
|
3846
|
-
"audit_baseline_load_failed",
|
|
3847
|
-
exc,
|
|
3848
|
-
context=f"baseline={baseline}",
|
|
3849
|
-
recovery_action="emit_cli_error_and_exit",
|
|
3850
|
-
level="exception",
|
|
3851
|
-
)
|
|
3852
|
-
if _cli_state["json_only"]:
|
|
3853
|
-
_emit_json({"error": f"Failed to read baseline: {exc}"})
|
|
3854
|
-
else:
|
|
3855
|
-
console.print(f"[red]Failed to read baseline:[/red] {exc}")
|
|
3856
|
-
raise typer.Exit(1) from exc
|
|
3857
|
-
|
|
3858
|
-
# 3. Dry-run mode - preview without executing checks
|
|
3859
|
-
if dry_run:
|
|
3860
|
-
profile_obj = None
|
|
3861
|
-
try:
|
|
3862
|
-
resolution = resolve_profile(
|
|
3863
|
-
repo_path=repos[0],
|
|
3864
|
-
project_id=repos[0].name,
|
|
3865
|
-
cli_profile=project_profile,
|
|
3866
|
-
profile_map_path=profile_map,
|
|
3867
|
-
)
|
|
3868
|
-
profile_obj = resolution.profile
|
|
3869
|
-
except (OSError, RuntimeError, TypeError, ValueError, LookupError, ImportError) as exc:
|
|
3870
|
-
_log_hardened_path_error(
|
|
3871
|
-
"profile_detection_failed_dry_run",
|
|
3872
|
-
exc,
|
|
3873
|
-
context=f"repo={repos[0]}, profile_map={profile_map}",
|
|
3874
|
-
recovery_action="continue_with_default_profile_preview",
|
|
3875
|
-
)
|
|
3876
|
-
_dry_run_output(audit_template, repos, profile_obj)
|
|
3877
|
-
raise typer.Exit(0)
|
|
3878
|
-
|
|
3879
|
-
gap_llm_client = None
|
|
3880
|
-
gap_model_name = gap_llm_model
|
|
3881
|
-
if gap_analysis:
|
|
3882
|
-
provider = LLMProvider()
|
|
3883
|
-
use_llm = provider.enabled if gap_llm is None else gap_llm
|
|
3884
|
-
if use_llm:
|
|
3885
|
-
try:
|
|
3886
|
-
gap_llm_client = provider.get_client()
|
|
3887
|
-
if not gap_model_name:
|
|
3888
|
-
gap_model_name = provider.get_model("complex")
|
|
3889
|
-
except (OSError, RuntimeError, TypeError, ValueError, LookupError, ImportError) as exc:
|
|
3890
|
-
_log_hardened_path_error(
|
|
3891
|
-
"gap_llm_unavailable",
|
|
3892
|
-
exc,
|
|
3893
|
-
context=f"llm_enabled={use_llm}, model_name={gap_model_name}",
|
|
3894
|
-
recovery_action="continue_without_gap_llm",
|
|
3895
|
-
)
|
|
3896
|
-
|
|
3897
|
-
checkpoint_plan: dict[Path, tuple[AuditCheckpoint | None, Path | None]] = {}
|
|
3898
|
-
checkpoint_paths: list[Path] = []
|
|
3899
|
-
if resume or checkpoint:
|
|
3900
|
-
if len(repos) > 1:
|
|
3901
|
-
if resume and resume.is_file():
|
|
3902
|
-
message = "When auditing multiple repositories, --resume must point to a directory of checkpoints."
|
|
3903
|
-
if _cli_state["json_only"]:
|
|
3904
|
-
_emit_json({"error": message})
|
|
3905
|
-
else:
|
|
3906
|
-
console.print(f"[red]{message}[/red]")
|
|
3907
|
-
raise typer.Exit(1)
|
|
3908
|
-
if checkpoint and checkpoint.exists() and checkpoint.is_file():
|
|
3909
|
-
message = "When auditing multiple repositories, --checkpoint must point to a directory."
|
|
3910
|
-
if _cli_state["json_only"]:
|
|
3911
|
-
_emit_json({"error": message})
|
|
3912
|
-
else:
|
|
3913
|
-
console.print(f"[red]{message}[/red]")
|
|
3914
|
-
raise typer.Exit(1)
|
|
3915
|
-
checkpoint_dir = checkpoint or resume or Path(".audit_checkpoints")
|
|
3916
|
-
if resume and not checkpoint_dir.exists():
|
|
3917
|
-
message = f"Checkpoint directory not found: {checkpoint_dir}"
|
|
3918
|
-
if _cli_state["json_only"]:
|
|
3919
|
-
_emit_json({"error": message})
|
|
3920
|
-
else:
|
|
3921
|
-
console.print(f"[red]{message}[/red]")
|
|
3922
|
-
raise typer.Exit(1)
|
|
3923
|
-
checkpoint_dir.mkdir(parents=True, exist_ok=True)
|
|
3924
|
-
for repo in repos:
|
|
3925
|
-
per_repo_path = checkpoint_dir / f"{repo.name}.checkpoint.json"
|
|
3926
|
-
if resume:
|
|
3927
|
-
if not per_repo_path.exists():
|
|
3928
|
-
message = f"Checkpoint not found for repo {repo.name}: {per_repo_path}"
|
|
3929
|
-
if _cli_state["json_only"]:
|
|
3930
|
-
_emit_json({"error": message})
|
|
3931
|
-
else:
|
|
3932
|
-
console.print(f"[red]{message}[/red]")
|
|
3933
|
-
raise typer.Exit(1)
|
|
3934
|
-
cp = load_checkpoint(per_repo_path)
|
|
3935
|
-
_validate_checkpoint(cp, repo, audit_template)
|
|
3936
|
-
else:
|
|
3937
|
-
cp = _create_checkpoint(repo, audit_template, started_at)
|
|
3938
|
-
save_checkpoint(cp, per_repo_path)
|
|
3939
|
-
checkpoint_plan[repo] = (cp, per_repo_path)
|
|
3940
|
-
checkpoint_paths.append(per_repo_path)
|
|
3941
|
-
else:
|
|
3942
|
-
repo = repos[0]
|
|
3943
|
-
checkpoint_path = checkpoint or resume
|
|
3944
|
-
single_cp: AuditCheckpoint | None = None
|
|
3945
|
-
if resume:
|
|
3946
|
-
single_cp = load_checkpoint(resume)
|
|
3947
|
-
_validate_checkpoint(single_cp, repo, audit_template)
|
|
3948
|
-
if checkpoint_path:
|
|
3949
|
-
if single_cp is None:
|
|
3950
|
-
single_cp = _create_checkpoint(repo, audit_template, started_at)
|
|
3951
|
-
save_checkpoint(single_cp, checkpoint_path)
|
|
3952
|
-
checkpoint_plan[repo] = (single_cp, checkpoint_path)
|
|
3953
|
-
checkpoint_paths.append(checkpoint_path)
|
|
3954
|
-
|
|
3955
|
-
# 4. Run Audit
|
|
3956
|
-
if ctx.verbose and not _cli_state["json_only"]:
|
|
3957
|
-
console.print(f"[dim]Starting audit with {len(repos)} repositories...[/dim]\n")
|
|
3958
|
-
engine = AuditEngine(concurrency=concurrency)
|
|
3959
|
-
scorer = Scorer()
|
|
3960
|
-
registry = get_default_registry() if section_packs else None
|
|
3961
|
-
results = []
|
|
3962
|
-
|
|
3963
|
-
async def run_all():
|
|
3964
|
-
async def _run_repo(repo: Path) -> tuple[Any, Any]:
|
|
3965
|
-
checkpoint_data = checkpoint_plan.get(repo, (None, None))
|
|
3966
|
-
audit_id = checkpoint_data[0].audit_id if checkpoint_data[0] is not None else str(uuid4())
|
|
3967
|
-
template_for_repo = audit_template
|
|
3968
|
-
profile_result = None
|
|
3969
|
-
pack_result = None
|
|
3970
|
-
adaptive_result = None
|
|
3971
|
-
|
|
3972
|
-
if adaptive_weights or section_packs or gap_analysis:
|
|
3973
|
-
try:
|
|
3974
|
-
resolution = await resolve_profile_async(
|
|
3975
|
-
repo_path=repo,
|
|
3976
|
-
project_id=repo.name,
|
|
3977
|
-
cli_profile=project_profile,
|
|
3978
|
-
profile_map_path=profile_map,
|
|
3979
|
-
)
|
|
3980
|
-
profile_result = resolution.profile
|
|
3981
|
-
except (OSError, RuntimeError, TypeError, ValueError, LookupError, ImportError) as exc:
|
|
3982
|
-
_log_hardened_path_error(
|
|
3983
|
-
"profile_resolution_failed",
|
|
3984
|
-
exc,
|
|
3985
|
-
context=f"repo={repo}, profile_map={profile_map}",
|
|
3986
|
-
recovery_action="continue_with_repo_name_profile",
|
|
3987
|
-
)
|
|
3988
|
-
|
|
3989
|
-
project_key = profile_result.project_id if profile_result is not None else repo.name
|
|
3990
|
-
context_vars = {
|
|
3991
|
-
"audit_id": str(audit_id),
|
|
3992
|
-
"project_key": str(project_key),
|
|
3993
|
-
}
|
|
3994
|
-
|
|
3995
|
-
if section_packs and registry is not None and profile_result is not None:
|
|
3996
|
-
pack_result = registry.apply_packs(template_for_repo, profile_result, section_pack)
|
|
3997
|
-
template_for_repo = registry.filter_template_sections(
|
|
3998
|
-
template_for_repo,
|
|
3999
|
-
profile_result,
|
|
4000
|
-
section_pack,
|
|
4001
|
-
)
|
|
4002
|
-
if pack_result.generated_sections:
|
|
4003
|
-
from vds_audit_orchestrator.models.template import AuditSection
|
|
4004
|
-
from vds_audit_orchestrator.models.template import AuditTemplate as AuditTemplateModel
|
|
4005
|
-
|
|
4006
|
-
generated = [AuditSection.model_validate(section) for section in pack_result.generated_sections]
|
|
4007
|
-
template_for_repo = AuditTemplateModel(
|
|
4008
|
-
name=template_for_repo.name,
|
|
4009
|
-
version=template_for_repo.version,
|
|
4010
|
-
sections=[*template_for_repo.sections, *generated],
|
|
4011
|
-
)
|
|
4012
|
-
|
|
4013
|
-
if adaptive_weights and profile_result is not None:
|
|
4014
|
-
adaptive_result = compute_adaptive_weights(template_for_repo, profile_result)
|
|
4015
|
-
|
|
4016
|
-
audit_result = await engine.audit_repo(
|
|
4017
|
-
repo,
|
|
4018
|
-
template_for_repo,
|
|
4019
|
-
context_vars=context_vars,
|
|
4020
|
-
dry_run=dry_run,
|
|
4021
|
-
checkpoint=checkpoint_data[0],
|
|
4022
|
-
checkpoint_path=checkpoint_data[1],
|
|
4023
|
-
checkpoint_enabled=checkpoint_enabled,
|
|
4024
|
-
scorer=scorer,
|
|
4025
|
-
use_adaptive_weights=adaptive_weights and profile_result is not None,
|
|
4026
|
-
adaptive_weights=adaptive_result,
|
|
4027
|
-
profile=profile_result,
|
|
4028
|
-
)
|
|
4029
|
-
|
|
4030
|
-
if gap_analysis:
|
|
4031
|
-
gap_profile = profile_result or default_profile(project_id=repo.name, name=repo.name)
|
|
4032
|
-
try:
|
|
4033
|
-
gap_result = await _run_gap_analysis_for_repo(
|
|
4034
|
-
repo_path=repo,
|
|
4035
|
-
audit_template=template_for_repo,
|
|
4036
|
-
profile_obj=gap_profile,
|
|
4037
|
-
llm_client=gap_llm_client,
|
|
4038
|
-
llm_model=gap_model_name,
|
|
4039
|
-
)
|
|
4040
|
-
if gap_result is not None:
|
|
4041
|
-
audit_result = audit_result.model_copy(update={"gap_analysis": gap_result})
|
|
4042
|
-
except Exception as exc:
|
|
4043
|
-
logger.warning("gap_analysis_failed", repo=str(repo), error=str(exc))
|
|
4044
|
-
|
|
4045
|
-
from vds_audit_orchestrator.observability.metrics import record_gaps
|
|
4046
|
-
|
|
4047
|
-
record_gaps(
|
|
4048
|
-
repository=repo.name,
|
|
4049
|
-
severity_counts={
|
|
4050
|
-
"critical": audit_result.critical_issues,
|
|
4051
|
-
"high": audit_result.high_issues,
|
|
4052
|
-
"medium": audit_result.medium_issues,
|
|
4053
|
-
"low": audit_result.low_issues,
|
|
4054
|
-
},
|
|
4055
|
-
)
|
|
4056
|
-
|
|
4057
|
-
audit_metadata = dict(audit_result.audit_metadata)
|
|
4058
|
-
audit_metadata["audit_id"] = str(audit_id)
|
|
4059
|
-
audit_result = audit_result.model_copy(update={"audit_metadata": audit_metadata})
|
|
4060
|
-
|
|
4061
|
-
return audit_result, pack_result
|
|
4062
|
-
|
|
4063
|
-
tasks = [_run_repo(repo) for repo in repos]
|
|
4064
|
-
return await asyncio.gather(*tasks)
|
|
4065
|
-
|
|
4066
|
-
try:
|
|
4067
|
-
if _cli_state["json_only"]:
|
|
4068
|
-
results = asyncio.run(run_all())
|
|
4069
|
-
else:
|
|
4070
|
-
with Progress(
|
|
4071
|
-
SpinnerColumn(),
|
|
4072
|
-
TextColumn("[progress.description]{task.description}"),
|
|
4073
|
-
transient=True,
|
|
4074
|
-
) as progress:
|
|
4075
|
-
progress.add_task(f"Auditing {len(repos)} repositories...", total=len(repos))
|
|
4076
|
-
results = asyncio.run(run_all())
|
|
4077
|
-
except Exception as e:
|
|
4078
|
-
if _cli_state["json_only"]:
|
|
4079
|
-
_emit_json({"error": str(e)})
|
|
4080
|
-
else:
|
|
4081
|
-
console.print(f"[red]Audit failed:[/red] {e}")
|
|
4082
|
-
_suggest_fix_for_audit_error(e)
|
|
4083
|
-
raise typer.Exit(1) from e
|
|
4084
|
-
|
|
4085
|
-
processed_results = []
|
|
4086
|
-
for repo_result_tuple in results:
|
|
4087
|
-
repo_result, pack_result = repo_result_tuple
|
|
4088
|
-
if pack_result is not None:
|
|
4089
|
-
repo_result.audit_metadata["section_packs"] = pack_result.to_dict()
|
|
4090
|
-
if baseline_scores:
|
|
4091
|
-
baseline_score = baseline_scores.get(repo_result.repo_name)
|
|
4092
|
-
if baseline_score is not None:
|
|
4093
|
-
repo_result = repo_result.model_copy(
|
|
4094
|
-
update={
|
|
4095
|
-
"baseline_score": baseline_score,
|
|
4096
|
-
"score_delta": repo_result.total_score - baseline_score,
|
|
4097
|
-
}
|
|
4098
|
-
)
|
|
4099
|
-
processed_results.append(repo_result)
|
|
4100
|
-
results = processed_results
|
|
4101
|
-
|
|
4102
|
-
finished_at = datetime.now(UTC)
|
|
4103
|
-
try:
|
|
4104
|
-
orchestrator_version = metadata.version("vds-audit-orchestrator")
|
|
4105
|
-
except metadata.PackageNotFoundError:
|
|
4106
|
-
orchestrator_version = "unknown"
|
|
4107
|
-
|
|
4108
|
-
inputs: dict[str, str] = {
|
|
4109
|
-
"repo_path": str(repo_path),
|
|
4110
|
-
"concurrency": str(concurrency),
|
|
4111
|
-
}
|
|
4112
|
-
if template:
|
|
4113
|
-
inputs["template"] = str(template)
|
|
4114
|
-
if template_url:
|
|
4115
|
-
inputs["template_url"] = template_url
|
|
4116
|
-
if template_sheet:
|
|
4117
|
-
inputs["template_sheet"] = template_sheet
|
|
4118
|
-
if template_gid is not None:
|
|
4119
|
-
inputs["template_gid"] = str(template_gid)
|
|
4120
|
-
if template_map:
|
|
4121
|
-
inputs["template_map"] = str(template_map)
|
|
4122
|
-
if template_table_index is not None:
|
|
4123
|
-
inputs["template_table_index"] = str(template_table_index)
|
|
4124
|
-
if gs_auth_method:
|
|
4125
|
-
inputs["gs_auth_method"] = gs_auth_method
|
|
4126
|
-
if gs_credentials_path:
|
|
4127
|
-
inputs["gs_credentials_path"] = str(gs_credentials_path)
|
|
4128
|
-
if gs_oauth_credentials_path:
|
|
4129
|
-
inputs["gs_oauth_credentials_path"] = str(gs_oauth_credentials_path)
|
|
4130
|
-
if gs_api_key:
|
|
4131
|
-
inputs["gs_api_key"] = "set"
|
|
4132
|
-
if project_profile:
|
|
4133
|
-
inputs["project_profile"] = str(project_profile)
|
|
4134
|
-
if profile_map:
|
|
4135
|
-
inputs["profile_map"] = str(profile_map)
|
|
4136
|
-
inputs["adaptive_weights"] = str(adaptive_weights)
|
|
4137
|
-
inputs["gap_analysis"] = str(gap_analysis)
|
|
4138
|
-
if gap_analysis:
|
|
4139
|
-
inputs["gap_llm_enabled"] = str(gap_llm if gap_llm is not None else bool(gap_llm_client))
|
|
4140
|
-
if gap_model_name:
|
|
4141
|
-
inputs["gap_llm_model"] = gap_model_name
|
|
4142
|
-
inputs["section_packs"] = str(section_packs)
|
|
4143
|
-
if section_pack:
|
|
4144
|
-
inputs["section_pack"] = ",".join(section_pack)
|
|
4145
|
-
if baseline:
|
|
4146
|
-
inputs["baseline"] = str(baseline)
|
|
4147
|
-
if output:
|
|
4148
|
-
inputs["output"] = str(output)
|
|
4149
|
-
cost_summary = global_tracker.summary()
|
|
4150
|
-
if cost_summary.get("total_cost_usd", 0) > 0 or cost_summary.get("total_input_tokens", 0) > 0:
|
|
4151
|
-
inputs["llm_cost_usd"] = str(cost_summary.get("total_cost_usd"))
|
|
4152
|
-
inputs["llm_input_tokens"] = str(cost_summary.get("total_input_tokens"))
|
|
4153
|
-
inputs["llm_output_tokens"] = str(cost_summary.get("total_output_tokens"))
|
|
4154
|
-
|
|
4155
|
-
meta = build_metadata(
|
|
4156
|
-
template_name=getattr(audit_template, "name", None),
|
|
4157
|
-
template_version=getattr(audit_template, "version", None),
|
|
4158
|
-
started_at=started_at,
|
|
4159
|
-
finished_at=finished_at,
|
|
4160
|
-
orchestrator_version=orchestrator_version,
|
|
4161
|
-
inputs=inputs,
|
|
4162
|
-
).model_copy(update={"repo_count": len(repos)})
|
|
4163
|
-
|
|
4164
|
-
report = AuditRunReport(metadata=meta, repositories=results)
|
|
4165
|
-
debug_bundle_path = _cli_state.get("debug_bundle")
|
|
4166
|
-
if debug_bundle_path:
|
|
4167
|
-
try:
|
|
4168
|
-
_write_debug_bundle(
|
|
4169
|
-
debug_bundle_path,
|
|
4170
|
-
report,
|
|
4171
|
-
inputs,
|
|
4172
|
-
cost_summary,
|
|
4173
|
-
_cli_state.get("log_capture"),
|
|
4174
|
-
)
|
|
4175
|
-
except (OSError, RuntimeError, TypeError, ValueError, LookupError, ImportError) as exc:
|
|
4176
|
-
_log_hardened_path_error(
|
|
4177
|
-
"debug_bundle_failed",
|
|
4178
|
-
exc,
|
|
4179
|
-
context=f"debug_bundle_path={debug_bundle_path}",
|
|
4180
|
-
recovery_action="continue_without_debug_bundle",
|
|
4181
|
-
)
|
|
4182
|
-
|
|
4183
|
-
report_paths = None
|
|
4184
|
-
if ctx.reports and not ctx.json_only:
|
|
4185
|
-
report_paths = generate_reports(
|
|
4186
|
-
report,
|
|
4187
|
-
report_dir=ctx.report_dir,
|
|
4188
|
-
excel_path=output,
|
|
4189
|
-
include_markdown=ctx.markdown,
|
|
4190
|
-
include_sarif=ctx.sarif,
|
|
4191
|
-
)
|
|
4192
|
-
|
|
4193
|
-
duration = (finished_at - started_at).total_seconds()
|
|
4194
|
-
|
|
4195
|
-
if _cli_state["json_only"]:
|
|
4196
|
-
payload = report.model_dump(mode="json", exclude_none=True)
|
|
4197
|
-
if report_paths:
|
|
4198
|
-
payload["report_paths"] = {
|
|
4199
|
-
"excel": str(report_paths.excel),
|
|
4200
|
-
"json": str(report_paths.json),
|
|
4201
|
-
"markdown": str(report_paths.markdown) if report_paths.markdown else None,
|
|
4202
|
-
"sarif": str(report_paths.sarif) if report_paths.sarif else None,
|
|
4203
|
-
}
|
|
4204
|
-
_emit_json(payload)
|
|
4205
|
-
else:
|
|
4206
|
-
console.print("[green]Audit Complete![/green]")
|
|
4207
|
-
console.print(f"[dim]Duration: {_format_duration(duration)}[/dim]\n")
|
|
4208
|
-
|
|
4209
|
-
if report_paths:
|
|
4210
|
-
console.print(f"Excel report: [bold]{report_paths.excel}[/bold]")
|
|
4211
|
-
console.print(f"JSON report: [bold]{report_paths.json}[/bold]")
|
|
4212
|
-
if report_paths.markdown:
|
|
4213
|
-
console.print(f"Markdown report: [bold]{report_paths.markdown}[/bold]")
|
|
4214
|
-
if report_paths.sarif:
|
|
4215
|
-
console.print(f"SARIF report: [bold]{report_paths.sarif}[/bold]")
|
|
4216
|
-
console.print()
|
|
4217
|
-
else:
|
|
4218
|
-
console.print("[yellow]Reports are disabled; no files were written.[/yellow]\n")
|
|
4219
|
-
|
|
4220
|
-
table = Table(title="Audit Summary")
|
|
4221
|
-
table.add_column("Repository")
|
|
4222
|
-
table.add_column("Score", justify="right")
|
|
4223
|
-
table.add_column("Critical", justify="right")
|
|
4224
|
-
table.add_column("High", justify="right")
|
|
4225
|
-
table.add_column("Medium", justify="right")
|
|
4226
|
-
table.add_column("Low", justify="right")
|
|
4227
|
-
|
|
4228
|
-
total_critical = 0
|
|
4229
|
-
total_high = 0
|
|
4230
|
-
total_medium = 0
|
|
4231
|
-
total_low = 0
|
|
4232
|
-
|
|
4233
|
-
for res in processed_results:
|
|
4234
|
-
color = "green" if res.total_score >= 80 else "yellow" if res.total_score >= 50 else "red"
|
|
4235
|
-
table.add_row(
|
|
4236
|
-
res.repo_name,
|
|
4237
|
-
f"[{color}]{res.total_score:.1f}[/{color}]",
|
|
4238
|
-
str(res.critical_issues),
|
|
4239
|
-
str(res.high_issues),
|
|
4240
|
-
str(res.medium_issues),
|
|
4241
|
-
str(res.low_issues),
|
|
4242
|
-
)
|
|
4243
|
-
total_critical += res.critical_issues
|
|
4244
|
-
total_high += res.high_issues
|
|
4245
|
-
total_medium += res.medium_issues
|
|
4246
|
-
total_low += res.low_issues
|
|
4247
|
-
|
|
4248
|
-
console.print(table)
|
|
4249
|
-
|
|
4250
|
-
console.print("\n[bold]Overall Statistics:[/bold]")
|
|
4251
|
-
console.print(f" Repositories audited: {len(results)}")
|
|
4252
|
-
console.print(f" Total issues found: {total_critical + total_high + total_medium + total_low}")
|
|
4253
|
-
if total_critical > 0:
|
|
4254
|
-
console.print(
|
|
4255
|
-
" 1. Address [red]Critical[/red] issues immediately - these represent security or compliance risks"
|
|
4256
|
-
)
|
|
4257
|
-
if total_high > 0:
|
|
4258
|
-
console.print(f" [red]Critical issues: {total_critical}[/red]")
|
|
4259
|
-
if total_high > 0:
|
|
4260
|
-
console.print(f" [bright_red]High issues: {total_high}[/bright_red]")
|
|
4261
|
-
if total_medium > 0:
|
|
4262
|
-
console.print(f" [yellow]Medium issues: {total_medium}[/yellow]")
|
|
4263
|
-
if total_low > 0:
|
|
4264
|
-
console.print(f" [blue]Low issues: {total_low}[/blue]")
|
|
4265
|
-
cost_summary = global_tracker.summary()
|
|
4266
|
-
if cost_summary.get("total_cost_usd", 0) > 0 or cost_summary.get("total_input_tokens", 0) > 0:
|
|
4267
|
-
console.print(f" LLM cost (USD): {cost_summary.get('total_cost_usd')}")
|
|
4268
|
-
console.print(
|
|
4269
|
-
" LLM tokens: "
|
|
4270
|
-
f"{cost_summary.get('total_input_tokens')} in / {cost_summary.get('total_output_tokens')} out"
|
|
4271
|
-
)
|
|
4272
|
-
|
|
4273
|
-
if total_critical > 0 or total_high > 0:
|
|
4274
|
-
console.print("\n[bold yellow]Recommended Actions:[/bold yellow]")
|
|
4275
|
-
if total_critical > 0:
|
|
4276
|
-
console.print(
|
|
4277
|
-
" 1. Address [red]Critical[/red] issues immediately - these represent security or compliance risks"
|
|
4278
|
-
)
|
|
4279
|
-
if total_high > 0:
|
|
4280
|
-
console.print(
|
|
4281
|
-
" 2. Prioritize [bright_red]High[/bright_red] issues to reduce risks and improve compliance"
|
|
4282
|
-
)
|
|
4283
|
-
if total_medium > 0:
|
|
4284
|
-
console.print(" 3. Review [yellow]Medium[/yellow] issues during regular development cycles")
|
|
4285
|
-
if total_low > 0:
|
|
4286
|
-
console.print(" 4. Consider [blue]Low[/blue] issues as technical debt to be addressed over time")
|
|
4287
|
-
|
|
4288
|
-
if global_tracker.summary().get("total_cost_usd", 0) > 0:
|
|
4289
|
-
console.print("\n[dim]LLM usage may incur costs. Monitor API usage.[/dim]")
|
|
4290
|
-
|
|
4291
|
-
if any(res.total_score < 50 for res in results):
|
|
4292
|
-
raise typer.Exit(1)
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
# ---------------------------------------------------------------------------
|
|
4296
|
-
# export-debug-bundle
|
|
4297
|
-
# ---------------------------------------------------------------------------
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
@app.command(name="export-debug-bundle")
|
|
4301
|
-
def export_debug_bundle(
|
|
4302
|
-
audit_id: str = typer.Argument(..., help="The ID of the audit to export artifacts for"),
|
|
4303
|
-
output: Path = typer.Option(None, "--output", "-o", help="Output path for the zip bundle"),
|
|
4304
|
-
) -> None:
|
|
4305
|
-
"""Export a debug bundle for a specific audit execution.
|
|
4306
|
-
|
|
4307
|
-
Collects logs, checkpoints, and environment info into a ZIP file
|
|
4308
|
-
for troubleshooting.
|
|
4309
|
-
"""
|
|
4310
|
-
try:
|
|
4311
|
-
path = export_debug_bundle_func(audit_id=audit_id, output_file=output)
|
|
4312
|
-
console.print(f"[green]Debug bundle exported to: {path}[/green]")
|
|
4313
|
-
except (OSError, RuntimeError, TypeError, ValueError, LookupError, ImportError) as e:
|
|
4314
|
-
_log_hardened_path_error(
|
|
4315
|
-
"export_debug_bundle_failed",
|
|
4316
|
-
e,
|
|
4317
|
-
context=f"audit_id={audit_id}, output={output}",
|
|
4318
|
-
recovery_action="emit_cli_error_and_exit",
|
|
4319
|
-
level="exception",
|
|
4320
|
-
)
|
|
4321
|
-
console.print(f"[red]Failed to export debug bundle: {e}[/red]")
|
|
4322
|
-
raise typer.Exit(1) from e
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
# ---------------------------------------------------------------------------
|
|
4326
|
-
# backfill-page-registry (Phase 154, FR-154.4, TSK-154.20)
|
|
4327
|
-
# ---------------------------------------------------------------------------
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
@app.command(name="backfill-page-registry")
|
|
4331
|
-
def backfill_page_registry(
|
|
4332
|
-
state_dsn: str | None = typer.Option(
|
|
4333
|
-
None,
|
|
4334
|
-
"--state-dsn",
|
|
4335
|
-
envvar=_STATE_DSN_ENV_VAR,
|
|
4336
|
-
help=f"Centralized state DSN (required; fallback: {_STATE_DSN_ENV_VAR}).",
|
|
4337
|
-
),
|
|
4338
|
-
project_key: str | None = typer.Option(
|
|
4339
|
-
None,
|
|
4340
|
-
"--project-storage-key",
|
|
4341
|
-
help="Limit backfill to a specific project storage key. If omitted, scans all completed runs.",
|
|
4342
|
-
),
|
|
4343
|
-
dry_run: bool = typer.Option(
|
|
4344
|
-
False,
|
|
4345
|
-
"--dry-run/--no-dry-run",
|
|
4346
|
-
help="Report what would be registered without writing.",
|
|
4347
|
-
),
|
|
4348
|
-
) -> None:
|
|
4349
|
-
"""Backfill the published_pages registry from existing run payload data.
|
|
4350
|
-
|
|
4351
|
-
Scans completed workflow runs in audit_state.runs, extracts
|
|
4352
|
-
confluence_repo_page_ids and confluence_aggregate_page_id from
|
|
4353
|
-
payload JSONB, and registers them in the published_pages table.
|
|
4354
|
-
|
|
4355
|
-
Idempotent: re-running updates existing entries rather than duplicating.
|
|
4356
|
-
"""
|
|
4357
|
-
resolved_state_dsn = _resolve_state_dsn(state_dsn, required=True)
|
|
4358
|
-
assert resolved_state_dsn is not None
|
|
4359
|
-
|
|
4360
|
-
from vds_audit_orchestrator.state.store import PostgresStateStore
|
|
4361
|
-
|
|
4362
|
-
store = PostgresStateStore(resolved_state_dsn)
|
|
4363
|
-
|
|
4364
|
-
try:
|
|
4365
|
-
runs = store.list_runs(status="completed")
|
|
4366
|
-
if project_key:
|
|
4367
|
-
runs = [r for r in runs if r.get("project_key") == project_key]
|
|
4368
|
-
|
|
4369
|
-
registered = 0
|
|
4370
|
-
skipped = 0
|
|
4371
|
-
errors = 0
|
|
4372
|
-
cross_project_skipped = 0
|
|
4373
|
-
|
|
4374
|
-
# Pre-load existing page→project mapping for cross-project dedup.
|
|
4375
|
-
# Prevents registering a page_id under project A when it already
|
|
4376
|
-
# belongs to project B (e.g. Auto Payment payload containing Core
|
|
4377
|
-
# Payment page IDs).
|
|
4378
|
-
existing_page_owners: dict[str, str] = {}
|
|
4379
|
-
with store._conn.cursor() as _cur:
|
|
4380
|
-
_cur.execute("SELECT confluence_page_id, project_key FROM audit_state.published_pages")
|
|
4381
|
-
for _row in _cur:
|
|
4382
|
-
existing_page_owners[_row["confluence_page_id"]] = _row["project_key"]
|
|
4383
|
-
|
|
4384
|
-
def _run_rank(run: dict[str, Any]) -> tuple[datetime, int, int]:
|
|
4385
|
-
started_at = run.get("started_at")
|
|
4386
|
-
if not isinstance(started_at, datetime):
|
|
4387
|
-
started_at = datetime.min.replace(tzinfo=UTC)
|
|
4388
|
-
run_type = str(run.get("run_type") or "")
|
|
4389
|
-
run_priority = 1 if run_type == "publish_project_run" else 0
|
|
4390
|
-
run_id = run.get("id")
|
|
4391
|
-
run_id_int = int(run_id) if isinstance(run_id, int | float) else 0
|
|
4392
|
-
return (started_at, run_priority, run_id_int)
|
|
4393
|
-
|
|
4394
|
-
latest_candidates: dict[tuple[str, str, str], dict[str, Any]] = {}
|
|
4395
|
-
for run in runs:
|
|
4396
|
-
run_project_key = str(run.get("project_key") or "")
|
|
4397
|
-
if not run_project_key:
|
|
4398
|
-
skipped += 1
|
|
4399
|
-
continue
|
|
4400
|
-
|
|
4401
|
-
payload = run.get("payload") or {}
|
|
4402
|
-
if not isinstance(payload, dict):
|
|
4403
|
-
skipped += 1
|
|
4404
|
-
continue
|
|
4405
|
-
|
|
4406
|
-
rank = _run_rank(run)
|
|
4407
|
-
|
|
4408
|
-
repo_page_ids = payload.get("confluence_repo_page_ids") or {}
|
|
4409
|
-
if isinstance(repo_page_ids, dict):
|
|
4410
|
-
for repo_key, page_id in repo_page_ids.items():
|
|
4411
|
-
if not repo_key or not page_id:
|
|
4412
|
-
skipped += 1
|
|
4413
|
-
continue
|
|
4414
|
-
logical_key = (run_project_key, str(repo_key), "repo_run")
|
|
4415
|
-
candidate = {
|
|
4416
|
-
"project_key": run_project_key,
|
|
4417
|
-
"repo_key": str(repo_key),
|
|
4418
|
-
"hierarchy_role": "repo_run",
|
|
4419
|
-
"confluence_page_id": str(page_id),
|
|
4420
|
-
"source_run_id": str(run.get("run_id") or ""),
|
|
4421
|
-
"rank": rank,
|
|
4422
|
-
}
|
|
4423
|
-
existing = latest_candidates.get(logical_key)
|
|
4424
|
-
if existing is None or candidate["rank"] > existing["rank"]:
|
|
4425
|
-
latest_candidates[logical_key] = candidate
|
|
4426
|
-
|
|
4427
|
-
aggregate_page_id = payload.get("confluence_aggregate_page_id")
|
|
4428
|
-
if aggregate_page_id:
|
|
4429
|
-
logical_key = (run_project_key, "", "aggregate")
|
|
4430
|
-
candidate = {
|
|
4431
|
-
"project_key": run_project_key,
|
|
4432
|
-
"repo_key": "",
|
|
4433
|
-
"hierarchy_role": "aggregate",
|
|
4434
|
-
"confluence_page_id": str(aggregate_page_id),
|
|
4435
|
-
"source_run_id": str(run.get("run_id") or ""),
|
|
4436
|
-
"rank": rank,
|
|
4437
|
-
}
|
|
4438
|
-
existing = latest_candidates.get(logical_key)
|
|
4439
|
-
if existing is None or candidate["rank"] > existing["rank"]:
|
|
4440
|
-
latest_candidates[logical_key] = candidate
|
|
4441
|
-
|
|
4442
|
-
for logical_key in sorted(latest_candidates):
|
|
4443
|
-
candidate = latest_candidates[logical_key]
|
|
4444
|
-
run_project_key = str(candidate["project_key"])
|
|
4445
|
-
repo_key = str(candidate["repo_key"])
|
|
4446
|
-
hierarchy_role = str(candidate["hierarchy_role"])
|
|
4447
|
-
page_id_str = str(candidate["confluence_page_id"])
|
|
4448
|
-
|
|
4449
|
-
owning_project = existing_page_owners.get(page_id_str)
|
|
4450
|
-
if owning_project and owning_project != run_project_key:
|
|
4451
|
-
log_context = {
|
|
4452
|
-
"confluence_page_id": page_id_str,
|
|
4453
|
-
"run_project_key": run_project_key,
|
|
4454
|
-
"owning_project": owning_project,
|
|
4455
|
-
"source_run_id": candidate.get("source_run_id"),
|
|
4456
|
-
}
|
|
4457
|
-
if hierarchy_role == "aggregate":
|
|
4458
|
-
logger.warning(
|
|
4459
|
-
"backfill_cross_project_dedup_skipped",
|
|
4460
|
-
hierarchy_role="aggregate",
|
|
4461
|
-
**log_context,
|
|
4462
|
-
)
|
|
4463
|
-
else:
|
|
4464
|
-
logger.warning(
|
|
4465
|
-
"backfill_cross_project_dedup_skipped",
|
|
4466
|
-
repo_key=repo_key,
|
|
4467
|
-
hierarchy_role=hierarchy_role,
|
|
4468
|
-
**log_context,
|
|
4469
|
-
)
|
|
4470
|
-
cross_project_skipped += 1
|
|
4471
|
-
continue
|
|
4472
|
-
|
|
4473
|
-
try:
|
|
4474
|
-
if not dry_run:
|
|
4475
|
-
store.register_published_page(
|
|
4476
|
-
project_key=run_project_key,
|
|
4477
|
-
repo_key=repo_key,
|
|
4478
|
-
hierarchy_role=hierarchy_role,
|
|
4479
|
-
confluence_page_id=page_id_str,
|
|
4480
|
-
)
|
|
4481
|
-
existing_page_owners[page_id_str] = run_project_key
|
|
4482
|
-
registered += 1
|
|
4483
|
-
except Exception as exc:
|
|
4484
|
-
if hierarchy_role == "aggregate":
|
|
4485
|
-
logger.warning(
|
|
4486
|
-
"backfill_page_registry_aggregate_error",
|
|
4487
|
-
project_key=run_project_key,
|
|
4488
|
-
page_id=page_id_str,
|
|
4489
|
-
source_run_id=candidate.get("source_run_id"),
|
|
4490
|
-
error=str(exc),
|
|
4491
|
-
)
|
|
4492
|
-
else:
|
|
4493
|
-
logger.warning(
|
|
4494
|
-
"backfill_page_registry_repo_error",
|
|
4495
|
-
project_key=run_project_key,
|
|
4496
|
-
repo_key=repo_key,
|
|
4497
|
-
page_id=page_id_str,
|
|
4498
|
-
source_run_id=candidate.get("source_run_id"),
|
|
4499
|
-
error=str(exc),
|
|
4500
|
-
)
|
|
4501
|
-
errors += 1
|
|
4502
|
-
|
|
4503
|
-
summary = {
|
|
4504
|
-
"registered": registered,
|
|
4505
|
-
"skipped": skipped,
|
|
4506
|
-
"errors": errors,
|
|
4507
|
-
"cross_project_skipped": cross_project_skipped,
|
|
4508
|
-
}
|
|
4509
|
-
if dry_run:
|
|
4510
|
-
summary["mode"] = "dry_run"
|
|
4511
|
-
|
|
4512
|
-
_emit_json(summary)
|
|
4513
|
-
|
|
4514
|
-
if errors > 0:
|
|
4515
|
-
console.print(f"[yellow]Backfill completed with {errors} error(s).[/yellow]")
|
|
4516
|
-
else:
|
|
4517
|
-
msg = f"Backfill complete: {registered} registered, {skipped} skipped."
|
|
4518
|
-
if cross_project_skipped:
|
|
4519
|
-
msg += f" {cross_project_skipped} cross-project conflict(s) skipped."
|
|
4520
|
-
console.print(f"[green]{msg}[/green]")
|
|
4521
|
-
finally:
|
|
4522
|
-
store.close()
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
# ---------------------------------------------------------------------------
|
|
4526
|
-
# archive-stale-page
|
|
4527
|
-
# ---------------------------------------------------------------------------
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
def _build_archive_title(base_title: str, source_page_id: str) -> str:
|
|
4531
|
-
"""Build the canonical archive title for stale Confluence pages."""
|
|
4532
|
-
|
|
4533
|
-
return f"[ARCHIVED] {base_title} - {source_page_id}"
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
def _extract_archive_base_title(title: str, source_page_id: str) -> str:
|
|
4537
|
-
"""Normalize legacy/current archived titles back to their base title."""
|
|
4538
|
-
|
|
4539
|
-
normalized = title.strip()
|
|
4540
|
-
if normalized.startswith("[ARCHIVED] "):
|
|
4541
|
-
normalized = normalized.removeprefix("[ARCHIVED] ").strip()
|
|
4542
|
-
if normalized.startswith("ARCHIVED DUPLICATE - "):
|
|
4543
|
-
normalized = normalized.removeprefix("ARCHIVED DUPLICATE - ").strip()
|
|
4544
|
-
if normalized.endswith(f" - {source_page_id}"):
|
|
4545
|
-
normalized = normalized[: -len(f" - {source_page_id}")].rstrip()
|
|
4546
|
-
if normalized.endswith(" (duplicate)"):
|
|
4547
|
-
normalized = normalized[: -len(" (duplicate)")].rstrip()
|
|
4548
|
-
return normalized
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
def _page_parent_id(page_payload: dict[str, Any] | None) -> str | None:
|
|
4552
|
-
"""Extract the immediate parent page id from a Confluence page payload."""
|
|
4553
|
-
|
|
4554
|
-
if not isinstance(page_payload, dict):
|
|
4555
|
-
return None
|
|
4556
|
-
ancestors = page_payload.get("ancestors")
|
|
4557
|
-
if not isinstance(ancestors, list) or not ancestors:
|
|
4558
|
-
return None
|
|
4559
|
-
last = ancestors[-1]
|
|
4560
|
-
if not isinstance(last, dict):
|
|
4561
|
-
return None
|
|
4562
|
-
parent_id = last.get("id")
|
|
4563
|
-
return str(parent_id) if parent_id else None
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
@app.command(name="archive-stale-page")
|
|
4567
|
-
def archive_stale_page(
|
|
4568
|
-
page_id: str = typer.Option(..., "--page-id", help="Source Confluence page id or URL to archive."),
|
|
4569
|
-
archive_parent: str = typer.Option(
|
|
4570
|
-
..., "--archive-parent", help="Archive parent Confluence page id or URL that will receive the page."
|
|
4571
|
-
),
|
|
4572
|
-
expected_title: str = typer.Option(
|
|
4573
|
-
..., "--expected-title", help="Required current page title guard before rename/move."
|
|
4574
|
-
),
|
|
4575
|
-
dry_run: bool = typer.Option(
|
|
4576
|
-
False,
|
|
4577
|
-
"--dry-run/--no-dry-run",
|
|
4578
|
-
help="Report the intended archive operation without performing rename or move.",
|
|
4579
|
-
),
|
|
4580
|
-
confluence_server: str | None = typer.Option(
|
|
4581
|
-
None,
|
|
4582
|
-
"--confluence-server",
|
|
4583
|
-
"--server",
|
|
4584
|
-
help="Explicit Confluence server label (internal|external). If omitted, infer from page refs.",
|
|
4585
|
-
),
|
|
4586
|
-
timeout: int = typer.Option(30, "--timeout", help="Confluence client timeout in seconds."),
|
|
4587
|
-
retries: int = typer.Option(3, "--retries", help="Retry count for Confluence client calls."),
|
|
4588
|
-
) -> None:
|
|
4589
|
-
"""Archive a stale Confluence page by renaming it and reparenting it safely."""
|
|
4590
|
-
|
|
4591
|
-
_validate_required_credentials(command="archive-stale-page", require_confluence=True)
|
|
4592
|
-
|
|
4593
|
-
inferred_server = _parse_confluence_ref(archive_parent).server or _parse_confluence_ref(page_id).server
|
|
4594
|
-
resolved_server = confluence_server or inferred_server
|
|
4595
|
-
confluence = ConfluenceCliClient(server=resolved_server, timeout=timeout, retries=retries)
|
|
4596
|
-
|
|
4597
|
-
try:
|
|
4598
|
-
resolved_page_id = asyncio.run(_resolve_confluence_page_id(confluence, page_id))
|
|
4599
|
-
resolved_parent_id = asyncio.run(_resolve_confluence_page_id(confluence, archive_parent))
|
|
4600
|
-
|
|
4601
|
-
page_payload = asyncio.run(confluence.get_page(resolved_page_id, expand="version,space,ancestors"))
|
|
4602
|
-
if not isinstance(page_payload, dict) or not page_payload.get("title"):
|
|
4603
|
-
message = f"Could not resolve source page {resolved_page_id}."
|
|
4604
|
-
_emit_json({"status": "error", "error": message, "page_id": resolved_page_id})
|
|
4605
|
-
raise typer.Exit(1)
|
|
4606
|
-
|
|
4607
|
-
live_title = str(page_payload.get("title"))
|
|
4608
|
-
if live_title != expected_title:
|
|
4609
|
-
message = "Current page title does not match --expected-title."
|
|
4610
|
-
_emit_json(
|
|
4611
|
-
{
|
|
4612
|
-
"status": "error",
|
|
4613
|
-
"error": message,
|
|
4614
|
-
"page_id": resolved_page_id,
|
|
4615
|
-
"live_title": live_title,
|
|
4616
|
-
"expected_title": expected_title,
|
|
4617
|
-
}
|
|
4618
|
-
)
|
|
4619
|
-
raise typer.Exit(1)
|
|
4620
|
-
|
|
4621
|
-
archive_parent_payload = asyncio.run(confluence.get_page(resolved_parent_id, expand="version,space"))
|
|
4622
|
-
if not isinstance(archive_parent_payload, dict) or not archive_parent_payload.get("title"):
|
|
4623
|
-
message = f"Could not resolve archive parent page {resolved_parent_id}."
|
|
4624
|
-
_emit_json({"status": "error", "error": message, "archive_parent_id": resolved_parent_id})
|
|
4625
|
-
raise typer.Exit(1)
|
|
4626
|
-
|
|
4627
|
-
archive_parent_title = str(archive_parent_payload.get("title"))
|
|
4628
|
-
old_parent_id = _page_parent_id(page_payload)
|
|
4629
|
-
canonical_base_title = _extract_archive_base_title(live_title, resolved_page_id)
|
|
4630
|
-
canonical_title = _build_archive_title(canonical_base_title, resolved_page_id)
|
|
4631
|
-
payload: dict[str, Any] = {
|
|
4632
|
-
"status": "ok",
|
|
4633
|
-
"page_id": resolved_page_id,
|
|
4634
|
-
"archive_parent_id": resolved_parent_id,
|
|
4635
|
-
"archive_parent_title": archive_parent_title,
|
|
4636
|
-
"old_title": live_title,
|
|
4637
|
-
"new_title": canonical_title,
|
|
4638
|
-
"old_parent_id": old_parent_id,
|
|
4639
|
-
}
|
|
4640
|
-
|
|
4641
|
-
already_archived = live_title.startswith("[ARCHIVED]")
|
|
4642
|
-
if already_archived and old_parent_id != resolved_parent_id:
|
|
4643
|
-
message = "Refusing to normalize an archived page that is not already under the supplied archive parent."
|
|
4644
|
-
payload["status"] = "error"
|
|
4645
|
-
payload["error"] = message
|
|
4646
|
-
_emit_json(payload)
|
|
4647
|
-
raise typer.Exit(1)
|
|
4648
|
-
|
|
4649
|
-
if already_archived and live_title == canonical_title:
|
|
4650
|
-
payload["mode"] = "noop_already_canonical"
|
|
4651
|
-
_emit_json(payload)
|
|
4652
|
-
return
|
|
4653
|
-
|
|
4654
|
-
if dry_run:
|
|
4655
|
-
payload["mode"] = "dry_run"
|
|
4656
|
-
_emit_json(payload)
|
|
4657
|
-
return
|
|
4658
|
-
|
|
4659
|
-
asyncio.run(confluence.rename_page(resolved_page_id, canonical_title))
|
|
4660
|
-
if not already_archived:
|
|
4661
|
-
asyncio.run(confluence.move_page_to_parent(resolved_page_id, resolved_parent_id))
|
|
4662
|
-
|
|
4663
|
-
verified_page = asyncio.run(confluence.get_page(resolved_page_id, expand="version,space,ancestors"))
|
|
4664
|
-
verified_title = str(verified_page.get("title")) if isinstance(verified_page, dict) else None
|
|
4665
|
-
verified_parent_id = _page_parent_id(verified_page if isinstance(verified_page, dict) else None)
|
|
4666
|
-
payload["verified_title"] = verified_title
|
|
4667
|
-
payload["verified_parent_id"] = verified_parent_id
|
|
4668
|
-
|
|
4669
|
-
if verified_title != canonical_title or verified_parent_id != resolved_parent_id:
|
|
4670
|
-
payload["status"] = "error"
|
|
4671
|
-
payload["error"] = "Archive verification failed after rename/reparent."
|
|
4672
|
-
_emit_json(payload)
|
|
4673
|
-
raise typer.Exit(1)
|
|
4674
|
-
|
|
4675
|
-
payload["mode"] = "applied"
|
|
4676
|
-
_emit_json(payload)
|
|
4677
|
-
except typer.Exit:
|
|
4678
|
-
raise
|
|
4679
|
-
except Exception as exc:
|
|
4680
|
-
logger.exception("archive_stale_page_failed", page_id=page_id, archive_parent=archive_parent)
|
|
4681
|
-
_emit_json(
|
|
4682
|
-
{
|
|
4683
|
-
"status": "error",
|
|
4684
|
-
"error": str(exc),
|
|
4685
|
-
"page_id": page_id,
|
|
4686
|
-
"archive_parent": archive_parent,
|
|
4687
|
-
}
|
|
4688
|
-
)
|
|
4689
|
-
raise typer.Exit(1) from None
|