@ngocsangairvds/vsaf 3.2.14 → 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 +1 -48
- 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,3048 +0,0 @@
|
|
|
1
|
-
"""Publish audit results to Confluence using vds-cli.
|
|
2
|
-
|
|
3
|
-
TSK-170B: Enhanced with publish mode (update|create-only) and attachments index.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
import hashlib
|
|
10
|
-
import json
|
|
11
|
-
import re
|
|
12
|
-
import tempfile
|
|
13
|
-
import xml.etree.ElementTree as ET
|
|
14
|
-
from collections.abc import Awaitable, Callable
|
|
15
|
-
from dataclasses import dataclass, field
|
|
16
|
-
from enum import StrEnum
|
|
17
|
-
from html import escape
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
from typing import TYPE_CHECKING, Any, TypeVar
|
|
20
|
-
|
|
21
|
-
from structlog import get_logger
|
|
22
|
-
|
|
23
|
-
from vds_audit_orchestrator.errors import DataSourceError
|
|
24
|
-
from vds_audit_orchestrator.publishers.bitbucket_link_resolver import BitbucketLinkResolver
|
|
25
|
-
from vds_audit_orchestrator.publishers.checklist_renderer import FullChecklistRenderer
|
|
26
|
-
from vds_audit_orchestrator.publishers.macro_builder import ConfluenceMacroBuilder
|
|
27
|
-
from vds_audit_orchestrator.publishers.markdown_converter import to_storage
|
|
28
|
-
from vds_audit_orchestrator.publishers.priority_renderer import PriorityActionsRenderer
|
|
29
|
-
|
|
30
|
-
if TYPE_CHECKING:
|
|
31
|
-
from vds_audit_orchestrator.clients.confluence_cli_client import ConfluenceCliClient
|
|
32
|
-
from vds_audit_orchestrator.models.checklist import AuditChecklist
|
|
33
|
-
|
|
34
|
-
logger = get_logger(__name__)
|
|
35
|
-
|
|
36
|
-
T = TypeVar("T")
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class PublishMode(StrEnum):
|
|
40
|
-
"""TSK-170B: Publish mode for Confluence pages.
|
|
41
|
-
|
|
42
|
-
Controls behavior when a page with the same title already exists.
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
UPDATE = "update" # Update existing page or create new (default)
|
|
46
|
-
CREATE_ONLY = "create-only" # Fail if page already exists (safety mode)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class UpdateMode(StrEnum):
|
|
50
|
-
"""Upload update mode for run page refresh."""
|
|
51
|
-
|
|
52
|
-
FULL = "full"
|
|
53
|
-
INCREMENTAL = "incremental"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class SystemDocSyncPolicy(StrEnum):
|
|
57
|
-
"""Sync policy for hierarchy system-doc publishing."""
|
|
58
|
-
|
|
59
|
-
IF_CHANGED = "if-changed"
|
|
60
|
-
FORCE = "force"
|
|
61
|
-
DRY_RUN = "dry-run"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class HierarchyTarget(StrEnum):
|
|
65
|
-
"""Hierarchy target for deterministic publish placement."""
|
|
66
|
-
|
|
67
|
-
REPO = "repo"
|
|
68
|
-
PROJECT = "project"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@dataclass(slots=True)
|
|
72
|
-
class UploadResult:
|
|
73
|
-
page_id: str
|
|
74
|
-
page_url: str | None
|
|
75
|
-
attachments: list[str]
|
|
76
|
-
reused_existing_page: bool = False
|
|
77
|
-
attachments_index: list[dict[str, str]] = field(default_factory=list) # TSK-170B
|
|
78
|
-
update_mode: str = UpdateMode.FULL.value
|
|
79
|
-
effective_update_mode: str = UpdateMode.FULL.value
|
|
80
|
-
counters: dict[str, int | str] = field(default_factory=dict)
|
|
81
|
-
fallback_reason: str | None = None
|
|
82
|
-
project_root_page_id: str | None = None
|
|
83
|
-
audit_root_page_id: str | None = None
|
|
84
|
-
repo_root_page_id: str | None = None
|
|
85
|
-
repo_run_page_id: str | None = None
|
|
86
|
-
project_analysis_root_page_id: str | None = None
|
|
87
|
-
project_aggregate_page_id: str | None = None
|
|
88
|
-
sections_rendered: list[str] = field(default_factory=list) # TSK-940.75
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@dataclass(slots=True)
|
|
92
|
-
class _IncrementalPlan:
|
|
93
|
-
rows_changed: int
|
|
94
|
-
rows_unchanged: int
|
|
95
|
-
changed_attachments: set[str]
|
|
96
|
-
unchanged_attachments: set[str]
|
|
97
|
-
template_hash: str | None
|
|
98
|
-
evidence_hash: str | None
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
_SECTION_MARKERS: tuple[str, ...] = (
|
|
102
|
-
"summary",
|
|
103
|
-
"top_findings",
|
|
104
|
-
"priority_actions",
|
|
105
|
-
"checklist_core",
|
|
106
|
-
"checklist_details",
|
|
107
|
-
"readiness_executive_summary",
|
|
108
|
-
"readiness_severity_matrix",
|
|
109
|
-
"readiness_improvement_forecast",
|
|
110
|
-
"readiness_recommendations",
|
|
111
|
-
)
|
|
112
|
-
_SECTION_TAG_PREFIX = "VDS:SECTION"
|
|
113
|
-
_SYNC_META_PREFIX = "VDS:SYNC-META"
|
|
114
|
-
_VERSION_CONFLICT_MAX_RETRIES = 2
|
|
115
|
-
_SECTION_FALLBACK_START_PATTERNS: dict[str, str] = {
|
|
116
|
-
"summary": r"<h2>\s*Audit Checklist Summary\s*</h2>",
|
|
117
|
-
"top_findings": r"<h2>\s*Top Findings \(Checklist\).*?</h2>",
|
|
118
|
-
"checklist_core": r"<h2>\s*Checklist Tổng hợp\s*</h2>",
|
|
119
|
-
"checklist_details": (
|
|
120
|
-
r"(?:"
|
|
121
|
-
r"<ac:structured-macro[^>]*ac:name=\"expand\"[^>]*>\s*"
|
|
122
|
-
r"<ac:parameter[^>]*ac:name=\"title\">Chi tiết lý do và minh chứng</ac:parameter>"
|
|
123
|
-
r"|"
|
|
124
|
-
r"<h2>\s*Chi tiết lý do và minh chứng\s*</h2>"
|
|
125
|
-
r")"
|
|
126
|
-
),
|
|
127
|
-
}
|
|
128
|
-
_SECTION_FALLBACK_BOUNDARY_PATTERNS: dict[str, tuple[str, ...]] = {
|
|
129
|
-
"summary": (
|
|
130
|
-
_SECTION_FALLBACK_START_PATTERNS["top_findings"],
|
|
131
|
-
_SECTION_FALLBACK_START_PATTERNS["checklist_core"],
|
|
132
|
-
_SECTION_FALLBACK_START_PATTERNS["checklist_details"],
|
|
133
|
-
r"<h2>\s*Summary\s*</h2>",
|
|
134
|
-
r"<h2>\s*Provenance\s*</h2>",
|
|
135
|
-
),
|
|
136
|
-
"top_findings": (
|
|
137
|
-
_SECTION_FALLBACK_START_PATTERNS["checklist_core"],
|
|
138
|
-
_SECTION_FALLBACK_START_PATTERNS["checklist_details"],
|
|
139
|
-
r"<h2>\s*Summary\s*</h2>",
|
|
140
|
-
r"<h2>\s*Provenance\s*</h2>",
|
|
141
|
-
),
|
|
142
|
-
"checklist_core": (
|
|
143
|
-
_SECTION_FALLBACK_START_PATTERNS["checklist_details"],
|
|
144
|
-
r"<h[1-6]\b[^>]*>",
|
|
145
|
-
r"<h2>\s*Summary\s*</h2>",
|
|
146
|
-
r"<h2>\s*Provenance\s*</h2>",
|
|
147
|
-
),
|
|
148
|
-
"checklist_details": (
|
|
149
|
-
r"<h[1-6]\b[^>]*>",
|
|
150
|
-
r"<h2>\s*Summary\s*</h2>",
|
|
151
|
-
r"<h2>\s*Provenance\s*</h2>",
|
|
152
|
-
),
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
class ConfluencePublisher:
|
|
157
|
-
"""Publish markdown reports and attachments to Confluence."""
|
|
158
|
-
|
|
159
|
-
client: ConfluenceCliClient
|
|
160
|
-
_retries: int
|
|
161
|
-
_backoff: float
|
|
162
|
-
|
|
163
|
-
def __init__(
|
|
164
|
-
self,
|
|
165
|
-
client: ConfluenceCliClient,
|
|
166
|
-
*,
|
|
167
|
-
retries: int = 2,
|
|
168
|
-
backoff: float = 1.0,
|
|
169
|
-
state_dsn: str | None = None,
|
|
170
|
-
):
|
|
171
|
-
self.client = client
|
|
172
|
-
self._retries = retries
|
|
173
|
-
self._backoff = backoff
|
|
174
|
-
self._last_sections_rendered: list[str] = []
|
|
175
|
-
# P161-F TSK-161.24: Session-level caches to minimize Confluence API calls.
|
|
176
|
-
self._cached_space_key: str | None = None
|
|
177
|
-
self._verified_parents: set[str] = set()
|
|
178
|
-
# Phase 154: Registry integration — create sync state store when DSN available
|
|
179
|
-
if state_dsn:
|
|
180
|
-
from vds_audit_orchestrator.state.store import PostgresStateStore
|
|
181
|
-
|
|
182
|
-
self._state_store: Any | None = PostgresStateStore(state_dsn)
|
|
183
|
-
else:
|
|
184
|
-
self._state_store = None
|
|
185
|
-
|
|
186
|
-
# ------------------------------------------------------------------
|
|
187
|
-
# Phase 154: Page registry helpers
|
|
188
|
-
# ------------------------------------------------------------------
|
|
189
|
-
|
|
190
|
-
def _register_page(
|
|
191
|
-
self,
|
|
192
|
-
project_key: str,
|
|
193
|
-
repo_key: str,
|
|
194
|
-
hierarchy_role: str,
|
|
195
|
-
confluence_page_id: str,
|
|
196
|
-
confluence_page_title: str | None = None,
|
|
197
|
-
parent_page_id: str | None = None,
|
|
198
|
-
space_key: str | None = None,
|
|
199
|
-
) -> None:
|
|
200
|
-
"""Register a published page in the Postgres registry.
|
|
201
|
-
|
|
202
|
-
No-op when ``_state_store`` is None (backward-compat / test mode).
|
|
203
|
-
P161-F TSK-161.23: passes *space_key* through to the store when known.
|
|
204
|
-
"""
|
|
205
|
-
if self._state_store is None:
|
|
206
|
-
return
|
|
207
|
-
# P161-F TSK-161.23: resolve space_key from session cache when not
|
|
208
|
-
# explicitly provided by the caller.
|
|
209
|
-
_effective_space_key = space_key or self._cached_space_key
|
|
210
|
-
try:
|
|
211
|
-
self._state_store.register_published_page(
|
|
212
|
-
project_key=project_key,
|
|
213
|
-
repo_key=repo_key,
|
|
214
|
-
hierarchy_role=hierarchy_role,
|
|
215
|
-
confluence_page_id=confluence_page_id,
|
|
216
|
-
confluence_page_title=confluence_page_title,
|
|
217
|
-
parent_page_id=parent_page_id,
|
|
218
|
-
space_key=_effective_space_key,
|
|
219
|
-
)
|
|
220
|
-
except Exception:
|
|
221
|
-
logger.warning(
|
|
222
|
-
"page_registry_register_failed",
|
|
223
|
-
project_key=project_key,
|
|
224
|
-
repo_key=repo_key,
|
|
225
|
-
hierarchy_role=hierarchy_role,
|
|
226
|
-
confluence_page_id=confluence_page_id,
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
def _lookup_page(
|
|
230
|
-
self,
|
|
231
|
-
project_key: str,
|
|
232
|
-
repo_key: str,
|
|
233
|
-
hierarchy_role: str,
|
|
234
|
-
) -> dict[str, Any] | None:
|
|
235
|
-
"""Look up a page from the Postgres registry.
|
|
236
|
-
|
|
237
|
-
Returns None when ``_state_store`` is None (backward-compat / test mode).
|
|
238
|
-
"""
|
|
239
|
-
if self._state_store is None:
|
|
240
|
-
return None
|
|
241
|
-
try:
|
|
242
|
-
return self._state_store.lookup_published_page(
|
|
243
|
-
project_key=project_key,
|
|
244
|
-
repo_key=repo_key,
|
|
245
|
-
hierarchy_role=hierarchy_role,
|
|
246
|
-
)
|
|
247
|
-
except Exception:
|
|
248
|
-
logger.warning(
|
|
249
|
-
"page_registry_lookup_failed",
|
|
250
|
-
project_key=project_key,
|
|
251
|
-
repo_key=repo_key,
|
|
252
|
-
hierarchy_role=hierarchy_role,
|
|
253
|
-
)
|
|
254
|
-
return None
|
|
255
|
-
|
|
256
|
-
async def _page_exists(self, page_id: str) -> bool:
|
|
257
|
-
"""Check if a Confluence page exists by ID.
|
|
258
|
-
|
|
259
|
-
P161-F TSK-161.24: Returns True immediately for already-verified parents
|
|
260
|
-
within the same publisher session (0 API calls on subsequent checks).
|
|
261
|
-
|
|
262
|
-
Uses ``_retry`` with fail-open behavior: returns False on any error
|
|
263
|
-
so the caller falls through to CQL-based resolution.
|
|
264
|
-
"""
|
|
265
|
-
# Session-level verified parents cache
|
|
266
|
-
if page_id in self._verified_parents:
|
|
267
|
-
return True
|
|
268
|
-
try:
|
|
269
|
-
page = await self._retry(
|
|
270
|
-
lambda: self.client.get_content_page(page_id),
|
|
271
|
-
page_id=page_id,
|
|
272
|
-
action="page_exists_check",
|
|
273
|
-
)
|
|
274
|
-
exists = page is not None and bool(page.get("id"))
|
|
275
|
-
if exists:
|
|
276
|
-
self._verified_parents.add(page_id)
|
|
277
|
-
return exists
|
|
278
|
-
except Exception:
|
|
279
|
-
logger.warning("page_exists_check_failed", page_id=page_id)
|
|
280
|
-
return False
|
|
281
|
-
|
|
282
|
-
async def _get_page_parent_id(self, page_id: str) -> str | None:
|
|
283
|
-
"""Return the immediate parent page ID from Confluence ancestors.
|
|
284
|
-
|
|
285
|
-
Phase 156: Used to verify that a page is under the expected parent
|
|
286
|
-
before deciding whether to move it. Returns ``None`` on any failure
|
|
287
|
-
so callers fall through gracefully.
|
|
288
|
-
"""
|
|
289
|
-
try:
|
|
290
|
-
page = await self._retry(
|
|
291
|
-
lambda: self.client.get_page(page_id, expand="ancestors"),
|
|
292
|
-
page_id=page_id,
|
|
293
|
-
action="get_page_ancestors",
|
|
294
|
-
)
|
|
295
|
-
if isinstance(page, dict):
|
|
296
|
-
ancestors = page.get("ancestors")
|
|
297
|
-
if isinstance(ancestors, list) and ancestors:
|
|
298
|
-
last_ancestor = ancestors[-1]
|
|
299
|
-
if isinstance(last_ancestor, dict) and last_ancestor.get("id"):
|
|
300
|
-
return str(last_ancestor["id"])
|
|
301
|
-
except Exception:
|
|
302
|
-
logger.warning("get_page_parent_id_failed", page_id=page_id)
|
|
303
|
-
return None
|
|
304
|
-
|
|
305
|
-
async def _move_page_if_parent_mismatched(
|
|
306
|
-
self,
|
|
307
|
-
page_id: str,
|
|
308
|
-
expected_parent_id: str,
|
|
309
|
-
) -> bool:
|
|
310
|
-
"""Check if *page_id* lives under *expected_parent_id*; move it if not.
|
|
311
|
-
|
|
312
|
-
Phase 156: After ``update_run_page`` updates the body, this method
|
|
313
|
-
verifies the Confluence parent matches the hierarchy target and
|
|
314
|
-
reparents the page when they diverge.
|
|
315
|
-
|
|
316
|
-
Returns ``True`` when the page was actually moved, ``False`` otherwise
|
|
317
|
-
(already correct or move failed).
|
|
318
|
-
"""
|
|
319
|
-
actual_parent_id = await self._get_page_parent_id(page_id)
|
|
320
|
-
if not actual_parent_id or actual_parent_id == expected_parent_id:
|
|
321
|
-
return False
|
|
322
|
-
|
|
323
|
-
logger.info(
|
|
324
|
-
"page_parent_mismatch_detected",
|
|
325
|
-
page_id=page_id,
|
|
326
|
-
actual_parent_id=actual_parent_id,
|
|
327
|
-
expected_parent_id=expected_parent_id,
|
|
328
|
-
)
|
|
329
|
-
try:
|
|
330
|
-
await self._retry(
|
|
331
|
-
lambda: self.client.move_page_to_parent(page_id, expected_parent_id),
|
|
332
|
-
page_id=page_id,
|
|
333
|
-
new_parent_id=expected_parent_id,
|
|
334
|
-
action="move_page_to_parent",
|
|
335
|
-
)
|
|
336
|
-
logger.info(
|
|
337
|
-
"page_moved_to_correct_parent",
|
|
338
|
-
page_id=page_id,
|
|
339
|
-
old_parent_id=actual_parent_id,
|
|
340
|
-
new_parent_id=expected_parent_id,
|
|
341
|
-
)
|
|
342
|
-
return True
|
|
343
|
-
except Exception:
|
|
344
|
-
logger.warning(
|
|
345
|
-
"page_move_failed",
|
|
346
|
-
page_id=page_id,
|
|
347
|
-
expected_parent_id=expected_parent_id,
|
|
348
|
-
actual_parent_id=actual_parent_id,
|
|
349
|
-
)
|
|
350
|
-
return False
|
|
351
|
-
|
|
352
|
-
def _delete_registry_entry(
|
|
353
|
-
self,
|
|
354
|
-
project_key: str,
|
|
355
|
-
repo_key: str,
|
|
356
|
-
hierarchy_role: str,
|
|
357
|
-
) -> None:
|
|
358
|
-
"""Remove a stale registry entry.
|
|
359
|
-
|
|
360
|
-
No-op when ``_state_store`` is None.
|
|
361
|
-
"""
|
|
362
|
-
if self._state_store is None:
|
|
363
|
-
return
|
|
364
|
-
try:
|
|
365
|
-
self._state_store.delete_published_page(
|
|
366
|
-
project_key=project_key,
|
|
367
|
-
repo_key=repo_key,
|
|
368
|
-
hierarchy_role=hierarchy_role,
|
|
369
|
-
)
|
|
370
|
-
except Exception:
|
|
371
|
-
logger.warning(
|
|
372
|
-
"page_registry_delete_failed",
|
|
373
|
-
project_key=project_key,
|
|
374
|
-
repo_key=repo_key,
|
|
375
|
-
hierarchy_role=hierarchy_role,
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
async def _retry(
|
|
379
|
-
self,
|
|
380
|
-
fn: Callable[[], Awaitable[T]],
|
|
381
|
-
*,
|
|
382
|
-
retries: int | None = None,
|
|
383
|
-
base_delay: float | None = None,
|
|
384
|
-
max_total_api_calls: int = 3,
|
|
385
|
-
**log_ctx: Any,
|
|
386
|
-
) -> T:
|
|
387
|
-
"""Retry vds-cli backed calls (Confluence can be flaky/slow).
|
|
388
|
-
|
|
389
|
-
P161-F TSK-161.25: ``max_total_api_calls`` caps the total API calls
|
|
390
|
-
for a single logical operation to prevent retry compounding when the
|
|
391
|
-
caller already has its own retry layer.
|
|
392
|
-
"""
|
|
393
|
-
retries = self._retries if retries is None else retries
|
|
394
|
-
# Cap retries so total attempts never exceed max_total_api_calls.
|
|
395
|
-
retries = min(retries, max_total_api_calls - 1)
|
|
396
|
-
base_delay = self._backoff if base_delay is None else base_delay
|
|
397
|
-
last_error: str | None = None
|
|
398
|
-
for attempt in range(retries + 1):
|
|
399
|
-
try:
|
|
400
|
-
return await fn()
|
|
401
|
-
except DataSourceError as exc:
|
|
402
|
-
last_error = str(exc)
|
|
403
|
-
# Duplicate-title errors are deterministic; retrying just burns time and
|
|
404
|
-
# makes the "search existing page" fallback slower.
|
|
405
|
-
action = str(log_ctx.get("action") or "").lower()
|
|
406
|
-
if "already exists" in (last_error or "").lower() and action in (
|
|
407
|
-
"create_page",
|
|
408
|
-
"create_or_update_page",
|
|
409
|
-
):
|
|
410
|
-
raise
|
|
411
|
-
if attempt >= retries:
|
|
412
|
-
logger.warning("confluence_request_failed", attempt=attempt + 1, error=last_error, **log_ctx)
|
|
413
|
-
raise
|
|
414
|
-
delay = base_delay * (2**attempt)
|
|
415
|
-
logger.warning(
|
|
416
|
-
"confluence_request_retry",
|
|
417
|
-
attempt=attempt + 1,
|
|
418
|
-
retries=retries,
|
|
419
|
-
delay_seconds=delay,
|
|
420
|
-
error=last_error,
|
|
421
|
-
**log_ctx,
|
|
422
|
-
)
|
|
423
|
-
await asyncio.sleep(delay)
|
|
424
|
-
raise DataSourceError(f"Confluence request failed: {last_error}", context=log_ctx)
|
|
425
|
-
|
|
426
|
-
def _is_permission_error(self, error: Exception) -> bool:
|
|
427
|
-
"""Check if an error indicates a permission-denied condition.
|
|
428
|
-
|
|
429
|
-
TSK-127.4: Detect permission errors for attachment operations.
|
|
430
|
-
|
|
431
|
-
Args:
|
|
432
|
-
error: Exception to check.
|
|
433
|
-
|
|
434
|
-
Returns:
|
|
435
|
-
True if the error indicates permission was denied.
|
|
436
|
-
"""
|
|
437
|
-
error_str = str(error).lower()
|
|
438
|
-
# Check for HTTP 403 status code
|
|
439
|
-
if hasattr(error, "status_code") and error.status_code == 403:
|
|
440
|
-
return True
|
|
441
|
-
# Check for common permission-denied keywords
|
|
442
|
-
permission_keywords = ("forbidden", "permission", "unauthorized", "not authorized", "access denied")
|
|
443
|
-
return any(keyword in error_str for keyword in permission_keywords)
|
|
444
|
-
|
|
445
|
-
def _build_permission_error_message(self, operation: str, page_id: str) -> str:
|
|
446
|
-
"""Build a clear permission-denied error message with --page-only suggestion.
|
|
447
|
-
|
|
448
|
-
TSK-127.4: Provide actionable guidance for permission errors.
|
|
449
|
-
|
|
450
|
-
Args:
|
|
451
|
-
operation: The operation that failed (e.g., 'upload_attachment', 'list_attachments').
|
|
452
|
-
page_id: The Confluence page ID.
|
|
453
|
-
|
|
454
|
-
Returns:
|
|
455
|
-
Formatted error message with actionable guidance.
|
|
456
|
-
"""
|
|
457
|
-
return (
|
|
458
|
-
f"Permission denied during {operation} on page {page_id}. "
|
|
459
|
-
f"The Confluence API returned a permission error, indicating insufficient permissions "
|
|
460
|
-
f"for attachment operations. Use --page-only to skip attachments (requires only edit permission)."
|
|
461
|
-
)
|
|
462
|
-
|
|
463
|
-
@staticmethod
|
|
464
|
-
def extract_page_id(url_or_id: str) -> str:
|
|
465
|
-
if "pageId=" in url_or_id:
|
|
466
|
-
return url_or_id.split("pageId=")[1].split("&", maxsplit=1)[0]
|
|
467
|
-
return url_or_id
|
|
468
|
-
|
|
469
|
-
async def _get_space_key(self, page_id: str) -> str:
|
|
470
|
-
# P161-F TSK-161.24: Return cached space_key if already resolved.
|
|
471
|
-
if self._cached_space_key is not None:
|
|
472
|
-
return self._cached_space_key
|
|
473
|
-
page = await self._retry(
|
|
474
|
-
lambda: self.client.get_content_page(page_id, expand="space"),
|
|
475
|
-
page_id=page_id,
|
|
476
|
-
action="get_content_page",
|
|
477
|
-
)
|
|
478
|
-
if not page:
|
|
479
|
-
raise ValueError(f"Page not found: {page_id}")
|
|
480
|
-
space = page.get("space") or {}
|
|
481
|
-
space_key = space.get("key")
|
|
482
|
-
if not space_key:
|
|
483
|
-
raise ValueError(f"Space key missing for page {page_id}")
|
|
484
|
-
self._cached_space_key = str(space_key)
|
|
485
|
-
return self._cached_space_key
|
|
486
|
-
|
|
487
|
-
async def ensure_audit_history(self, parent_id: str, *, title: str = "Audit History") -> str:
|
|
488
|
-
# Confluence enforces unique page titles per space in many installations.
|
|
489
|
-
# "Audit History" may already exist in the space, even under a different parent.
|
|
490
|
-
# Prefer the simple title under the requested parent when possible, otherwise
|
|
491
|
-
# fall back to a parent-scoped title.
|
|
492
|
-
scoped_title = f"{title} - {parent_id}"
|
|
493
|
-
|
|
494
|
-
for candidate in (title, scoped_title):
|
|
495
|
-
cql = f'parent={parent_id} and title="{candidate}" and type=page'
|
|
496
|
-
results = await self._retry(
|
|
497
|
-
lambda cql=cql: self.client.search_cql(cql, limit=1),
|
|
498
|
-
cql=cql,
|
|
499
|
-
action="search_cql",
|
|
500
|
-
)
|
|
501
|
-
if results and results[0].get("id"):
|
|
502
|
-
return str(results[0].get("id"))
|
|
503
|
-
|
|
504
|
-
space_key = await self._get_space_key(parent_id)
|
|
505
|
-
body_path = Path("/tmp/audit-history-body.html")
|
|
506
|
-
# Enhance parent page with a dynamic dashboard of child audit results
|
|
507
|
-
dashboard_html = (
|
|
508
|
-
"<p>Danh sách các báo cáo đánh giá (tự động cập nhật):</p>"
|
|
509
|
-
'<ac:structured-macro ac:name="detailssummary">'
|
|
510
|
-
'<ac:parameter ac:name="cql">parent = currentContent() and type = "page"</ac:parameter>'
|
|
511
|
-
'<ac:parameter ac:name="columns">title,Mức độ trưởng thành,Điểm tổng,Phát hiện Critical/High,✅ Đạt,❌ Không đạt</ac:parameter>'
|
|
512
|
-
'<ac:parameter ac:name="sortBy">created</ac:parameter>'
|
|
513
|
-
'<ac:parameter ac:name="reverse">true</ac:parameter>'
|
|
514
|
-
"</ac:structured-macro>"
|
|
515
|
-
)
|
|
516
|
-
body_path.write_text(dashboard_html, encoding="utf-8")
|
|
517
|
-
|
|
518
|
-
async def _search_by_title_in_space(page_title: str) -> str | None:
|
|
519
|
-
cql = f'space="{space_key}" and title="{page_title}" and type=page'
|
|
520
|
-
results = await self._retry(
|
|
521
|
-
lambda cql=cql: self.client.search_cql(cql, limit=1),
|
|
522
|
-
cql=cql,
|
|
523
|
-
action="search_cql",
|
|
524
|
-
)
|
|
525
|
-
if results and results[0].get("id"):
|
|
526
|
-
return str(results[0].get("id"))
|
|
527
|
-
return None
|
|
528
|
-
|
|
529
|
-
for candidate in (title, scoped_title):
|
|
530
|
-
try:
|
|
531
|
-
created = await self._retry(
|
|
532
|
-
lambda candidate=candidate: self.client.create_page(
|
|
533
|
-
space_key=space_key,
|
|
534
|
-
title=candidate,
|
|
535
|
-
body_file=body_path,
|
|
536
|
-
parent_id=parent_id,
|
|
537
|
-
),
|
|
538
|
-
space_key=space_key,
|
|
539
|
-
title=candidate,
|
|
540
|
-
parent_id=parent_id,
|
|
541
|
-
action="create_page",
|
|
542
|
-
)
|
|
543
|
-
if created.get("id"):
|
|
544
|
-
return str(created.get("id"))
|
|
545
|
-
except Exception as exc:
|
|
546
|
-
msg = str(exc).lower()
|
|
547
|
-
# If Confluence rejects duplicates, locate the existing page by title.
|
|
548
|
-
if "already exists" in msg:
|
|
549
|
-
existing = await _search_by_title_in_space(candidate)
|
|
550
|
-
if existing:
|
|
551
|
-
return existing
|
|
552
|
-
continue
|
|
553
|
-
# Timeouts are ambiguous: the page may have been created server-side.
|
|
554
|
-
if "timed out" in msg or "timeout" in msg:
|
|
555
|
-
existing = await _search_by_title_in_space(candidate)
|
|
556
|
-
if existing:
|
|
557
|
-
return existing
|
|
558
|
-
continue
|
|
559
|
-
raise
|
|
560
|
-
|
|
561
|
-
raise ValueError(f"Failed to ensure audit history page under parent {parent_id}")
|
|
562
|
-
|
|
563
|
-
async def _find_child_page(self, parent_id: str, title: str) -> dict | None:
|
|
564
|
-
cql = f'parent={parent_id} and title="{title}" and type=page'
|
|
565
|
-
results = await self._retry(
|
|
566
|
-
lambda cql=cql: self.client.search_cql(cql, limit=1),
|
|
567
|
-
cql=cql,
|
|
568
|
-
action="search_cql",
|
|
569
|
-
)
|
|
570
|
-
if results:
|
|
571
|
-
return results[0]
|
|
572
|
-
return None
|
|
573
|
-
|
|
574
|
-
async def _find_page_in_space(self, space_key: str, title: str) -> dict | None:
|
|
575
|
-
cql = f'space="{space_key}" and title="{title}" and type=page'
|
|
576
|
-
results = await self._retry(
|
|
577
|
-
lambda cql=cql: self.client.search_cql(cql, limit=5),
|
|
578
|
-
cql=cql,
|
|
579
|
-
action="search_cql",
|
|
580
|
-
)
|
|
581
|
-
if results:
|
|
582
|
-
return results[0]
|
|
583
|
-
return None
|
|
584
|
-
|
|
585
|
-
@staticmethod
|
|
586
|
-
def _normalize_hierarchy_key(value: str | None, *, fallback: str) -> str:
|
|
587
|
-
raw = str(value or "").strip().lower()
|
|
588
|
-
if not raw:
|
|
589
|
-
raw = fallback.strip().lower()
|
|
590
|
-
normalized = re.sub(r"[^a-z0-9]+", "-", raw).strip("-")
|
|
591
|
-
return normalized or "default"
|
|
592
|
-
|
|
593
|
-
@staticmethod
|
|
594
|
-
def _build_project_display_label(
|
|
595
|
-
project_name_hint: str | None,
|
|
596
|
-
project_storage_key: str | None,
|
|
597
|
-
fallback_key: str,
|
|
598
|
-
) -> str:
|
|
599
|
-
"""Build a human-readable project label for page titles.
|
|
600
|
-
|
|
601
|
-
When a human-readable name is available and differs from the storage key,
|
|
602
|
-
returns ``"{name} ({psk})"`` so titles include both the readable name and
|
|
603
|
-
the unique identifier. Falls back to just the normalised key when no
|
|
604
|
-
distinct name is provided.
|
|
605
|
-
|
|
606
|
-
Examples::
|
|
607
|
-
|
|
608
|
-
_build_project_display_label("Core Payment", "88718943", "88718943")
|
|
609
|
-
# → "Core Payment (88718943)"
|
|
610
|
-
|
|
611
|
-
_build_project_display_label(None, "88718943", "88718943")
|
|
612
|
-
# → "88718943"
|
|
613
|
-
|
|
614
|
-
_build_project_display_label("wf-project", "wf-project", "wf-project")
|
|
615
|
-
# → "wf-project" (name == key → no duplication)
|
|
616
|
-
"""
|
|
617
|
-
psk = str(project_storage_key or fallback_key).strip() or fallback_key
|
|
618
|
-
name = str(project_name_hint or "").strip()
|
|
619
|
-
if name and name.lower() != psk.lower():
|
|
620
|
-
return f"{name} ({psk})"
|
|
621
|
-
return psk
|
|
622
|
-
|
|
623
|
-
async def _ensure_hierarchy_node(
|
|
624
|
-
self,
|
|
625
|
-
*,
|
|
626
|
-
parent_id: str,
|
|
627
|
-
title: str,
|
|
628
|
-
body_html: str,
|
|
629
|
-
project_key: str = "",
|
|
630
|
-
hierarchy_role: str = "",
|
|
631
|
-
) -> str:
|
|
632
|
-
# Phase 154: Step 1 — Registry lookup (fast path)
|
|
633
|
-
if project_key and hierarchy_role:
|
|
634
|
-
entry = self._lookup_page(project_key, "", hierarchy_role)
|
|
635
|
-
if entry:
|
|
636
|
-
page_id = str(entry["confluence_page_id"])
|
|
637
|
-
if await self._page_exists(page_id):
|
|
638
|
-
# Phase 156: Verify the registered parent matches the
|
|
639
|
-
# expected parent. If the hierarchy root has changed
|
|
640
|
-
# (e.g. project re-parented), move the node and update
|
|
641
|
-
# the registry so future lookups are consistent.
|
|
642
|
-
registered_parent = str(entry.get("parent_page_id") or "")
|
|
643
|
-
expected_parent = str(parent_id)
|
|
644
|
-
if registered_parent != expected_parent:
|
|
645
|
-
actual_parent = await self._get_page_parent_id(page_id)
|
|
646
|
-
moved = False
|
|
647
|
-
if actual_parent != expected_parent:
|
|
648
|
-
moved = await self._move_page_if_parent_mismatched(page_id, parent_id)
|
|
649
|
-
if actual_parent == expected_parent or moved:
|
|
650
|
-
self._register_page(
|
|
651
|
-
project_key,
|
|
652
|
-
"",
|
|
653
|
-
hierarchy_role,
|
|
654
|
-
page_id,
|
|
655
|
-
title,
|
|
656
|
-
parent_id,
|
|
657
|
-
)
|
|
658
|
-
return page_id
|
|
659
|
-
# Page was deleted externally — purge stale entry and fall through
|
|
660
|
-
self._delete_registry_entry(project_key, "", hierarchy_role)
|
|
661
|
-
|
|
662
|
-
# Step 2 — CQL fallback (existing behavior)
|
|
663
|
-
existing = await self._find_child_page(parent_id, title)
|
|
664
|
-
if existing and existing.get("id"):
|
|
665
|
-
found_id = str(existing.get("id"))
|
|
666
|
-
self._register_page(project_key, "", hierarchy_role, found_id, title, parent_id)
|
|
667
|
-
return found_id
|
|
668
|
-
|
|
669
|
-
space_key = await self._get_space_key(parent_id)
|
|
670
|
-
existing_in_space = await self._find_page_in_space(space_key, title)
|
|
671
|
-
if existing_in_space and existing_in_space.get("id"):
|
|
672
|
-
found_id = str(existing_in_space.get("id"))
|
|
673
|
-
self._register_page(project_key, "", hierarchy_role, found_id, title, parent_id)
|
|
674
|
-
return found_id
|
|
675
|
-
|
|
676
|
-
# Step 3 — Create new
|
|
677
|
-
temp_path: Path | None = None
|
|
678
|
-
try:
|
|
679
|
-
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as handle:
|
|
680
|
-
handle.write(body_html)
|
|
681
|
-
temp_path = Path(handle.name)
|
|
682
|
-
|
|
683
|
-
created = await self._retry(
|
|
684
|
-
lambda: self.client.create_page(
|
|
685
|
-
space_key=space_key,
|
|
686
|
-
title=title,
|
|
687
|
-
body_file=temp_path,
|
|
688
|
-
parent_id=parent_id,
|
|
689
|
-
),
|
|
690
|
-
space_key=space_key,
|
|
691
|
-
title=title,
|
|
692
|
-
parent_id=parent_id,
|
|
693
|
-
action="create_page",
|
|
694
|
-
)
|
|
695
|
-
if created.get("id"):
|
|
696
|
-
created_id = str(created.get("id"))
|
|
697
|
-
self._register_page(project_key, "", hierarchy_role, created_id, title, parent_id)
|
|
698
|
-
return created_id
|
|
699
|
-
except Exception as exc:
|
|
700
|
-
message = str(exc).lower()
|
|
701
|
-
if "already exists" in message or "timed out" in message or "timeout" in message:
|
|
702
|
-
for _attempt in range(3):
|
|
703
|
-
existing = await self._find_child_page(parent_id, title)
|
|
704
|
-
if existing and existing.get("id"):
|
|
705
|
-
found_id = str(existing.get("id"))
|
|
706
|
-
self._register_page(project_key, "", hierarchy_role, found_id, title, parent_id)
|
|
707
|
-
return found_id
|
|
708
|
-
existing_in_space = await self._find_page_in_space(space_key, title)
|
|
709
|
-
if existing_in_space and existing_in_space.get("id"):
|
|
710
|
-
found_id = str(existing_in_space.get("id"))
|
|
711
|
-
self._register_page(project_key, "", hierarchy_role, found_id, title, parent_id)
|
|
712
|
-
return found_id
|
|
713
|
-
await asyncio.sleep(0.5)
|
|
714
|
-
if "already exists" in message:
|
|
715
|
-
raise ValueError(
|
|
716
|
-
f"Hierarchy node title conflict for '{title}' under parent '{parent_id}'."
|
|
717
|
-
) from exc
|
|
718
|
-
raise
|
|
719
|
-
finally:
|
|
720
|
-
if temp_path and temp_path.exists():
|
|
721
|
-
temp_path.unlink()
|
|
722
|
-
|
|
723
|
-
raise ValueError(f"Failed to ensure hierarchy node '{title}' under parent {parent_id}")
|
|
724
|
-
|
|
725
|
-
async def _resolve_publish_parent(
|
|
726
|
-
self,
|
|
727
|
-
*,
|
|
728
|
-
parent_id: str,
|
|
729
|
-
page_title: str,
|
|
730
|
-
project_storage_key: str | None,
|
|
731
|
-
repo_storage_key: str | None,
|
|
732
|
-
hierarchy_target: str | None,
|
|
733
|
-
project_name_hint: str | None = None,
|
|
734
|
-
) -> tuple[str, dict[str, str]]:
|
|
735
|
-
resolved_target: HierarchyTarget
|
|
736
|
-
if hierarchy_target:
|
|
737
|
-
target_value = hierarchy_target.strip().lower().replace("_", "-")
|
|
738
|
-
try:
|
|
739
|
-
resolved_target = HierarchyTarget(target_value)
|
|
740
|
-
except ValueError as exc:
|
|
741
|
-
raise ValueError(
|
|
742
|
-
f"Invalid hierarchy target: {hierarchy_target}. Use '{HierarchyTarget.REPO.value}' or "
|
|
743
|
-
f"'{HierarchyTarget.PROJECT.value}'."
|
|
744
|
-
) from exc
|
|
745
|
-
elif repo_storage_key:
|
|
746
|
-
resolved_target = HierarchyTarget.REPO
|
|
747
|
-
else:
|
|
748
|
-
resolved_target = HierarchyTarget.PROJECT
|
|
749
|
-
|
|
750
|
-
project_root_id = parent_id
|
|
751
|
-
project_key = self._normalize_hierarchy_key(project_storage_key, fallback=project_root_id)
|
|
752
|
-
lineage: dict[str, str] = {"project_root_page_id": project_root_id}
|
|
753
|
-
|
|
754
|
-
if resolved_target == HierarchyTarget.PROJECT:
|
|
755
|
-
display_label = self._build_project_display_label(project_name_hint, project_storage_key, project_key)
|
|
756
|
-
analysis_title = f"Project Analysis - {display_label}"
|
|
757
|
-
analysis_root_id = await self._ensure_hierarchy_node(
|
|
758
|
-
parent_id=project_root_id,
|
|
759
|
-
title=analysis_title,
|
|
760
|
-
body_html=(
|
|
761
|
-
"<p>Deterministic project analysis root for aggregate audit reports.</p>"
|
|
762
|
-
f"<p>project_storage_key: {escape(project_key)}</p>"
|
|
763
|
-
),
|
|
764
|
-
project_key=project_key,
|
|
765
|
-
hierarchy_role="analysis_root",
|
|
766
|
-
)
|
|
767
|
-
lineage["project_analysis_root_page_id"] = analysis_root_id
|
|
768
|
-
lineage["project_analysis_title"] = analysis_title
|
|
769
|
-
return analysis_root_id, lineage
|
|
770
|
-
|
|
771
|
-
str(project_storage_key or project_key).strip() or project_key
|
|
772
|
-
project_page_id = str(parent_id).strip() or project_root_id
|
|
773
|
-
display_label = self._build_project_display_label(project_name_hint, project_storage_key, project_key)
|
|
774
|
-
audit_root_title = f"Project Audit - {display_label}"
|
|
775
|
-
audit_root_id = await self._ensure_hierarchy_node(
|
|
776
|
-
parent_id=project_root_id,
|
|
777
|
-
title=audit_root_title,
|
|
778
|
-
body_html=(
|
|
779
|
-
"<p>Deterministic audit root for repository reports.</p>"
|
|
780
|
-
f"<p>project_storage_key: {escape(project_key)}</p>"
|
|
781
|
-
f"<p>project_page_id: {escape(project_page_id)}</p>"
|
|
782
|
-
),
|
|
783
|
-
project_key=project_key,
|
|
784
|
-
hierarchy_role="audit_root",
|
|
785
|
-
)
|
|
786
|
-
repo_key = self._normalize_hierarchy_key(repo_storage_key, fallback=page_title)
|
|
787
|
-
lineage["audit_root_page_id"] = audit_root_id
|
|
788
|
-
lineage["repo_root_page_id"] = audit_root_id
|
|
789
|
-
lineage["repo_root_title"] = audit_root_title
|
|
790
|
-
lineage["repo_report_title"] = page_title
|
|
791
|
-
lineage["repo_storage_key"] = repo_key
|
|
792
|
-
return audit_root_id, lineage
|
|
793
|
-
|
|
794
|
-
async def create_run_page(
|
|
795
|
-
self,
|
|
796
|
-
*,
|
|
797
|
-
parent_id: str,
|
|
798
|
-
title: str,
|
|
799
|
-
report_dir: Path,
|
|
800
|
-
markdown_path: Path,
|
|
801
|
-
thread_id: str | None = None,
|
|
802
|
-
priority_actions_limit: int = 10,
|
|
803
|
-
checklist_collapsed: bool = False,
|
|
804
|
-
) -> str:
|
|
805
|
-
space_key = await self._get_space_key(parent_id)
|
|
806
|
-
body_html = self._build_run_body_html(
|
|
807
|
-
report_dir=report_dir,
|
|
808
|
-
markdown_path=markdown_path,
|
|
809
|
-
thread_id=thread_id,
|
|
810
|
-
priority_actions_limit=priority_actions_limit,
|
|
811
|
-
checklist_collapsed=checklist_collapsed,
|
|
812
|
-
)
|
|
813
|
-
body_path = markdown_path.with_suffix(".storage.html")
|
|
814
|
-
body_path.write_text(body_html, encoding="utf-8")
|
|
815
|
-
upserted = await self._retry(
|
|
816
|
-
lambda: self.client.create_or_update_page(
|
|
817
|
-
space_key=space_key,
|
|
818
|
-
title=title,
|
|
819
|
-
body_file=body_path,
|
|
820
|
-
parent_id=parent_id,
|
|
821
|
-
),
|
|
822
|
-
space_key=space_key,
|
|
823
|
-
title=title,
|
|
824
|
-
parent_id=parent_id,
|
|
825
|
-
action="create_or_update_page",
|
|
826
|
-
)
|
|
827
|
-
upserted_id = upserted.get("id") if isinstance(upserted, dict) else None
|
|
828
|
-
if upserted_id is None:
|
|
829
|
-
raise ValueError(f"Confluence create_or_update_page returned no page id for title '{title}'")
|
|
830
|
-
return str(upserted_id)
|
|
831
|
-
|
|
832
|
-
async def update_run_page(
|
|
833
|
-
self,
|
|
834
|
-
*,
|
|
835
|
-
page_id: str,
|
|
836
|
-
title: str,
|
|
837
|
-
report_dir: Path,
|
|
838
|
-
markdown_path: Path,
|
|
839
|
-
thread_id: str | None = None,
|
|
840
|
-
priority_actions_limit: int = 10,
|
|
841
|
-
checklist_collapsed: bool = False,
|
|
842
|
-
) -> str:
|
|
843
|
-
body_html = self._build_run_body_html(
|
|
844
|
-
report_dir=report_dir,
|
|
845
|
-
markdown_path=markdown_path,
|
|
846
|
-
thread_id=thread_id,
|
|
847
|
-
priority_actions_limit=priority_actions_limit,
|
|
848
|
-
checklist_collapsed=checklist_collapsed,
|
|
849
|
-
)
|
|
850
|
-
body_path = markdown_path.with_suffix(".storage.html")
|
|
851
|
-
body_path.write_text(body_html, encoding="utf-8")
|
|
852
|
-
page = await self._retry(
|
|
853
|
-
lambda: self.client.get_content_page(page_id, expand="title,version"),
|
|
854
|
-
page_id=page_id,
|
|
855
|
-
action="get_content_page",
|
|
856
|
-
)
|
|
857
|
-
version = None
|
|
858
|
-
if isinstance(page, dict):
|
|
859
|
-
current_version = page.get("version") or {}
|
|
860
|
-
if isinstance(current_version, dict):
|
|
861
|
-
number = current_version.get("number")
|
|
862
|
-
if isinstance(number, int):
|
|
863
|
-
version = number + 1
|
|
864
|
-
resolved_title = self._resolve_update_title(page if isinstance(page, dict) else {}, title)
|
|
865
|
-
await self._retry(
|
|
866
|
-
lambda: self.client.update_page(
|
|
867
|
-
page_id,
|
|
868
|
-
title=resolved_title,
|
|
869
|
-
body_file=body_path,
|
|
870
|
-
version=version,
|
|
871
|
-
),
|
|
872
|
-
page_id=page_id,
|
|
873
|
-
title=resolved_title,
|
|
874
|
-
version=version,
|
|
875
|
-
action="update_page",
|
|
876
|
-
)
|
|
877
|
-
return page_id
|
|
878
|
-
|
|
879
|
-
@staticmethod
|
|
880
|
-
def _section_start(key: str) -> str:
|
|
881
|
-
return f"<!-- {_SECTION_TAG_PREFIX}:{key}:START -->"
|
|
882
|
-
|
|
883
|
-
@staticmethod
|
|
884
|
-
def _section_end(key: str) -> str:
|
|
885
|
-
return f"<!-- {_SECTION_TAG_PREFIX}:{key}:END -->"
|
|
886
|
-
|
|
887
|
-
def _wrap_section(self, key: str, content: str) -> str:
|
|
888
|
-
return "\n".join([self._section_start(key), content, self._section_end(key)])
|
|
889
|
-
|
|
890
|
-
def _replace_section(self, content: str, key: str, replacement: str) -> tuple[str, bool]:
|
|
891
|
-
start = re.escape(self._section_start(key))
|
|
892
|
-
end = re.escape(self._section_end(key))
|
|
893
|
-
pattern = re.compile(f"{start}.*?{end}", flags=re.DOTALL)
|
|
894
|
-
wrapped = self._wrap_section(key, replacement)
|
|
895
|
-
updated, count = pattern.subn(lambda _match: wrapped, content, count=1)
|
|
896
|
-
if count == 0:
|
|
897
|
-
return self._replace_section_without_markers(content, key, replacement)
|
|
898
|
-
return updated, count > 0
|
|
899
|
-
|
|
900
|
-
def _replace_section_without_markers(self, content: str, key: str, replacement: str) -> tuple[str, bool]:
|
|
901
|
-
start_pattern = _SECTION_FALLBACK_START_PATTERNS.get(key)
|
|
902
|
-
if not start_pattern:
|
|
903
|
-
return content, False
|
|
904
|
-
start_match = re.search(start_pattern, content, flags=re.DOTALL | re.IGNORECASE)
|
|
905
|
-
if not start_match:
|
|
906
|
-
return self._insert_section_without_markers(content, key, replacement)
|
|
907
|
-
|
|
908
|
-
start_index = start_match.start()
|
|
909
|
-
end_index = len(content)
|
|
910
|
-
for boundary_pattern in _SECTION_FALLBACK_BOUNDARY_PATTERNS.get(key, ()):
|
|
911
|
-
boundary_match = re.search(boundary_pattern, content[start_match.end() :], flags=re.DOTALL | re.IGNORECASE)
|
|
912
|
-
if boundary_match:
|
|
913
|
-
candidate_end = start_match.end() + boundary_match.start()
|
|
914
|
-
end_index = min(end_index, candidate_end)
|
|
915
|
-
wrapped = self._wrap_section(key, replacement)
|
|
916
|
-
updated = f"{content[:start_index]}{wrapped}{content[end_index:]}"
|
|
917
|
-
return updated, True
|
|
918
|
-
|
|
919
|
-
def _insert_section_without_markers(self, content: str, key: str, replacement: str) -> tuple[str, bool]:
|
|
920
|
-
wrapped = self._wrap_section(key, replacement)
|
|
921
|
-
for boundary_pattern in _SECTION_FALLBACK_BOUNDARY_PATTERNS.get(key, ()):
|
|
922
|
-
boundary_match = re.search(boundary_pattern, content, flags=re.DOTALL | re.IGNORECASE)
|
|
923
|
-
if boundary_match:
|
|
924
|
-
index = boundary_match.start()
|
|
925
|
-
updated = f"{content[:index]}{wrapped}{content[index:]}"
|
|
926
|
-
return updated, True
|
|
927
|
-
updated = f"{content}{wrapped}"
|
|
928
|
-
return updated, True
|
|
929
|
-
|
|
930
|
-
def _append_to_section(self, content: str, key: str, new_content: str) -> tuple[str, bool]:
|
|
931
|
-
"""Append *new_content* inside an existing section, before the END marker.
|
|
932
|
-
|
|
933
|
-
Returns ``(updated_body, True)`` when the section was found and
|
|
934
|
-
appended to, or ``(content, False)`` when the section does not exist.
|
|
935
|
-
"""
|
|
936
|
-
end_marker = self._section_end(key)
|
|
937
|
-
end_escaped = re.escape(end_marker)
|
|
938
|
-
pattern = re.compile(f"({end_escaped})", flags=re.DOTALL)
|
|
939
|
-
# Insert new_content (with a preceding newline) just before the END marker.
|
|
940
|
-
updated, count = pattern.subn(lambda match: f"\n{new_content}\n{match.group(1)}", content, count=1)
|
|
941
|
-
return updated, count > 0
|
|
942
|
-
|
|
943
|
-
def _extract_section_content(self, body: str, key: str) -> str | None:
|
|
944
|
-
"""Extract the inner content of a ``VDS:SECTION`` block.
|
|
945
|
-
|
|
946
|
-
Returns the text between the START and END markers (exclusive),
|
|
947
|
-
or ``None`` when the section is not present.
|
|
948
|
-
"""
|
|
949
|
-
start = re.escape(self._section_start(key))
|
|
950
|
-
end = re.escape(self._section_end(key))
|
|
951
|
-
pattern = re.compile(f"{start}(.*?){end}", flags=re.DOTALL)
|
|
952
|
-
match = pattern.search(body)
|
|
953
|
-
if not match:
|
|
954
|
-
return None
|
|
955
|
-
return match.group(1)
|
|
956
|
-
|
|
957
|
-
async def read_section(self, page_id: str, section_key: str) -> str | None:
|
|
958
|
-
"""Fetch a page and return the content of a single managed section.
|
|
959
|
-
|
|
960
|
-
Convenience wrapper around :meth:`_extract_section_content` that
|
|
961
|
-
handles the Confluence API round-trip. Returns ``None`` when the
|
|
962
|
-
page cannot be loaded or the section is absent.
|
|
963
|
-
"""
|
|
964
|
-
page = await self._retry(
|
|
965
|
-
lambda: self.client.get_content_page(page_id, expand="body.storage"),
|
|
966
|
-
page_id=page_id,
|
|
967
|
-
action="read_section",
|
|
968
|
-
)
|
|
969
|
-
if not isinstance(page, dict):
|
|
970
|
-
return None
|
|
971
|
-
body = self._extract_storage_body(page)
|
|
972
|
-
if not body:
|
|
973
|
-
return None
|
|
974
|
-
return self._extract_section_content(body, section_key)
|
|
975
|
-
|
|
976
|
-
@staticmethod
|
|
977
|
-
def _extract_storage_body(page_payload: dict[str, Any]) -> str | None:
|
|
978
|
-
body = page_payload.get("body")
|
|
979
|
-
if not isinstance(body, dict):
|
|
980
|
-
return None
|
|
981
|
-
storage = body.get("storage")
|
|
982
|
-
if not isinstance(storage, dict):
|
|
983
|
-
return None
|
|
984
|
-
value = storage.get("value")
|
|
985
|
-
if isinstance(value, str):
|
|
986
|
-
return value
|
|
987
|
-
return None
|
|
988
|
-
|
|
989
|
-
@staticmethod
|
|
990
|
-
def _extract_version(page_payload: dict[str, Any]) -> int | None:
|
|
991
|
-
version = page_payload.get("version")
|
|
992
|
-
if not isinstance(version, dict):
|
|
993
|
-
return None
|
|
994
|
-
number = version.get("number")
|
|
995
|
-
if isinstance(number, int):
|
|
996
|
-
return number + 1
|
|
997
|
-
return None
|
|
998
|
-
|
|
999
|
-
@staticmethod
|
|
1000
|
-
def _extract_version_number(page_payload: dict[str, Any]) -> int | None:
|
|
1001
|
-
version = page_payload.get("version")
|
|
1002
|
-
if not isinstance(version, dict):
|
|
1003
|
-
return None
|
|
1004
|
-
number = version.get("number")
|
|
1005
|
-
if isinstance(number, int):
|
|
1006
|
-
return number
|
|
1007
|
-
return None
|
|
1008
|
-
|
|
1009
|
-
@staticmethod
|
|
1010
|
-
def _resolve_update_title(page_payload: dict[str, Any], desired_title: str) -> str | None:
|
|
1011
|
-
"""Omit the title on exact page-id updates when it is unchanged.
|
|
1012
|
-
|
|
1013
|
-
Some Confluence deployments can reject an update-by-id if the request
|
|
1014
|
-
redundantly resubmits an unchanged title that is duplicated elsewhere.
|
|
1015
|
-
When the current page already has the desired title, perform a
|
|
1016
|
-
body/version-only update instead.
|
|
1017
|
-
"""
|
|
1018
|
-
current_title = page_payload.get("title")
|
|
1019
|
-
if isinstance(current_title, str) and current_title.strip() == desired_title.strip():
|
|
1020
|
-
return None
|
|
1021
|
-
return desired_title
|
|
1022
|
-
|
|
1023
|
-
@staticmethod
|
|
1024
|
-
def _is_confluence_version_conflict(exc: DataSourceError) -> bool:
|
|
1025
|
-
"""Detect optimistic-lock conflicts returned by Confluence update APIs."""
|
|
1026
|
-
context = exc.context if isinstance(exc.context, dict) else {}
|
|
1027
|
-
context_blob = " ".join(str(value) for value in context.values())
|
|
1028
|
-
text = f"{exc}\n{context_blob}".lower()
|
|
1029
|
-
has_conflict_signal = "409" in text or "conflict" in text
|
|
1030
|
-
has_version_signal = "version" in text or "optimistic" in text
|
|
1031
|
-
return has_conflict_signal and has_version_signal
|
|
1032
|
-
|
|
1033
|
-
async def _update_page_storage_with_version_retry(
|
|
1034
|
-
self,
|
|
1035
|
-
*,
|
|
1036
|
-
page_id: str,
|
|
1037
|
-
title: str,
|
|
1038
|
-
action: str,
|
|
1039
|
-
failure_reason_prefix: str,
|
|
1040
|
-
body_suffix: str,
|
|
1041
|
-
build_body: Callable[[dict[str, Any]], tuple[str | None, str | None]],
|
|
1042
|
-
max_conflict_retries: int = _VERSION_CONFLICT_MAX_RETRIES,
|
|
1043
|
-
) -> tuple[bool, str | None]:
|
|
1044
|
-
"""Reload page/version and retry update when Confluence reports version conflicts."""
|
|
1045
|
-
max_attempts = max_conflict_retries + 1
|
|
1046
|
-
for attempt in range(1, max_attempts + 1):
|
|
1047
|
-
page = await self._retry(
|
|
1048
|
-
lambda: self.client.get_content_page(page_id, expand="body.storage,version"),
|
|
1049
|
-
page_id=page_id,
|
|
1050
|
-
action="get_content_page",
|
|
1051
|
-
)
|
|
1052
|
-
if not isinstance(page, dict):
|
|
1053
|
-
return False, f"{failure_reason_prefix}_page_load_failed"
|
|
1054
|
-
|
|
1055
|
-
body_html, failure_reason = build_body(page)
|
|
1056
|
-
if body_html is None:
|
|
1057
|
-
return False, failure_reason or f"{failure_reason_prefix}_body_build_failed"
|
|
1058
|
-
|
|
1059
|
-
with tempfile.NamedTemporaryFile(mode="w", suffix=body_suffix, delete=False, encoding="utf-8") as f:
|
|
1060
|
-
f.write(body_html)
|
|
1061
|
-
body_path = Path(f.name)
|
|
1062
|
-
|
|
1063
|
-
version = self._extract_version(page)
|
|
1064
|
-
resolved_title = self._resolve_update_title(page, title)
|
|
1065
|
-
try:
|
|
1066
|
-
await self._retry(
|
|
1067
|
-
lambda _t=resolved_title, _b=body_path, _v=version: self.client.update_page(
|
|
1068
|
-
page_id,
|
|
1069
|
-
title=_t,
|
|
1070
|
-
body_file=_b,
|
|
1071
|
-
version=_v,
|
|
1072
|
-
),
|
|
1073
|
-
page_id=page_id,
|
|
1074
|
-
title=resolved_title,
|
|
1075
|
-
version=version,
|
|
1076
|
-
action=action,
|
|
1077
|
-
retries=0,
|
|
1078
|
-
)
|
|
1079
|
-
return True, None
|
|
1080
|
-
except DataSourceError as exc:
|
|
1081
|
-
if self._is_confluence_version_conflict(exc):
|
|
1082
|
-
if attempt < max_attempts:
|
|
1083
|
-
logger.warning(
|
|
1084
|
-
"confluence_update_version_conflict_retry",
|
|
1085
|
-
page_id=page_id,
|
|
1086
|
-
action=action,
|
|
1087
|
-
attempt=attempt,
|
|
1088
|
-
max_attempts=max_attempts,
|
|
1089
|
-
error=str(exc),
|
|
1090
|
-
)
|
|
1091
|
-
continue
|
|
1092
|
-
logger.warning(
|
|
1093
|
-
"confluence_update_version_conflict_exhausted",
|
|
1094
|
-
page_id=page_id,
|
|
1095
|
-
action=action,
|
|
1096
|
-
max_attempts=max_attempts,
|
|
1097
|
-
error=str(exc),
|
|
1098
|
-
)
|
|
1099
|
-
return False, f"{failure_reason_prefix}_version_conflict_retry_exhausted"
|
|
1100
|
-
raise
|
|
1101
|
-
finally:
|
|
1102
|
-
if body_path.exists():
|
|
1103
|
-
body_path.unlink()
|
|
1104
|
-
return False, f"{failure_reason_prefix}_version_conflict_retry_exhausted"
|
|
1105
|
-
|
|
1106
|
-
@staticmethod
|
|
1107
|
-
def _sha256_text(value: str) -> str:
|
|
1108
|
-
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
|
1109
|
-
|
|
1110
|
-
def _extract_sync_meta_hash(self, content: str) -> str | None:
|
|
1111
|
-
pattern = re.compile(
|
|
1112
|
-
rf"<!--\s*{re.escape(_SYNC_META_PREFIX)}:rendered_hash=([a-fA-F0-9]{{64}})\s*-->",
|
|
1113
|
-
flags=re.IGNORECASE,
|
|
1114
|
-
)
|
|
1115
|
-
match = pattern.search(content)
|
|
1116
|
-
if not match:
|
|
1117
|
-
return None
|
|
1118
|
-
return match.group(1).lower()
|
|
1119
|
-
|
|
1120
|
-
def _build_sync_meta_comment(self, rendered_hash: str) -> str:
|
|
1121
|
-
return f"<!-- {_SYNC_META_PREFIX}:rendered_hash={rendered_hash} -->"
|
|
1122
|
-
|
|
1123
|
-
def _extract_managed_hash(self, content: str, managed_keys: tuple[str, ...]) -> str | None:
|
|
1124
|
-
chunks: list[str] = []
|
|
1125
|
-
for key in managed_keys:
|
|
1126
|
-
start = re.escape(self._section_start(key))
|
|
1127
|
-
end = re.escape(self._section_end(key))
|
|
1128
|
-
match = re.search(f"{start}.*?{end}", content, flags=re.DOTALL)
|
|
1129
|
-
if not match:
|
|
1130
|
-
return None
|
|
1131
|
-
chunks.append(match.group(0))
|
|
1132
|
-
chunks.append(self._extract_sync_meta_hash(content) or "")
|
|
1133
|
-
return self._sha256_text("\n".join(chunks))
|
|
1134
|
-
|
|
1135
|
-
def _patch_managed_sections(
|
|
1136
|
-
self,
|
|
1137
|
-
*,
|
|
1138
|
-
existing_body: str,
|
|
1139
|
-
managed_sections: dict[str, str],
|
|
1140
|
-
managed_keys: tuple[str, ...],
|
|
1141
|
-
rendered_hash: str,
|
|
1142
|
-
allow_backfill: bool,
|
|
1143
|
-
) -> tuple[str, bool]:
|
|
1144
|
-
patched = existing_body
|
|
1145
|
-
replaced = 0
|
|
1146
|
-
missing: list[str] = []
|
|
1147
|
-
for key in managed_keys:
|
|
1148
|
-
replacement = managed_sections.get(key)
|
|
1149
|
-
if replacement is None:
|
|
1150
|
-
missing.append(key)
|
|
1151
|
-
continue
|
|
1152
|
-
patched_candidate, changed = self._replace_section(patched, key, replacement)
|
|
1153
|
-
if changed:
|
|
1154
|
-
patched = patched_candidate
|
|
1155
|
-
replaced += 1
|
|
1156
|
-
else:
|
|
1157
|
-
missing.append(key)
|
|
1158
|
-
|
|
1159
|
-
if replaced == 0 and not allow_backfill:
|
|
1160
|
-
return existing_body, False
|
|
1161
|
-
|
|
1162
|
-
if missing and allow_backfill:
|
|
1163
|
-
appended = [patched.rstrip()]
|
|
1164
|
-
for key in managed_keys:
|
|
1165
|
-
if key in missing and managed_sections.get(key) is not None:
|
|
1166
|
-
appended.append(self._wrap_section(key, managed_sections[key]))
|
|
1167
|
-
patched = "\n\n".join([part for part in appended if part])
|
|
1168
|
-
|
|
1169
|
-
sync_meta_comment = self._build_sync_meta_comment(rendered_hash)
|
|
1170
|
-
existing_meta_hash = self._extract_sync_meta_hash(patched)
|
|
1171
|
-
if existing_meta_hash:
|
|
1172
|
-
meta_pattern = re.compile(
|
|
1173
|
-
rf"<!--\s*{re.escape(_SYNC_META_PREFIX)}:rendered_hash=[a-fA-F0-9]{{64}}\s*-->",
|
|
1174
|
-
flags=re.IGNORECASE,
|
|
1175
|
-
)
|
|
1176
|
-
patched = meta_pattern.sub(sync_meta_comment, patched, count=1)
|
|
1177
|
-
else:
|
|
1178
|
-
patched = f"{patched.rstrip()}\n{sync_meta_comment}\n"
|
|
1179
|
-
|
|
1180
|
-
return patched, True
|
|
1181
|
-
|
|
1182
|
-
@staticmethod
|
|
1183
|
-
def _normalize_text(text: str) -> str:
|
|
1184
|
-
return "\n".join(line.rstrip() for line in text.replace("\r\n", "\n").split("\n")).strip()
|
|
1185
|
-
|
|
1186
|
-
async def update_run_page_incremental(
|
|
1187
|
-
self,
|
|
1188
|
-
*,
|
|
1189
|
-
page_id: str,
|
|
1190
|
-
title: str,
|
|
1191
|
-
report_dir: Path,
|
|
1192
|
-
markdown_path: Path,
|
|
1193
|
-
thread_id: str | None = None,
|
|
1194
|
-
priority_actions_limit: int = 10,
|
|
1195
|
-
checklist_collapsed: bool = False,
|
|
1196
|
-
) -> tuple[bool, str | None]:
|
|
1197
|
-
"""Patch only deterministic sections in existing storage HTML."""
|
|
1198
|
-
checklist, _ = self._load_audit_checklist(report_dir)
|
|
1199
|
-
skipped_sections: list[str] = []
|
|
1200
|
-
section_updates: dict[str, str] = {}
|
|
1201
|
-
|
|
1202
|
-
summary_html = self._render_summary_html(checklist=checklist, report_dir=report_dir)
|
|
1203
|
-
if summary_html:
|
|
1204
|
-
section_updates["summary"] = summary_html
|
|
1205
|
-
|
|
1206
|
-
top_findings_html = self._render_top_findings_html(checklist=checklist, report_dir=report_dir)
|
|
1207
|
-
if top_findings_html:
|
|
1208
|
-
section_updates["top_findings"] = top_findings_html
|
|
1209
|
-
|
|
1210
|
-
if checklist:
|
|
1211
|
-
resolver = self._build_bitbucket_resolver_from_report_dir(report_dir)
|
|
1212
|
-
knowledge_ref_map = self._build_knowledge_ref_map_from_checklist(checklist)
|
|
1213
|
-
priority_renderer = PriorityActionsRenderer(bitbucket_link_resolver=resolver)
|
|
1214
|
-
priority_html = priority_renderer.render(checklist, limit=priority_actions_limit)
|
|
1215
|
-
if priority_html:
|
|
1216
|
-
section_updates["priority_actions"] = priority_html
|
|
1217
|
-
|
|
1218
|
-
checklist_renderer = FullChecklistRenderer(
|
|
1219
|
-
bitbucket_link_resolver=resolver,
|
|
1220
|
-
knowledge_ref_map=knowledge_ref_map or None,
|
|
1221
|
-
)
|
|
1222
|
-
checklist_core_html = checklist_renderer.render_core(checklist)
|
|
1223
|
-
checklist_details_html = checklist_renderer.render_details(checklist, collapsed=checklist_collapsed)
|
|
1224
|
-
if checklist_core_html:
|
|
1225
|
-
section_updates["checklist_core"] = checklist_core_html
|
|
1226
|
-
if checklist_details_html:
|
|
1227
|
-
section_updates["checklist_details"] = checklist_details_html
|
|
1228
|
-
else:
|
|
1229
|
-
skipped_sections = ["priority_actions", "checklist_core", "checklist_details"]
|
|
1230
|
-
logger.warning(
|
|
1231
|
-
"incremental_patch_checklist_missing",
|
|
1232
|
-
report_dir=str(report_dir),
|
|
1233
|
-
skipped_sections=skipped_sections,
|
|
1234
|
-
page_id=page_id,
|
|
1235
|
-
)
|
|
1236
|
-
|
|
1237
|
-
for readiness_key, readiness_html in self._render_readiness_sections(report_dir).items():
|
|
1238
|
-
if readiness_html:
|
|
1239
|
-
section_updates[readiness_key] = readiness_html
|
|
1240
|
-
|
|
1241
|
-
def _build_incremental_body(page_payload: dict[str, Any]) -> tuple[str | None, str | None]:
|
|
1242
|
-
existing_body = self._extract_storage_body(page_payload)
|
|
1243
|
-
if not existing_body:
|
|
1244
|
-
return None, "incremental_missing_storage_body"
|
|
1245
|
-
|
|
1246
|
-
if not checklist:
|
|
1247
|
-
has_existing_checklist_section = any(
|
|
1248
|
-
self._extract_section_content(existing_body, key) is not None
|
|
1249
|
-
for key in ("checklist_core", "checklist_details")
|
|
1250
|
-
)
|
|
1251
|
-
if has_existing_checklist_section:
|
|
1252
|
-
return None, "incremental_missing_checklist_for_existing_sections"
|
|
1253
|
-
|
|
1254
|
-
patched = existing_body
|
|
1255
|
-
patched_count = 0
|
|
1256
|
-
for key in _SECTION_MARKERS:
|
|
1257
|
-
replacement = section_updates.get(key)
|
|
1258
|
-
if not replacement:
|
|
1259
|
-
continue
|
|
1260
|
-
patched, changed = self._replace_section(patched, key, replacement)
|
|
1261
|
-
if changed:
|
|
1262
|
-
patched_count += 1
|
|
1263
|
-
|
|
1264
|
-
if patched_count == 0:
|
|
1265
|
-
return None, "incremental_unpatchable_content"
|
|
1266
|
-
return patched, None
|
|
1267
|
-
|
|
1268
|
-
self._last_sections_rendered = [key for key in _SECTION_MARKERS if key in section_updates]
|
|
1269
|
-
updated, failure_reason = await self._update_page_storage_with_version_retry(
|
|
1270
|
-
page_id=page_id,
|
|
1271
|
-
title=title,
|
|
1272
|
-
action="update_page_incremental",
|
|
1273
|
-
failure_reason_prefix="incremental",
|
|
1274
|
-
body_suffix=".incremental.storage.html",
|
|
1275
|
-
build_body=_build_incremental_body,
|
|
1276
|
-
)
|
|
1277
|
-
if updated:
|
|
1278
|
-
# Keep the canonical local storage artifact in sync with the live page body so
|
|
1279
|
-
# manual inspection does not lag behind successful incremental publishes.
|
|
1280
|
-
body_html = self._build_run_body_html(
|
|
1281
|
-
report_dir=report_dir,
|
|
1282
|
-
markdown_path=markdown_path,
|
|
1283
|
-
thread_id=thread_id,
|
|
1284
|
-
priority_actions_limit=priority_actions_limit,
|
|
1285
|
-
checklist_collapsed=checklist_collapsed,
|
|
1286
|
-
)
|
|
1287
|
-
markdown_path.with_suffix(".storage.html").write_text(body_html, encoding="utf-8")
|
|
1288
|
-
return updated, failure_reason
|
|
1289
|
-
|
|
1290
|
-
def _build_run_body_html(
|
|
1291
|
-
self,
|
|
1292
|
-
*,
|
|
1293
|
-
report_dir: Path,
|
|
1294
|
-
markdown_path: Path,
|
|
1295
|
-
thread_id: str | None = None,
|
|
1296
|
-
priority_actions_limit: int = 10,
|
|
1297
|
-
checklist_collapsed: bool = False,
|
|
1298
|
-
) -> str:
|
|
1299
|
-
"""Build Confluence storage HTML for the run page.
|
|
1300
|
-
|
|
1301
|
-
Phase 33 Structure:
|
|
1302
|
-
1. Score Summary Panel (Page Properties)
|
|
1303
|
-
2. Priority Actions Section
|
|
1304
|
-
3. Full Checklist Table (Collapsible)
|
|
1305
|
-
4. Markdown Report Content
|
|
1306
|
-
5. Provenance Block
|
|
1307
|
-
6. Attachments
|
|
1308
|
-
"""
|
|
1309
|
-
try:
|
|
1310
|
-
body_html = to_storage(markdown_path.read_text(encoding="utf-8"))
|
|
1311
|
-
except Exception:
|
|
1312
|
-
body_html = ""
|
|
1313
|
-
|
|
1314
|
-
parts: list[str] = []
|
|
1315
|
-
sections_rendered: list[str] = []
|
|
1316
|
-
|
|
1317
|
-
# Phase 144Q: Targeted run banner (shown when --target was used)
|
|
1318
|
-
targeted_banner = self._render_targeted_run_banner(report_dir)
|
|
1319
|
-
if targeted_banner:
|
|
1320
|
-
parts.append(targeted_banner)
|
|
1321
|
-
sections_rendered.append("targeted_run_banner")
|
|
1322
|
-
|
|
1323
|
-
# Try to load AuditChecklist for rich rendering
|
|
1324
|
-
checklist, _ = self._load_audit_checklist(report_dir)
|
|
1325
|
-
|
|
1326
|
-
# 1. Summary section
|
|
1327
|
-
summary_html = self._render_summary_html(checklist=checklist, report_dir=report_dir)
|
|
1328
|
-
if summary_html:
|
|
1329
|
-
parts.append(self._wrap_section("summary", summary_html))
|
|
1330
|
-
sections_rendered.append("summary")
|
|
1331
|
-
|
|
1332
|
-
# 2. Top Findings section (always attempt; falls back to workflow/xlsx sources)
|
|
1333
|
-
top_findings_html = self._render_top_findings_html(checklist=checklist, report_dir=report_dir)
|
|
1334
|
-
if top_findings_html:
|
|
1335
|
-
parts.append(self._wrap_section("top_findings", top_findings_html))
|
|
1336
|
-
sections_rendered.append("top_findings")
|
|
1337
|
-
|
|
1338
|
-
if checklist:
|
|
1339
|
-
# AC-144Q.1.4: Build Bitbucket resolver and knowledge ref map for rich rendering
|
|
1340
|
-
resolver = self._build_bitbucket_resolver_from_report_dir(report_dir)
|
|
1341
|
-
knowledge_ref_map = self._build_knowledge_ref_map_from_checklist(checklist)
|
|
1342
|
-
|
|
1343
|
-
# 3. Priority Actions (TSK-332)
|
|
1344
|
-
priority_renderer = PriorityActionsRenderer(bitbucket_link_resolver=resolver)
|
|
1345
|
-
priority_html = priority_renderer.render(checklist, limit=priority_actions_limit)
|
|
1346
|
-
if priority_html:
|
|
1347
|
-
parts.append(self._wrap_section("priority_actions", priority_html))
|
|
1348
|
-
sections_rendered.append("priority_actions")
|
|
1349
|
-
|
|
1350
|
-
# 4. Full Checklist (TSK-330)
|
|
1351
|
-
checklist_renderer = FullChecklistRenderer(
|
|
1352
|
-
bitbucket_link_resolver=resolver,
|
|
1353
|
-
knowledge_ref_map=knowledge_ref_map or None,
|
|
1354
|
-
)
|
|
1355
|
-
checklist_core_html = checklist_renderer.render_core(checklist)
|
|
1356
|
-
checklist_details_html = checklist_renderer.render_details(checklist, collapsed=checklist_collapsed)
|
|
1357
|
-
if checklist_core_html:
|
|
1358
|
-
parts.append(self._wrap_section("checklist_core", checklist_core_html))
|
|
1359
|
-
sections_rendered.append("checklist_core")
|
|
1360
|
-
if checklist_details_html:
|
|
1361
|
-
parts.append(self._wrap_section("checklist_details", checklist_details_html))
|
|
1362
|
-
sections_rendered.append("checklist_details")
|
|
1363
|
-
else:
|
|
1364
|
-
logger.warning(
|
|
1365
|
-
"run_page_checklist_missing",
|
|
1366
|
-
report_dir=str(report_dir),
|
|
1367
|
-
skipped_sections=["priority_actions", "checklist_core", "checklist_details"],
|
|
1368
|
-
)
|
|
1369
|
-
# Render synthesis-based critique as fallback (Phase 109)
|
|
1370
|
-
critique_html = self._render_synthesis_critique(report_dir)
|
|
1371
|
-
if critique_html:
|
|
1372
|
-
parts.append(self._wrap_section("synthesis_critique", critique_html))
|
|
1373
|
-
sections_rendered.append("synthesis_critique")
|
|
1374
|
-
|
|
1375
|
-
# Optional readiness sections (Phase 85)
|
|
1376
|
-
for readiness_key, readiness_html in self._render_readiness_sections(report_dir).items():
|
|
1377
|
-
if readiness_html:
|
|
1378
|
-
parts.append(self._wrap_section(readiness_key, readiness_html))
|
|
1379
|
-
sections_rendered.append(readiness_key)
|
|
1380
|
-
|
|
1381
|
-
# 5. Markdown content
|
|
1382
|
-
if body_html:
|
|
1383
|
-
parts.append(body_html)
|
|
1384
|
-
sections_rendered.append("markdown")
|
|
1385
|
-
|
|
1386
|
-
# 6. Provenance block
|
|
1387
|
-
provenance_html = self._render_provenance_html(
|
|
1388
|
-
checklist=checklist,
|
|
1389
|
-
report_dir=report_dir,
|
|
1390
|
-
thread_id=thread_id,
|
|
1391
|
-
)
|
|
1392
|
-
if provenance_html:
|
|
1393
|
-
parts.append(provenance_html)
|
|
1394
|
-
sections_rendered.append("provenance")
|
|
1395
|
-
|
|
1396
|
-
# 7. Attachments
|
|
1397
|
-
parts.append(self._render_attachments_macro_html())
|
|
1398
|
-
|
|
1399
|
-
# TSK-940.75: Record which sections were rendered for observability.
|
|
1400
|
-
self._last_sections_rendered = sections_rendered
|
|
1401
|
-
|
|
1402
|
-
rendered = "\n".join(parts)
|
|
1403
|
-
if not self.validate_storage_format(rendered):
|
|
1404
|
-
logger.warning(
|
|
1405
|
-
"confluence_storage_format_validation_failed",
|
|
1406
|
-
report_dir=str(report_dir),
|
|
1407
|
-
markdown_path=str(markdown_path),
|
|
1408
|
-
)
|
|
1409
|
-
return rendered
|
|
1410
|
-
|
|
1411
|
-
@staticmethod
|
|
1412
|
-
def validate_storage_format(html: str) -> bool:
|
|
1413
|
-
"""Validate Confluence storage body as parseable XHTML fragment."""
|
|
1414
|
-
if not html.strip():
|
|
1415
|
-
return True
|
|
1416
|
-
|
|
1417
|
-
wrapped = (
|
|
1418
|
-
'<root xmlns:ac="http://atlassian.com/content" '
|
|
1419
|
-
'xmlns:ri="http://atlassian.com/resource/identifier" '
|
|
1420
|
-
'xmlns:at="http://atlassian.com/template">'
|
|
1421
|
-
f"{html}"
|
|
1422
|
-
"</root>"
|
|
1423
|
-
)
|
|
1424
|
-
try:
|
|
1425
|
-
ET.fromstring(wrapped)
|
|
1426
|
-
return True
|
|
1427
|
-
except ET.ParseError:
|
|
1428
|
-
return False
|
|
1429
|
-
|
|
1430
|
-
def _load_audit_checklist(self, report_dir: Path) -> tuple[AuditChecklist | None, str | None]:
|
|
1431
|
-
"""Load AuditChecklist from checklist artifacts (best-effort).
|
|
1432
|
-
|
|
1433
|
-
Preference order:
|
|
1434
|
-
1) audit-checklist.json (canonical)
|
|
1435
|
-
2) audit_checklist_*.json (workflow artifacts)
|
|
1436
|
-
"""
|
|
1437
|
-
from vds_audit_orchestrator.models.checklist import AuditChecklist
|
|
1438
|
-
|
|
1439
|
-
checklist_json = self._newest_named(report_dir, "audit-checklist.json")
|
|
1440
|
-
if not checklist_json:
|
|
1441
|
-
matches = sorted(
|
|
1442
|
-
report_dir.rglob("audit_checklist_*.json"),
|
|
1443
|
-
key=lambda p: p.stat().st_mtime,
|
|
1444
|
-
reverse=True,
|
|
1445
|
-
)
|
|
1446
|
-
checklist_json = matches[0] if matches else None
|
|
1447
|
-
if not checklist_json:
|
|
1448
|
-
logger.warning(
|
|
1449
|
-
"checklist_file_not_found",
|
|
1450
|
-
report_dir=str(report_dir),
|
|
1451
|
-
expected_names=["audit-checklist.json", "audit_checklist_*.json"],
|
|
1452
|
-
reason="no_matching_artifact",
|
|
1453
|
-
)
|
|
1454
|
-
return None, None
|
|
1455
|
-
|
|
1456
|
-
try:
|
|
1457
|
-
payload = json.loads(checklist_json.read_text(encoding="utf-8"))
|
|
1458
|
-
return AuditChecklist.model_validate(payload), checklist_json.name
|
|
1459
|
-
except Exception as e:
|
|
1460
|
-
logger.warning("checklist_load_failed", path=str(checklist_json), error=str(e))
|
|
1461
|
-
return None, None
|
|
1462
|
-
|
|
1463
|
-
@staticmethod
|
|
1464
|
-
def _build_bitbucket_resolver_from_report_dir(report_dir: Path) -> BitbucketLinkResolver | None:
|
|
1465
|
-
"""AC-144Q.1.4: Build a BitbucketLinkResolver from report_dir path convention.
|
|
1466
|
-
|
|
1467
|
-
Repo cache path convention: ~/.vds/cache/repos/<BB_PROJECT>-project/<repo>/runs/<thread_id>/
|
|
1468
|
-
The project key is extracted from the '<BB_PROJECT>-project' segment immediately after
|
|
1469
|
-
the 'repos' directory; the repo slug is the segment after that.
|
|
1470
|
-
Falls back to workflow-summary.json 'repo_key' when the path does not follow the convention.
|
|
1471
|
-
Returns None when identity cannot be determined.
|
|
1472
|
-
"""
|
|
1473
|
-
parts = report_dir.resolve().parts
|
|
1474
|
-
try:
|
|
1475
|
-
repos_idx = parts.index("repos")
|
|
1476
|
-
except ValueError:
|
|
1477
|
-
return None
|
|
1478
|
-
|
|
1479
|
-
if repos_idx + 2 < len(parts):
|
|
1480
|
-
project_segment = parts[repos_idx + 1] # e.g. "PAR-project"
|
|
1481
|
-
repo_slug = parts[repos_idx + 2] # e.g. "merchant-billing"
|
|
1482
|
-
if project_segment.endswith("-project"):
|
|
1483
|
-
project_key = project_segment[: -len("-project")].upper()
|
|
1484
|
-
if project_key and repo_slug:
|
|
1485
|
-
return BitbucketLinkResolver(project_key=project_key, repo_slug=repo_slug)
|
|
1486
|
-
|
|
1487
|
-
return None
|
|
1488
|
-
|
|
1489
|
-
@staticmethod
|
|
1490
|
-
def _build_knowledge_ref_map_from_checklist(checklist: Any | None) -> dict[str, tuple[str, str]]:
|
|
1491
|
-
"""AC-144Q.3.1: Build a chunk_id -> (url, title) map from checklist evidence refs.
|
|
1492
|
-
|
|
1493
|
-
Iterates all rows' evidence_refs and collects refs that have a chunk_id and page_title,
|
|
1494
|
-
using source_url as the URL. Refs without a chunk_id are skipped.
|
|
1495
|
-
Returns an empty dict when checklist is None or has no qualifying refs.
|
|
1496
|
-
"""
|
|
1497
|
-
knowledge_ref_map: dict[str, tuple[str, str]] = {}
|
|
1498
|
-
if checklist is None:
|
|
1499
|
-
return knowledge_ref_map
|
|
1500
|
-
rows = getattr(checklist, "rows", None) or []
|
|
1501
|
-
for row in rows:
|
|
1502
|
-
evidence_refs = getattr(row, "evidence_refs", None) or []
|
|
1503
|
-
for ref in evidence_refs:
|
|
1504
|
-
chunk_id = str(getattr(ref, "chunk_id", "") or "").strip()
|
|
1505
|
-
page_title = str(getattr(ref, "page_title", "") or "").strip()
|
|
1506
|
-
source_url = str(getattr(ref, "source_url", "") or "").strip()
|
|
1507
|
-
if chunk_id and page_title:
|
|
1508
|
-
knowledge_ref_map[chunk_id] = (source_url, page_title)
|
|
1509
|
-
return knowledge_ref_map
|
|
1510
|
-
|
|
1511
|
-
def _load_readiness_report(self, report_dir: Path) -> Any | None:
|
|
1512
|
-
"""Load readiness-report.json as ReadinessReport model when present."""
|
|
1513
|
-
from vds_audit_orchestrator.models.readiness import ReadinessReport
|
|
1514
|
-
|
|
1515
|
-
readiness_json = self._newest_named(report_dir, "readiness-report.json")
|
|
1516
|
-
if not readiness_json:
|
|
1517
|
-
return None
|
|
1518
|
-
try:
|
|
1519
|
-
payload = json.loads(readiness_json.read_text(encoding="utf-8"))
|
|
1520
|
-
return ReadinessReport.model_validate(payload)
|
|
1521
|
-
except Exception as exc:
|
|
1522
|
-
logger.warning("readiness_report_load_failed", path=str(readiness_json), error=str(exc))
|
|
1523
|
-
return None
|
|
1524
|
-
|
|
1525
|
-
def _render_readiness_sections(self, report_dir: Path) -> dict[str, str]:
|
|
1526
|
-
"""Render readiness sections for run page extension when artifact is available."""
|
|
1527
|
-
from vds_audit_orchestrator.publishers.readiness_renderer import ReadinessConfluenceRenderer
|
|
1528
|
-
|
|
1529
|
-
readiness_report = self._load_readiness_report(report_dir)
|
|
1530
|
-
if readiness_report is None:
|
|
1531
|
-
return {}
|
|
1532
|
-
try:
|
|
1533
|
-
return ReadinessConfluenceRenderer().render_sections(readiness_report)
|
|
1534
|
-
except Exception as exc:
|
|
1535
|
-
logger.warning("readiness_section_render_failed", report_dir=str(report_dir), error=str(exc))
|
|
1536
|
-
return {}
|
|
1537
|
-
|
|
1538
|
-
@staticmethod
|
|
1539
|
-
def _newest_named(report_dir: Path, name: str) -> Path | None:
|
|
1540
|
-
"""Find the newest file with the given name in report_dir."""
|
|
1541
|
-
matches = list(report_dir.rglob(name))
|
|
1542
|
-
if not matches:
|
|
1543
|
-
return None
|
|
1544
|
-
return max(matches, key=lambda p: p.stat().st_mtime)
|
|
1545
|
-
|
|
1546
|
-
def _render_targeted_run_banner(self, report_dir: Path) -> str | None:
|
|
1547
|
-
"""Phase 144Q: Render a banner when this is a targeted (partial) run.
|
|
1548
|
-
|
|
1549
|
-
Reads workflow-summary.json to detect is_targeted_run and the target_selector.
|
|
1550
|
-
Returns None when no targeted run flag is present.
|
|
1551
|
-
"""
|
|
1552
|
-
workflow_json = self._newest_named(report_dir, "workflow-summary.json")
|
|
1553
|
-
if not workflow_json:
|
|
1554
|
-
return None
|
|
1555
|
-
try:
|
|
1556
|
-
payload = json.loads(workflow_json.read_text(encoding="utf-8"))
|
|
1557
|
-
except Exception:
|
|
1558
|
-
return None
|
|
1559
|
-
if not isinstance(payload, dict):
|
|
1560
|
-
return None
|
|
1561
|
-
if not payload.get("is_targeted_run"):
|
|
1562
|
-
return None
|
|
1563
|
-
target_selector = str(payload.get("target_selector") or "").strip()
|
|
1564
|
-
if not target_selector:
|
|
1565
|
-
return None
|
|
1566
|
-
return "\n".join(
|
|
1567
|
-
[
|
|
1568
|
-
'<ac:structured-macro ac:name="info"><ac:rich-text-body>',
|
|
1569
|
-
f"<p>Targeted run: rows matching selector "{escape(target_selector)}" only.</p>",
|
|
1570
|
-
"</ac:rich-text-body></ac:structured-macro>",
|
|
1571
|
-
]
|
|
1572
|
-
)
|
|
1573
|
-
|
|
1574
|
-
def _render_summary_html(
|
|
1575
|
-
self,
|
|
1576
|
-
*,
|
|
1577
|
-
checklist: AuditChecklist | None,
|
|
1578
|
-
report_dir: Path,
|
|
1579
|
-
) -> str | None:
|
|
1580
|
-
"""Render Audit Checklist Summary section (TSK-182).
|
|
1581
|
-
|
|
1582
|
-
Includes:
|
|
1583
|
-
- Overall score (percentage and 1-5 scale)
|
|
1584
|
-
- Maturity level indicator
|
|
1585
|
-
- Pass/Partial/Fail/NA/Error row counts
|
|
1586
|
-
- Critical findings count
|
|
1587
|
-
"""
|
|
1588
|
-
if not checklist:
|
|
1589
|
-
# Fall back to workflow-summary.json for basic stats
|
|
1590
|
-
return self._render_summary_from_workflow(report_dir)
|
|
1591
|
-
|
|
1592
|
-
# Determine maturity level based on score
|
|
1593
|
-
score = checklist.overall_score
|
|
1594
|
-
if score >= 90:
|
|
1595
|
-
maturity = "Exemplary"
|
|
1596
|
-
maturity_color = "Green"
|
|
1597
|
-
maturity_subtle = False
|
|
1598
|
-
elif score >= 70:
|
|
1599
|
-
maturity = "Established"
|
|
1600
|
-
maturity_color = "Green"
|
|
1601
|
-
maturity_subtle = True
|
|
1602
|
-
elif score >= 50:
|
|
1603
|
-
maturity = "Developing"
|
|
1604
|
-
maturity_color = "Yellow"
|
|
1605
|
-
maturity_subtle = True
|
|
1606
|
-
elif score >= 30:
|
|
1607
|
-
maturity = "Initial"
|
|
1608
|
-
maturity_color = "Yellow"
|
|
1609
|
-
maturity_subtle = True
|
|
1610
|
-
else:
|
|
1611
|
-
maturity = "Ad-hoc"
|
|
1612
|
-
maturity_color = "Red"
|
|
1613
|
-
maturity_subtle = False
|
|
1614
|
-
|
|
1615
|
-
critical_count = sum(
|
|
1616
|
-
1 for r in checklist.rows if r.status.value == "FAIL" and (r.severity or "").lower() in ("critical", "high")
|
|
1617
|
-
)
|
|
1618
|
-
|
|
1619
|
-
# Build Status macros
|
|
1620
|
-
pass_macro = ConfluenceMacroBuilder.status_macro(str(checklist.pass_count), "Green", subtle=True)
|
|
1621
|
-
partial_macro = ConfluenceMacroBuilder.status_macro(str(checklist.partial_count), "Yellow", subtle=True)
|
|
1622
|
-
fail_macro = ConfluenceMacroBuilder.status_macro(str(checklist.fail_count), "Red", subtle=False)
|
|
1623
|
-
na_macro = ConfluenceMacroBuilder.status_macro(str(checklist.na_count), "Grey", subtle=True)
|
|
1624
|
-
|
|
1625
|
-
maturity_macro = ConfluenceMacroBuilder.status_macro(maturity, maturity_color, subtle=maturity_subtle)
|
|
1626
|
-
|
|
1627
|
-
# Build table rows for Page Properties
|
|
1628
|
-
rows = [
|
|
1629
|
-
f"<tr><td><strong>Overall Score (Điểm tổng)</strong></td><td><strong>{score:.1f}%</strong> ({checklist.overall_score_1_5:.1f}/5)</td></tr>",
|
|
1630
|
-
f"<tr><td><strong>Maturity Level (Mức độ trưởng thành)</strong></td><td>{maturity_macro}</td></tr>",
|
|
1631
|
-
f"<tr><td><strong>Total Checks (Tổng kiểm tra)</strong></td><td>{checklist.total_rows}</td></tr>",
|
|
1632
|
-
f"<tr><td>Pass (✅ Đạt)</td><td>{pass_macro}</td></tr>",
|
|
1633
|
-
f"<tr><td>Partial (⚠️ Một phần)</td><td>{partial_macro}</td></tr>",
|
|
1634
|
-
f"<tr><td>Fail (❌ Không đạt)</td><td>{fail_macro}</td></tr>",
|
|
1635
|
-
f"<tr><td>N/A (➖ KHÔNG ÁP DỤNG)</td><td>{na_macro}</td></tr>",
|
|
1636
|
-
]
|
|
1637
|
-
|
|
1638
|
-
if checklist.error_count > 0:
|
|
1639
|
-
error_macro = ConfluenceMacroBuilder.status_macro(str(checklist.error_count), "Red", subtle=False)
|
|
1640
|
-
rows.append(f"<tr><td>🔴 Lỗi</td><td>{error_macro}</td></tr>")
|
|
1641
|
-
|
|
1642
|
-
if critical_count > 0:
|
|
1643
|
-
crit_macro = ConfluenceMacroBuilder.status_macro(str(critical_count), "Red", subtle=False)
|
|
1644
|
-
rows.append(f"<tr><td><strong>Phát hiện Critical/High</strong></td><td>{crit_macro}</td></tr>")
|
|
1645
|
-
|
|
1646
|
-
table_body = f"<table><tbody>{''.join(rows)}</tbody></table>"
|
|
1647
|
-
return "\n".join(
|
|
1648
|
-
[
|
|
1649
|
-
"<h2>Audit Checklist Summary</h2>",
|
|
1650
|
-
ConfluenceMacroBuilder.page_properties_macro(table_body),
|
|
1651
|
-
]
|
|
1652
|
-
)
|
|
1653
|
-
|
|
1654
|
-
def _render_summary_from_workflow(self, report_dir: Path) -> str | None:
|
|
1655
|
-
"""Fallback summary rendering from workflow-summary.json.
|
|
1656
|
-
|
|
1657
|
-
Produces rich Confluence content with Page Properties macro, Status
|
|
1658
|
-
badges, and maturity level derivation — matching the same visual
|
|
1659
|
-
quality as the checklist-based path.
|
|
1660
|
-
"""
|
|
1661
|
-
workflow_json = self._newest_named(report_dir, "workflow-summary.json")
|
|
1662
|
-
if not workflow_json:
|
|
1663
|
-
return None
|
|
1664
|
-
|
|
1665
|
-
try:
|
|
1666
|
-
payload = json.loads(workflow_json.read_text(encoding="utf-8"))
|
|
1667
|
-
if not isinstance(payload, dict):
|
|
1668
|
-
return None
|
|
1669
|
-
|
|
1670
|
-
# Extract score — try top-level then synthesis fallback
|
|
1671
|
-
overall_score = payload.get("overall_score") or payload.get("score")
|
|
1672
|
-
synthesis = payload.get("synthesis") if isinstance(payload.get("synthesis"), dict) else {}
|
|
1673
|
-
if overall_score is None:
|
|
1674
|
-
overall_score = synthesis.get("score")
|
|
1675
|
-
if overall_score is None:
|
|
1676
|
-
return None
|
|
1677
|
-
|
|
1678
|
-
score = float(overall_score)
|
|
1679
|
-
|
|
1680
|
-
# Derive maturity level using same thresholds as checklist path
|
|
1681
|
-
if score >= 90:
|
|
1682
|
-
maturity, maturity_color, maturity_subtle = "Exemplary", "Green", False
|
|
1683
|
-
elif score >= 70:
|
|
1684
|
-
maturity, maturity_color, maturity_subtle = "Established", "Green", True
|
|
1685
|
-
elif score >= 50:
|
|
1686
|
-
maturity, maturity_color, maturity_subtle = "Developing", "Yellow", True
|
|
1687
|
-
elif score >= 30:
|
|
1688
|
-
maturity, maturity_color, maturity_subtle = "Initial", "Yellow", True
|
|
1689
|
-
else:
|
|
1690
|
-
maturity, maturity_color, maturity_subtle = "Ad-hoc", "Red", False
|
|
1691
|
-
|
|
1692
|
-
# Override with explicit maturity_level if present
|
|
1693
|
-
explicit_maturity = payload.get("maturity_level")
|
|
1694
|
-
if explicit_maturity:
|
|
1695
|
-
maturity = str(explicit_maturity)
|
|
1696
|
-
|
|
1697
|
-
maturity_macro = ConfluenceMacroBuilder.status_macro(maturity, maturity_color, subtle=maturity_subtle)
|
|
1698
|
-
|
|
1699
|
-
# Build table rows
|
|
1700
|
-
rows: list[str] = [
|
|
1701
|
-
f"<tr><td><strong>Overall Score (Điểm tổng)</strong></td><td><strong>{score:.1f}%</strong></td></tr>",
|
|
1702
|
-
f"<tr><td><strong>Maturity Level (Mức độ trưởng thành)</strong></td><td>{maturity_macro}</td></tr>",
|
|
1703
|
-
]
|
|
1704
|
-
|
|
1705
|
-
# Source indicator
|
|
1706
|
-
source = payload.get("summary_score_source", "")
|
|
1707
|
-
if source:
|
|
1708
|
-
rows.append(f"<tr><td><strong>Score Source</strong></td><td>{escape(str(source))}</td></tr>")
|
|
1709
|
-
|
|
1710
|
-
# Total checks / count fields — use explicit or infer from rows_evaluated
|
|
1711
|
-
total_checks = payload.get("total_checks") or payload.get("rows_evaluated")
|
|
1712
|
-
if total_checks is not None:
|
|
1713
|
-
rows.append(f"<tr><td><strong>Total Checks (Tổng kiểm tra)</strong></td><td>{total_checks}</td></tr>")
|
|
1714
|
-
|
|
1715
|
-
# Count fields with Status macros (matching checklist style)
|
|
1716
|
-
count_fields = [
|
|
1717
|
-
("pass_count", "Pass (✅ Đạt)", "Green"),
|
|
1718
|
-
("partial_count", "Partial (⚠️ Một phần)", "Yellow"),
|
|
1719
|
-
("fail_count", "Fail (❌ Không đạt)", "Red"),
|
|
1720
|
-
("na_count", "N/A (➖ KHÔNG ÁP DỤNG)", "Grey"),
|
|
1721
|
-
("error_count", "🔴 Lỗi", "Red"),
|
|
1722
|
-
]
|
|
1723
|
-
for key, label, color in count_fields:
|
|
1724
|
-
value = payload.get(key)
|
|
1725
|
-
if value is not None:
|
|
1726
|
-
subtle = color not in ("Red",)
|
|
1727
|
-
count_macro = ConfluenceMacroBuilder.status_macro(str(value), color, subtle=subtle)
|
|
1728
|
-
rows.append(f"<tr><td>{label}</td><td>{count_macro}</td></tr>")
|
|
1729
|
-
|
|
1730
|
-
# Synthesis-derived metadata (repository name, total findings)
|
|
1731
|
-
repo_name = payload.get("repository")
|
|
1732
|
-
if repo_name:
|
|
1733
|
-
rows.append(f"<tr><td><strong>Repository</strong></td><td>{escape(str(repo_name))}</td></tr>")
|
|
1734
|
-
|
|
1735
|
-
total_findings = payload.get("total_findings") or synthesis.get("total_findings")
|
|
1736
|
-
if total_findings is not None and int(total_findings) > 0:
|
|
1737
|
-
findings_macro = ConfluenceMacroBuilder.status_macro(str(total_findings), "Yellow", subtle=True)
|
|
1738
|
-
rows.append(f"<tr><td><strong>Total Findings</strong></td><td>{findings_macro}</td></tr>")
|
|
1739
|
-
|
|
1740
|
-
table_body = f"<table><tbody>{''.join(rows)}</tbody></table>"
|
|
1741
|
-
return "\n".join(
|
|
1742
|
-
[
|
|
1743
|
-
"<h2>Audit Checklist Summary</h2>",
|
|
1744
|
-
ConfluenceMacroBuilder.page_properties_macro(table_body),
|
|
1745
|
-
]
|
|
1746
|
-
)
|
|
1747
|
-
except Exception:
|
|
1748
|
-
return None
|
|
1749
|
-
|
|
1750
|
-
def _render_synthesis_critique(self, report_dir: Path) -> str | None:
|
|
1751
|
-
"""Render synthesis-based critique from workflow-summary.json.
|
|
1752
|
-
|
|
1753
|
-
Extracts gaps and aligned_practices from the synthesis field and
|
|
1754
|
-
renders them as a structured Confluence panel (Phase 109 fallback
|
|
1755
|
-
when audit-checklist.json is unavailable).
|
|
1756
|
-
"""
|
|
1757
|
-
workflow_json = self._newest_named(report_dir, "workflow-summary.json")
|
|
1758
|
-
if not workflow_json:
|
|
1759
|
-
return None
|
|
1760
|
-
|
|
1761
|
-
try:
|
|
1762
|
-
payload = json.loads(workflow_json.read_text(encoding="utf-8"))
|
|
1763
|
-
if not isinstance(payload, dict):
|
|
1764
|
-
return None
|
|
1765
|
-
|
|
1766
|
-
synthesis = payload.get("synthesis") if isinstance(payload.get("synthesis"), dict) else {}
|
|
1767
|
-
if not synthesis:
|
|
1768
|
-
return None
|
|
1769
|
-
|
|
1770
|
-
gaps = synthesis.get("gaps", [])
|
|
1771
|
-
aligned = synthesis.get("aligned_practices", [])
|
|
1772
|
-
status = synthesis.get("status", "")
|
|
1773
|
-
|
|
1774
|
-
if not gaps and not aligned:
|
|
1775
|
-
return None
|
|
1776
|
-
|
|
1777
|
-
parts: list[str] = ["<h2>Synthesis Critique (Đánh giá tổng hợp)</h2>"]
|
|
1778
|
-
|
|
1779
|
-
# Info macro for synthesis status
|
|
1780
|
-
if status:
|
|
1781
|
-
status_color = "Green" if status == "complete" else "Yellow"
|
|
1782
|
-
parts.append(
|
|
1783
|
-
f"<p>Synthesis Status: {ConfluenceMacroBuilder.status_macro(status, status_color, subtle=True)}</p>"
|
|
1784
|
-
)
|
|
1785
|
-
|
|
1786
|
-
# Gaps section
|
|
1787
|
-
if gaps:
|
|
1788
|
-
parts.append("<h3>Identified Gaps (Khoảng trống)</h3>")
|
|
1789
|
-
parts.append(
|
|
1790
|
-
ConfluenceMacroBuilder.info_macro(
|
|
1791
|
-
title="Gaps Requiring Attention",
|
|
1792
|
-
body=self._render_synthesis_items_html(gaps, empty_label="No gaps captured."),
|
|
1793
|
-
icon="warning",
|
|
1794
|
-
)
|
|
1795
|
-
)
|
|
1796
|
-
|
|
1797
|
-
# Aligned practices section
|
|
1798
|
-
if aligned:
|
|
1799
|
-
parts.append("<h3>Aligned Practices (Thực hành phù hợp)</h3>")
|
|
1800
|
-
parts.append(
|
|
1801
|
-
ConfluenceMacroBuilder.info_macro(
|
|
1802
|
-
title="Aligned Practices",
|
|
1803
|
-
body=self._render_synthesis_items_html(aligned, empty_label="No aligned practices captured."),
|
|
1804
|
-
icon="check",
|
|
1805
|
-
)
|
|
1806
|
-
)
|
|
1807
|
-
|
|
1808
|
-
return "\n".join(parts)
|
|
1809
|
-
except Exception:
|
|
1810
|
-
return None
|
|
1811
|
-
|
|
1812
|
-
def _render_synthesis_items_html(self, items: list[Any], *, empty_label: str) -> str:
|
|
1813
|
-
structured_items = [item for item in items if isinstance(item, dict)]
|
|
1814
|
-
if structured_items:
|
|
1815
|
-
rows: list[str] = []
|
|
1816
|
-
for item in structured_items:
|
|
1817
|
-
check_id = escape(str(item.get("check_id") or item.get("row_id") or item.get("id") or "-"))
|
|
1818
|
-
severity = self._build_severity_macro_text(str(item.get("severity") or ""))
|
|
1819
|
-
finding = escape(
|
|
1820
|
-
str(
|
|
1821
|
-
item.get("finding") or item.get("title") or item.get("reason") or item.get("description") or "-"
|
|
1822
|
-
).strip()
|
|
1823
|
-
)
|
|
1824
|
-
rows.append(f"<tr><td><code>{check_id}</code></td><td>{severity}</td><td>{finding}</td></tr>")
|
|
1825
|
-
return (
|
|
1826
|
-
"<table><thead><tr><th>Check</th><th>Severity</th><th>Finding</th></tr></thead>"
|
|
1827
|
-
f"<tbody>{''.join(rows)}</tbody></table>"
|
|
1828
|
-
)
|
|
1829
|
-
|
|
1830
|
-
text_items = [escape(str(item)) for item in items if item]
|
|
1831
|
-
if text_items:
|
|
1832
|
-
return "<ul>" + "".join(f"<li>{item}</li>" for item in text_items) + "</ul>"
|
|
1833
|
-
return f"<p>{escape(empty_label)}</p>"
|
|
1834
|
-
|
|
1835
|
-
def _render_top_findings_html(
|
|
1836
|
-
self,
|
|
1837
|
-
*,
|
|
1838
|
-
checklist: AuditChecklist | None,
|
|
1839
|
-
report_dir: Path,
|
|
1840
|
-
limit: int = 10,
|
|
1841
|
-
bitbucket_link_resolver: Any = None,
|
|
1842
|
-
) -> str | None:
|
|
1843
|
-
"""Render top findings table from checklist, workflow summary, or checklist xlsx.
|
|
1844
|
-
|
|
1845
|
-
Args:
|
|
1846
|
-
checklist: Optional audit checklist for rich rendering.
|
|
1847
|
-
report_dir: Directory containing report artifacts.
|
|
1848
|
-
limit: Maximum number of findings to render.
|
|
1849
|
-
bitbucket_link_resolver: Optional BitbucketLinkResolver for source field links.
|
|
1850
|
-
"""
|
|
1851
|
-
rows = self._collect_top_findings_rows(checklist=checklist, report_dir=report_dir, limit=limit)
|
|
1852
|
-
if not rows:
|
|
1853
|
-
return None
|
|
1854
|
-
|
|
1855
|
-
headers = ["Trạng thái", "Mức độ", "Phát hiện", "Tham chiếu", "Nguồn"]
|
|
1856
|
-
thead = "<thead><tr>" + "".join(f"<th>{h}</th>" for h in headers) + "</tr></thead>"
|
|
1857
|
-
body_rows: list[str] = []
|
|
1858
|
-
for row in rows:
|
|
1859
|
-
status_macro = self._build_status_macro_text(str(row.get("status", "")))
|
|
1860
|
-
severity_macro = self._build_severity_macro_text(str(row.get("severity", "")))
|
|
1861
|
-
finding = escape(str(row.get("finding", "")).strip())
|
|
1862
|
-
reference = escape(str(row.get("reference", "")).strip())
|
|
1863
|
-
raw_source = str(row.get("source", "")).strip()
|
|
1864
|
-
# TSK-144Q.2: resolve source to Bitbucket URL if it doesn't start with http
|
|
1865
|
-
if bitbucket_link_resolver and not raw_source.startswith("http"):
|
|
1866
|
-
resolved = bitbucket_link_resolver.resolve(raw_source)
|
|
1867
|
-
source = f'<a href="{escape(resolved)}">{escape(raw_source)}</a>' if resolved else escape(raw_source)
|
|
1868
|
-
else:
|
|
1869
|
-
source = escape(raw_source)
|
|
1870
|
-
body_rows.append(
|
|
1871
|
-
"<tr>"
|
|
1872
|
-
f"<td>{status_macro}</td>"
|
|
1873
|
-
f"<td>{severity_macro}</td>"
|
|
1874
|
-
f"<td>{finding}</td>"
|
|
1875
|
-
f"<td>{reference}</td>"
|
|
1876
|
-
f"<td>{source}</td>"
|
|
1877
|
-
"</tr>"
|
|
1878
|
-
)
|
|
1879
|
-
|
|
1880
|
-
return (
|
|
1881
|
-
"<h2>Top Findings (Checklist) / Phát hiện nổi bật (Checklist)</h2>"
|
|
1882
|
-
f"<table>{thead}<tbody>{''.join(body_rows)}</tbody></table>"
|
|
1883
|
-
)
|
|
1884
|
-
|
|
1885
|
-
def _collect_top_findings_rows(
|
|
1886
|
-
self,
|
|
1887
|
-
*,
|
|
1888
|
-
checklist: AuditChecklist | None,
|
|
1889
|
-
report_dir: Path,
|
|
1890
|
-
limit: int,
|
|
1891
|
-
) -> list[dict[str, str]]:
|
|
1892
|
-
rows: list[dict[str, str]] = []
|
|
1893
|
-
if checklist:
|
|
1894
|
-
actionable = {"FAIL", "PARTIAL", "ERROR"}
|
|
1895
|
-
ranked = [r for r in checklist.rows if r.status.value in actionable]
|
|
1896
|
-
ranked.sort(
|
|
1897
|
-
key=lambda r: (
|
|
1898
|
-
0 if (r.severity or "").upper() == "CRITICAL" else 1 if (r.severity or "").upper() == "HIGH" else 2,
|
|
1899
|
-
r.score_percent,
|
|
1900
|
-
)
|
|
1901
|
-
)
|
|
1902
|
-
for row in ranked[:limit]:
|
|
1903
|
-
check_ref = row.check_id or row.row_id
|
|
1904
|
-
evidence_ref = ""
|
|
1905
|
-
if row.evidence_refs:
|
|
1906
|
-
first = row.evidence_refs[0]
|
|
1907
|
-
evidence_ref = getattr(first, "ref_value", str(first))
|
|
1908
|
-
rows.append(
|
|
1909
|
-
{
|
|
1910
|
-
"status": row.status.value,
|
|
1911
|
-
"severity": row.severity or "",
|
|
1912
|
-
"finding": (row.finding or row.reason or "").strip(),
|
|
1913
|
-
"reference": evidence_ref or check_ref or "",
|
|
1914
|
-
"source": f"{check_ref or 'row'}",
|
|
1915
|
-
}
|
|
1916
|
-
)
|
|
1917
|
-
return rows
|
|
1918
|
-
|
|
1919
|
-
# Fallback: workflow-summary.json
|
|
1920
|
-
workflow_json = self._newest_named(report_dir, "workflow-summary.json")
|
|
1921
|
-
if workflow_json:
|
|
1922
|
-
try:
|
|
1923
|
-
payload = json.loads(workflow_json.read_text(encoding="utf-8"))
|
|
1924
|
-
for agent_result in payload.get("agent_results", []) if isinstance(payload, dict) else []:
|
|
1925
|
-
if not isinstance(agent_result, dict):
|
|
1926
|
-
continue
|
|
1927
|
-
agent_name = str(agent_result.get("agent", "agent"))
|
|
1928
|
-
for finding in (
|
|
1929
|
-
agent_result.get("findings", []) if isinstance(agent_result.get("findings"), list) else []
|
|
1930
|
-
):
|
|
1931
|
-
if not isinstance(finding, dict):
|
|
1932
|
-
continue
|
|
1933
|
-
finding_type = str(finding.get("type", "finding"))
|
|
1934
|
-
description = str(finding.get("description") or finding.get("title") or "").strip()
|
|
1935
|
-
location = str(finding.get("location") or finding.get("path") or "").strip()
|
|
1936
|
-
if not description:
|
|
1937
|
-
continue
|
|
1938
|
-
rows.append(
|
|
1939
|
-
{
|
|
1940
|
-
"status": "FAIL",
|
|
1941
|
-
"severity": str(finding.get("severity") or "").upper(),
|
|
1942
|
-
"finding": description,
|
|
1943
|
-
"reference": location,
|
|
1944
|
-
"source": f"{agent_name} / {finding_type} (workflow-summary.json)",
|
|
1945
|
-
}
|
|
1946
|
-
)
|
|
1947
|
-
if rows:
|
|
1948
|
-
return rows[:limit]
|
|
1949
|
-
except Exception:
|
|
1950
|
-
logger.warning("workflow_top_findings_parse_failed", path=str(workflow_json))
|
|
1951
|
-
|
|
1952
|
-
# Fallback: report.md synthesized summary section
|
|
1953
|
-
markdown_report = self._newest_named(report_dir, "report.md")
|
|
1954
|
-
if markdown_report:
|
|
1955
|
-
try:
|
|
1956
|
-
markdown_text = markdown_report.read_text(encoding="utf-8")
|
|
1957
|
-
rows.extend(self._extract_top_findings_from_markdown(markdown_text, limit=limit))
|
|
1958
|
-
if rows:
|
|
1959
|
-
return rows[:limit]
|
|
1960
|
-
except Exception:
|
|
1961
|
-
logger.warning("markdown_top_findings_parse_failed", path=str(markdown_report))
|
|
1962
|
-
|
|
1963
|
-
# Fallback: audit-checklist.xlsx
|
|
1964
|
-
checklist_xlsx = self._newest_named(report_dir, "audit-checklist.xlsx")
|
|
1965
|
-
if checklist_xlsx:
|
|
1966
|
-
try:
|
|
1967
|
-
from openpyxl import load_workbook
|
|
1968
|
-
|
|
1969
|
-
workbook = load_workbook(checklist_xlsx, data_only=True, read_only=True)
|
|
1970
|
-
sheet = workbook.active
|
|
1971
|
-
if sheet is None:
|
|
1972
|
-
return rows[:limit]
|
|
1973
|
-
header_row = next(sheet.iter_rows(min_row=1, max_row=1, values_only=True), None)
|
|
1974
|
-
if header_row:
|
|
1975
|
-
header_map = {
|
|
1976
|
-
str(cell).strip().lower(): idx for idx, cell in enumerate(header_row) if cell is not None
|
|
1977
|
-
}
|
|
1978
|
-
idx_status = header_map.get("status")
|
|
1979
|
-
idx_severity = header_map.get("severity")
|
|
1980
|
-
idx_finding = header_map.get("finding")
|
|
1981
|
-
idx_location = header_map.get("location")
|
|
1982
|
-
idx_provenance = header_map.get("provenance")
|
|
1983
|
-
for row_vals in sheet.iter_rows(min_row=2, values_only=True):
|
|
1984
|
-
if idx_finding is None:
|
|
1985
|
-
break
|
|
1986
|
-
finding_val = row_vals[idx_finding] if idx_finding < len(row_vals) else None
|
|
1987
|
-
if not finding_val:
|
|
1988
|
-
continue
|
|
1989
|
-
status_val = (
|
|
1990
|
-
row_vals[idx_status] if idx_status is not None and idx_status < len(row_vals) else ""
|
|
1991
|
-
)
|
|
1992
|
-
severity_val = (
|
|
1993
|
-
row_vals[idx_severity] if idx_severity is not None and idx_severity < len(row_vals) else ""
|
|
1994
|
-
)
|
|
1995
|
-
location_val = (
|
|
1996
|
-
row_vals[idx_location] if idx_location is not None and idx_location < len(row_vals) else ""
|
|
1997
|
-
)
|
|
1998
|
-
provenance_val = (
|
|
1999
|
-
row_vals[idx_provenance]
|
|
2000
|
-
if idx_provenance is not None and idx_provenance < len(row_vals)
|
|
2001
|
-
else ""
|
|
2002
|
-
)
|
|
2003
|
-
rows.append(
|
|
2004
|
-
{
|
|
2005
|
-
"status": str(status_val or ""),
|
|
2006
|
-
"severity": str(severity_val or ""),
|
|
2007
|
-
"finding": str(finding_val),
|
|
2008
|
-
"reference": str(location_val or ""),
|
|
2009
|
-
"source": str(provenance_val or ""),
|
|
2010
|
-
}
|
|
2011
|
-
)
|
|
2012
|
-
if len(rows) >= limit:
|
|
2013
|
-
break
|
|
2014
|
-
workbook.close()
|
|
2015
|
-
if rows:
|
|
2016
|
-
return rows[:limit]
|
|
2017
|
-
except Exception:
|
|
2018
|
-
logger.warning("xlsx_top_findings_parse_failed", path=str(checklist_xlsx))
|
|
2019
|
-
|
|
2020
|
-
return rows
|
|
2021
|
-
|
|
2022
|
-
def _extract_top_findings_from_markdown(self, markdown_text: str, *, limit: int) -> list[dict[str, str]]:
|
|
2023
|
-
match = re.search(r"^## Top Findings\s*$([\s\S]*?)(?=^##\s|\Z)", markdown_text, flags=re.MULTILINE)
|
|
2024
|
-
if not match:
|
|
2025
|
-
return []
|
|
2026
|
-
|
|
2027
|
-
rows: list[dict[str, str]] = []
|
|
2028
|
-
for line in match.group(1).splitlines():
|
|
2029
|
-
stripped = line.strip()
|
|
2030
|
-
if not stripped.startswith("- "):
|
|
2031
|
-
continue
|
|
2032
|
-
tokens = re.findall(r"`([^`]+)`", stripped)
|
|
2033
|
-
if len(tokens) < 3:
|
|
2034
|
-
continue
|
|
2035
|
-
check_id, status, score = tokens[:3]
|
|
2036
|
-
rows.append(
|
|
2037
|
-
{
|
|
2038
|
-
"status": status,
|
|
2039
|
-
"severity": "",
|
|
2040
|
-
"finding": f"{check_id} scored {score}",
|
|
2041
|
-
"reference": check_id,
|
|
2042
|
-
"source": "report.md",
|
|
2043
|
-
}
|
|
2044
|
-
)
|
|
2045
|
-
if len(rows) >= limit:
|
|
2046
|
-
break
|
|
2047
|
-
return rows
|
|
2048
|
-
|
|
2049
|
-
def _build_status_macro_text(self, status: str) -> str:
|
|
2050
|
-
token = status.strip().upper()
|
|
2051
|
-
if token in {"PASS", "PASSED", "ĐẠT"}:
|
|
2052
|
-
return ConfluenceMacroBuilder.status_macro("ĐẠT", "Green", subtle=True)
|
|
2053
|
-
if token in {"PARTIAL", "MỘT PHẦN"}:
|
|
2054
|
-
return ConfluenceMacroBuilder.status_macro("MỘT PHẦN", "Yellow", subtle=True)
|
|
2055
|
-
if token in {"ERROR", "LỖI"}:
|
|
2056
|
-
return ConfluenceMacroBuilder.status_macro("LỖI", "Red", subtle=False)
|
|
2057
|
-
if token in {"NA", "N/A", "KHÔNG ÁP DỤNG"}:
|
|
2058
|
-
return ConfluenceMacroBuilder.status_macro("KHÔNG ÁP DỤNG", "Grey", subtle=True)
|
|
2059
|
-
return ConfluenceMacroBuilder.status_macro("KHÔNG ĐẠT", "Red", subtle=False)
|
|
2060
|
-
|
|
2061
|
-
def _build_severity_macro_text(self, severity: str) -> str:
|
|
2062
|
-
token = severity.strip().upper()
|
|
2063
|
-
if token == "CRITICAL":
|
|
2064
|
-
return ConfluenceMacroBuilder.status_macro("CRITICAL", "Red", subtle=False)
|
|
2065
|
-
if token == "HIGH":
|
|
2066
|
-
return ConfluenceMacroBuilder.status_macro("HIGH", "Red", subtle=True)
|
|
2067
|
-
if token == "MEDIUM":
|
|
2068
|
-
return ConfluenceMacroBuilder.status_macro("MEDIUM", "Yellow", subtle=True)
|
|
2069
|
-
if token == "LOW":
|
|
2070
|
-
return ConfluenceMacroBuilder.status_macro("LOW", "Blue", subtle=True)
|
|
2071
|
-
if token:
|
|
2072
|
-
return ConfluenceMacroBuilder.status_macro(token, "Grey", subtle=True)
|
|
2073
|
-
return "-"
|
|
2074
|
-
|
|
2075
|
-
def _render_provenance_html(
|
|
2076
|
-
self,
|
|
2077
|
-
*,
|
|
2078
|
-
checklist: AuditChecklist | None,
|
|
2079
|
-
report_dir: Path,
|
|
2080
|
-
thread_id: str | None = None,
|
|
2081
|
-
) -> str | None:
|
|
2082
|
-
"""Render Provenance block (TSK-182).
|
|
2083
|
-
|
|
2084
|
-
Includes:
|
|
2085
|
-
- thread-id: Workflow thread identifier
|
|
2086
|
-
- template_hash: SHA256 of template content
|
|
2087
|
-
- template_version: Template version string
|
|
2088
|
-
- evidence_hash: SHA256 of evidence bundle
|
|
2089
|
-
- evaluated_at: Timestamp of evaluation
|
|
2090
|
-
- row_llm_mode: LLM mode used
|
|
2091
|
-
- model: LLM model used (if applicable)
|
|
2092
|
-
"""
|
|
2093
|
-
provenance_items: list[tuple[str, str]] = []
|
|
2094
|
-
|
|
2095
|
-
effective_thread_id = thread_id or self._infer_thread_id_from_report_dir(report_dir)
|
|
2096
|
-
if effective_thread_id:
|
|
2097
|
-
provenance_items.append(("Thread ID", effective_thread_id))
|
|
2098
|
-
|
|
2099
|
-
if checklist:
|
|
2100
|
-
provenance_items.extend(
|
|
2101
|
-
[
|
|
2102
|
-
("Template", f"{checklist.template_name} v{checklist.template_version}"),
|
|
2103
|
-
("Template Hash", checklist.template_hash),
|
|
2104
|
-
("Evidence Hash", checklist.evidence_hash),
|
|
2105
|
-
("Evaluated At", checklist.generated_at.isoformat() if checklist.generated_at else "N/A"),
|
|
2106
|
-
("Row LLM Mode", checklist.row_llm_mode),
|
|
2107
|
-
]
|
|
2108
|
-
)
|
|
2109
|
-
|
|
2110
|
-
# Extract model from first row with LLM provenance
|
|
2111
|
-
for row in checklist.rows:
|
|
2112
|
-
if row.provenance and row.provenance.model:
|
|
2113
|
-
provenance_items.append(("LLM Model", row.provenance.model))
|
|
2114
|
-
break
|
|
2115
|
-
else:
|
|
2116
|
-
# Try to extract from workflow-summary.json
|
|
2117
|
-
workflow_json = self._newest_named(report_dir, "workflow-summary.json")
|
|
2118
|
-
if workflow_json:
|
|
2119
|
-
try:
|
|
2120
|
-
payload = json.loads(workflow_json.read_text(encoding="utf-8"))
|
|
2121
|
-
if isinstance(payload, dict):
|
|
2122
|
-
for key in ("template_hash", "evidence_hash", "row_llm_mode", "model"):
|
|
2123
|
-
value = payload.get(key)
|
|
2124
|
-
if value:
|
|
2125
|
-
label = key.replace("_", " ").title()
|
|
2126
|
-
provenance_items.append((label, str(value)))
|
|
2127
|
-
evaluated_at = payload.get("evaluated_at") or payload.get("generated_at")
|
|
2128
|
-
if evaluated_at:
|
|
2129
|
-
provenance_items.append(("Evaluated At", str(evaluated_at)))
|
|
2130
|
-
merge_level = payload.get("merge_level")
|
|
2131
|
-
if merge_level:
|
|
2132
|
-
provenance_items.append(("Merge Level", str(merge_level)))
|
|
2133
|
-
summary_score_source = payload.get("summary_score_source")
|
|
2134
|
-
if summary_score_source:
|
|
2135
|
-
provenance_items.append(("Summary Score Source", str(summary_score_source)))
|
|
2136
|
-
source_runs = payload.get("source_runs")
|
|
2137
|
-
if isinstance(source_runs, list):
|
|
2138
|
-
normalized_runs = [str(item).strip() for item in source_runs if str(item).strip()]
|
|
2139
|
-
if normalized_runs:
|
|
2140
|
-
provenance_items.append(("Source Runs", ", ".join(normalized_runs[:5])))
|
|
2141
|
-
except Exception:
|
|
2142
|
-
pass
|
|
2143
|
-
|
|
2144
|
-
if not provenance_items:
|
|
2145
|
-
return None
|
|
2146
|
-
|
|
2147
|
-
out: list[str] = [
|
|
2148
|
-
"<h2>Provenance</h2>",
|
|
2149
|
-
'<ac:structured-macro ac:name="info">',
|
|
2150
|
-
"<ac:rich-text-body>",
|
|
2151
|
-
"<table>",
|
|
2152
|
-
"<tbody>",
|
|
2153
|
-
]
|
|
2154
|
-
|
|
2155
|
-
for label, value in provenance_items:
|
|
2156
|
-
out.append(f"<tr><td><strong>{escape(label)}</strong></td><td><code>{escape(value)}</code></td></tr>")
|
|
2157
|
-
|
|
2158
|
-
out.extend(
|
|
2159
|
-
[
|
|
2160
|
-
"</tbody>",
|
|
2161
|
-
"</table>",
|
|
2162
|
-
"</ac:rich-text-body>",
|
|
2163
|
-
"</ac:structured-macro>",
|
|
2164
|
-
]
|
|
2165
|
-
)
|
|
2166
|
-
return "\n".join(out)
|
|
2167
|
-
|
|
2168
|
-
def _infer_thread_id_from_report_dir(self, report_dir: Path) -> str | None:
|
|
2169
|
-
"""Infer run/thread identity from standard report directory layout."""
|
|
2170
|
-
parts = report_dir.parts
|
|
2171
|
-
try:
|
|
2172
|
-
repos_idx = parts.index("repos")
|
|
2173
|
-
except ValueError:
|
|
2174
|
-
return None
|
|
2175
|
-
if repos_idx >= 1:
|
|
2176
|
-
candidate = parts[repos_idx - 1].strip()
|
|
2177
|
-
if candidate:
|
|
2178
|
-
return candidate
|
|
2179
|
-
return None
|
|
2180
|
-
|
|
2181
|
-
def _render_attachments_macro_html(self) -> str:
|
|
2182
|
-
# Storage format macro. We intentionally prefer the macro over an explicit list
|
|
2183
|
-
# so the page always reflects the latest attachment state.
|
|
2184
|
-
return "\n".join(
|
|
2185
|
-
[
|
|
2186
|
-
"<h2>Attachments</h2>",
|
|
2187
|
-
'<ac:structured-macro ac:name="attachments">',
|
|
2188
|
-
'<ac:parameter ac:name="old">false</ac:parameter>',
|
|
2189
|
-
"</ac:structured-macro>",
|
|
2190
|
-
]
|
|
2191
|
-
)
|
|
2192
|
-
|
|
2193
|
-
def _render_attachments_index_html(self, attachments_index: list[dict[str, str]]) -> str:
|
|
2194
|
-
"""TSK-170B: Render attachments index section with links to uploaded files.
|
|
2195
|
-
|
|
2196
|
-
Args:
|
|
2197
|
-
attachments_index: List of attachment info dicts with 'name' and 'id' keys.
|
|
2198
|
-
|
|
2199
|
-
Returns:
|
|
2200
|
-
HTML string for attachments index section.
|
|
2201
|
-
"""
|
|
2202
|
-
if not attachments_index:
|
|
2203
|
-
return ""
|
|
2204
|
-
|
|
2205
|
-
out = [
|
|
2206
|
-
"<h2>📎 Attachments Index</h2>",
|
|
2207
|
-
"<table><thead><tr><th>File</th><th>Type</th><th>Download</th></tr></thead>",
|
|
2208
|
-
"<tbody>",
|
|
2209
|
-
]
|
|
2210
|
-
|
|
2211
|
-
for att in attachments_index:
|
|
2212
|
-
name = escape(att.get("name", "Unknown"))
|
|
2213
|
-
ext = Path(name).suffix.lower() if "." in name else ""
|
|
2214
|
-
file_type = {
|
|
2215
|
-
".xlsx": "Excel Spreadsheet",
|
|
2216
|
-
".json": "JSON Data",
|
|
2217
|
-
".md": "Markdown",
|
|
2218
|
-
".sarif": "SARIF Report",
|
|
2219
|
-
".pdf": "PDF Document",
|
|
2220
|
-
}.get(ext, "File")
|
|
2221
|
-
|
|
2222
|
-
# Confluence attachment link using ri:attachment
|
|
2223
|
-
link = f'<ac:link><ri:attachment ri:filename="{name}"/><ac:plain-text-link-body><![CDATA[{name}]]></ac:plain-text-link-body></ac:link>'
|
|
2224
|
-
out.append(f"<tr><td>{name}</td><td>{file_type}</td><td>{link}</td></tr>")
|
|
2225
|
-
|
|
2226
|
-
out.append("</tbody></table>")
|
|
2227
|
-
return "\n".join(out)
|
|
2228
|
-
|
|
2229
|
-
@staticmethod
|
|
2230
|
-
def _extract_hash(payload: dict[str, Any]) -> str | None:
|
|
2231
|
-
for key in ("sha256", "content_hash", "hash", "file_sha256"):
|
|
2232
|
-
value = payload.get(key)
|
|
2233
|
-
if isinstance(value, str) and value.strip():
|
|
2234
|
-
return value.strip().lower()
|
|
2235
|
-
return None
|
|
2236
|
-
|
|
2237
|
-
def _load_incremental_plan(self, report_dir: Path) -> tuple[_IncrementalPlan | None, str | None]:
|
|
2238
|
-
meta_path = self._newest_named(report_dir, "audit-checklist.meta.json")
|
|
2239
|
-
delta_path = self._newest_named(report_dir, "audit-checklist.delta.json")
|
|
2240
|
-
if not meta_path or not delta_path:
|
|
2241
|
-
return None, "incremental_missing_meta_or_delta"
|
|
2242
|
-
|
|
2243
|
-
try:
|
|
2244
|
-
meta_payload = json.loads(meta_path.read_text(encoding="utf-8"))
|
|
2245
|
-
delta_payload = json.loads(delta_path.read_text(encoding="utf-8"))
|
|
2246
|
-
except Exception:
|
|
2247
|
-
return None, "incremental_artifact_parse_error"
|
|
2248
|
-
if not isinstance(meta_payload, dict) or not isinstance(delta_payload, dict):
|
|
2249
|
-
return None, "incremental_artifact_invalid_shape"
|
|
2250
|
-
|
|
2251
|
-
meta_template = meta_payload.get("template_hash")
|
|
2252
|
-
delta_template = delta_payload.get("template_hash")
|
|
2253
|
-
if isinstance(meta_template, str) and isinstance(delta_template, str) and meta_template != delta_template:
|
|
2254
|
-
return None, "incremental_template_hash_mismatch"
|
|
2255
|
-
|
|
2256
|
-
meta_evidence = meta_payload.get("evidence_hash")
|
|
2257
|
-
delta_evidence = delta_payload.get("evidence_hash")
|
|
2258
|
-
if isinstance(meta_evidence, str) and isinstance(delta_evidence, str) and meta_evidence != delta_evidence:
|
|
2259
|
-
return None, "incremental_evidence_hash_mismatch"
|
|
2260
|
-
|
|
2261
|
-
changed_rows_raw = delta_payload.get("rows_changed")
|
|
2262
|
-
if not isinstance(changed_rows_raw, int):
|
|
2263
|
-
changed_rows_raw = delta_payload.get("changed_row_count")
|
|
2264
|
-
if not isinstance(changed_rows_raw, int):
|
|
2265
|
-
changed_rows_raw = delta_payload.get("changed_rows")
|
|
2266
|
-
if isinstance(changed_rows_raw, list):
|
|
2267
|
-
changed_rows_raw = len(changed_rows_raw)
|
|
2268
|
-
changed_rows = changed_rows_raw if isinstance(changed_rows_raw, int) and changed_rows_raw >= 0 else None
|
|
2269
|
-
if changed_rows is None:
|
|
2270
|
-
row_changes = delta_payload.get("row_changes")
|
|
2271
|
-
if isinstance(row_changes, list):
|
|
2272
|
-
changed_rows = len(row_changes)
|
|
2273
|
-
if changed_rows is None:
|
|
2274
|
-
return None, "incremental_missing_rows_changed"
|
|
2275
|
-
|
|
2276
|
-
unchanged_rows_raw = delta_payload.get("rows_unchanged")
|
|
2277
|
-
if not isinstance(unchanged_rows_raw, int):
|
|
2278
|
-
unchanged_rows_raw = meta_payload.get("rows_unchanged")
|
|
2279
|
-
if not isinstance(unchanged_rows_raw, int):
|
|
2280
|
-
row_count_raw = meta_payload.get("row_count")
|
|
2281
|
-
if isinstance(row_count_raw, int):
|
|
2282
|
-
unchanged_rows_raw = max(0, row_count_raw - changed_rows)
|
|
2283
|
-
rows_unchanged = unchanged_rows_raw if isinstance(unchanged_rows_raw, int) and unchanged_rows_raw >= 0 else 0
|
|
2284
|
-
|
|
2285
|
-
changed_attachments: set[str] = set()
|
|
2286
|
-
unchanged_attachments: set[str] = set()
|
|
2287
|
-
|
|
2288
|
-
changed_list = delta_payload.get("changed_attachments")
|
|
2289
|
-
if isinstance(changed_list, list):
|
|
2290
|
-
for item in changed_list:
|
|
2291
|
-
if isinstance(item, str):
|
|
2292
|
-
changed_attachments.add(item.lower())
|
|
2293
|
-
elif isinstance(item, dict):
|
|
2294
|
-
name = item.get("name") or item.get("title") or item.get("file")
|
|
2295
|
-
if isinstance(name, str) and name:
|
|
2296
|
-
changed_attachments.add(name.lower())
|
|
2297
|
-
|
|
2298
|
-
unchanged_list = delta_payload.get("unchanged_attachments")
|
|
2299
|
-
if isinstance(unchanged_list, list):
|
|
2300
|
-
for item in unchanged_list:
|
|
2301
|
-
if isinstance(item, str):
|
|
2302
|
-
unchanged_attachments.add(item.lower())
|
|
2303
|
-
elif isinstance(item, dict):
|
|
2304
|
-
name = item.get("name") or item.get("title") or item.get("file")
|
|
2305
|
-
if isinstance(name, str) and name:
|
|
2306
|
-
unchanged_attachments.add(name.lower())
|
|
2307
|
-
|
|
2308
|
-
attachments_delta = delta_payload.get("attachments")
|
|
2309
|
-
if isinstance(attachments_delta, list):
|
|
2310
|
-
for item in attachments_delta:
|
|
2311
|
-
if not isinstance(item, dict):
|
|
2312
|
-
continue
|
|
2313
|
-
name = item.get("name") or item.get("title") or item.get("file")
|
|
2314
|
-
if not isinstance(name, str) or not name:
|
|
2315
|
-
continue
|
|
2316
|
-
before_hash = item.get("before_hash") or item.get("old_hash")
|
|
2317
|
-
after_hash = item.get("after_hash") or item.get("new_hash") or item.get("content_hash")
|
|
2318
|
-
if isinstance(before_hash, str) and isinstance(after_hash, str):
|
|
2319
|
-
if before_hash.lower() == after_hash.lower():
|
|
2320
|
-
unchanged_attachments.add(name.lower())
|
|
2321
|
-
else:
|
|
2322
|
-
changed_attachments.add(name.lower())
|
|
2323
|
-
|
|
2324
|
-
return (
|
|
2325
|
-
_IncrementalPlan(
|
|
2326
|
-
rows_changed=changed_rows,
|
|
2327
|
-
rows_unchanged=rows_unchanged,
|
|
2328
|
-
changed_attachments=changed_attachments,
|
|
2329
|
-
unchanged_attachments=unchanged_attachments,
|
|
2330
|
-
template_hash=meta_template if isinstance(meta_template, str) else None,
|
|
2331
|
-
evidence_hash=meta_evidence if isinstance(meta_evidence, str) else None,
|
|
2332
|
-
),
|
|
2333
|
-
None,
|
|
2334
|
-
)
|
|
2335
|
-
|
|
2336
|
-
@staticmethod
|
|
2337
|
-
def _file_sha256(path: Path) -> str:
|
|
2338
|
-
digest = hashlib.sha256()
|
|
2339
|
-
with path.open("rb") as fh:
|
|
2340
|
-
for chunk in iter(lambda: fh.read(1024 * 1024), b""):
|
|
2341
|
-
digest.update(chunk)
|
|
2342
|
-
return digest.hexdigest()
|
|
2343
|
-
|
|
2344
|
-
def _attachment_payload_hash(self, payload: dict[str, Any]) -> str | None:
|
|
2345
|
-
direct = self._extract_hash(payload)
|
|
2346
|
-
if direct:
|
|
2347
|
-
return direct
|
|
2348
|
-
metadata = payload.get("metadata")
|
|
2349
|
-
if isinstance(metadata, dict):
|
|
2350
|
-
nested = self._extract_hash(metadata)
|
|
2351
|
-
if nested:
|
|
2352
|
-
return nested
|
|
2353
|
-
comment = metadata.get("comment")
|
|
2354
|
-
if isinstance(comment, str):
|
|
2355
|
-
match = re.search(r"sha256[:=]([a-fA-F0-9]{64})", comment)
|
|
2356
|
-
if match:
|
|
2357
|
-
return match.group(1).lower()
|
|
2358
|
-
return None
|
|
2359
|
-
|
|
2360
|
-
async def extend_run_page_readiness(
|
|
2361
|
-
self,
|
|
2362
|
-
*,
|
|
2363
|
-
page_id: str,
|
|
2364
|
-
title: str,
|
|
2365
|
-
report_dir: Path,
|
|
2366
|
-
) -> bool:
|
|
2367
|
-
"""Append or patch readiness sections on an existing run page."""
|
|
2368
|
-
readiness_sections = self._render_readiness_sections(report_dir)
|
|
2369
|
-
if not readiness_sections:
|
|
2370
|
-
return False
|
|
2371
|
-
|
|
2372
|
-
page = await self._retry(
|
|
2373
|
-
lambda: self.client.get_content_page(page_id, expand="body.storage,version"),
|
|
2374
|
-
page_id=page_id,
|
|
2375
|
-
action="get_content_page",
|
|
2376
|
-
)
|
|
2377
|
-
if not isinstance(page, dict):
|
|
2378
|
-
return False
|
|
2379
|
-
existing_body = self._extract_storage_body(page)
|
|
2380
|
-
if not isinstance(existing_body, str) or not existing_body.strip():
|
|
2381
|
-
return False
|
|
2382
|
-
|
|
2383
|
-
patched = existing_body
|
|
2384
|
-
patched_count = 0
|
|
2385
|
-
for key, replacement in readiness_sections.items():
|
|
2386
|
-
updated, changed = self._replace_section(patched, key, replacement)
|
|
2387
|
-
if changed:
|
|
2388
|
-
patched = updated
|
|
2389
|
-
patched_count += 1
|
|
2390
|
-
continue
|
|
2391
|
-
patched = f"{patched}\n\n{self._wrap_section(key, replacement)}"
|
|
2392
|
-
patched_count += 1
|
|
2393
|
-
|
|
2394
|
-
if patched_count <= 0:
|
|
2395
|
-
return False
|
|
2396
|
-
|
|
2397
|
-
with tempfile.NamedTemporaryFile(
|
|
2398
|
-
mode="w", suffix=".readiness.storage.html", delete=False, encoding="utf-8"
|
|
2399
|
-
) as f:
|
|
2400
|
-
f.write(patched)
|
|
2401
|
-
temp_path = Path(f.name)
|
|
2402
|
-
try:
|
|
2403
|
-
resolved_title = self._resolve_update_title(page, title)
|
|
2404
|
-
await self._retry(
|
|
2405
|
-
lambda: self.client.update_page(
|
|
2406
|
-
page_id,
|
|
2407
|
-
title=resolved_title,
|
|
2408
|
-
body_file=temp_path,
|
|
2409
|
-
version=self._extract_version(page),
|
|
2410
|
-
),
|
|
2411
|
-
page_id=page_id,
|
|
2412
|
-
title=resolved_title,
|
|
2413
|
-
version=self._extract_version(page),
|
|
2414
|
-
action="update_page_readiness_sections",
|
|
2415
|
-
)
|
|
2416
|
-
finally:
|
|
2417
|
-
if temp_path.exists():
|
|
2418
|
-
temp_path.unlink()
|
|
2419
|
-
|
|
2420
|
-
logger.info(
|
|
2421
|
-
"run_page_readiness_extended",
|
|
2422
|
-
page_id=page_id,
|
|
2423
|
-
sections_patched=patched_count,
|
|
2424
|
-
report_dir=str(report_dir),
|
|
2425
|
-
)
|
|
2426
|
-
return True
|
|
2427
|
-
|
|
2428
|
-
async def publish_project_aggregate(
|
|
2429
|
-
self,
|
|
2430
|
-
*,
|
|
2431
|
-
report_dir: Path,
|
|
2432
|
-
parent_page: str,
|
|
2433
|
-
page_title: str,
|
|
2434
|
-
include_attachments: bool = False,
|
|
2435
|
-
project_storage_key: str | None = None,
|
|
2436
|
-
hierarchy_target: str | None = None,
|
|
2437
|
-
project_name_hint: str | None = None,
|
|
2438
|
-
) -> UploadResult:
|
|
2439
|
-
"""Publish project aggregate readiness page under deterministic project hierarchy.
|
|
2440
|
-
|
|
2441
|
-
TSK-940.71 (FR-282): When ``hierarchy_target="repo"``, the aggregate
|
|
2442
|
-
page is placed under the shared ``Project Audit - {key}`` node
|
|
2443
|
-
alongside repo pages (unified hierarchy). The default
|
|
2444
|
-
(``hierarchy_target="project"`` or ``None``) routes to the separate
|
|
2445
|
-
``Project Analysis - {key}`` branch.
|
|
2446
|
-
|
|
2447
|
-
``project_name_hint`` provides the human-readable project name used to
|
|
2448
|
-
generate descriptive page titles such as
|
|
2449
|
-
``"Project Analysis - Core Payment (88718943)"`` instead of
|
|
2450
|
-
``"Project Analysis - 88718943"``.
|
|
2451
|
-
|
|
2452
|
-
Phase 127: Default changed to skip attachments (page-only mode).
|
|
2453
|
-
Use --upload-attachments flag to enable attachments.
|
|
2454
|
-
"""
|
|
2455
|
-
if self.client.is_degraded:
|
|
2456
|
-
logger.warning(
|
|
2457
|
-
"confluence_upload_skipped_degraded",
|
|
2458
|
-
method="publish_project_aggregate",
|
|
2459
|
-
note="Confluence client is degraded — skipping upload",
|
|
2460
|
-
)
|
|
2461
|
-
return UploadResult(page_id="", page_url=None, attachments=[], fallback_reason="degraded")
|
|
2462
|
-
from vds_audit_orchestrator.models.readiness import ProjectReadinessReport
|
|
2463
|
-
from vds_audit_orchestrator.publishers.project_aggregate_renderer import ProjectAggregateRenderer
|
|
2464
|
-
|
|
2465
|
-
project_readiness_path = self._newest_named(report_dir, "project-readiness.json")
|
|
2466
|
-
if not project_readiness_path:
|
|
2467
|
-
raise ValueError(f"Missing project-readiness.json in {report_dir}")
|
|
2468
|
-
payload = json.loads(project_readiness_path.read_text(encoding="utf-8"))
|
|
2469
|
-
report = ProjectReadinessReport.model_validate(payload)
|
|
2470
|
-
rendered_html = ProjectAggregateRenderer().render_page(report)
|
|
2471
|
-
if not self.validate_storage_format(rendered_html):
|
|
2472
|
-
raise ValueError("Rendered project aggregate storage HTML is invalid.")
|
|
2473
|
-
|
|
2474
|
-
resolved_hierarchy_target = hierarchy_target or HierarchyTarget.PROJECT.value
|
|
2475
|
-
parent_id = self.extract_page_id(parent_page)
|
|
2476
|
-
publish_parent_id, hierarchy_lineage = await self._resolve_publish_parent(
|
|
2477
|
-
parent_id=parent_id,
|
|
2478
|
-
page_title=page_title,
|
|
2479
|
-
project_storage_key=project_storage_key,
|
|
2480
|
-
repo_storage_key=None,
|
|
2481
|
-
hierarchy_target=resolved_hierarchy_target,
|
|
2482
|
-
project_name_hint=project_name_hint,
|
|
2483
|
-
)
|
|
2484
|
-
|
|
2485
|
-
analysis_root_id = str(hierarchy_lineage.get("project_analysis_root_page_id") or "").strip() or None
|
|
2486
|
-
# Match against the deterministic analysis title generated by _resolve_publish_parent
|
|
2487
|
-
# (stored in lineage) so both old-format (PSK only) and new-format (name + PSK) pages
|
|
2488
|
-
# are recognised as the analysis root and not re-created as a child page.
|
|
2489
|
-
_deterministic_analysis_title = str(hierarchy_lineage.get("project_analysis_title") or "").strip()
|
|
2490
|
-
use_analysis_root_as_target = (
|
|
2491
|
-
resolved_hierarchy_target == HierarchyTarget.PROJECT.value
|
|
2492
|
-
and analysis_root_id is not None
|
|
2493
|
-
and bool(_deterministic_analysis_title)
|
|
2494
|
-
and str(page_title).strip() == _deterministic_analysis_title
|
|
2495
|
-
)
|
|
2496
|
-
|
|
2497
|
-
# Phase 154: Registry-first resolution for aggregate pages
|
|
2498
|
-
agg_project_key = self._normalize_hierarchy_key(project_storage_key, fallback=parent_id)
|
|
2499
|
-
agg_registry_hit = False
|
|
2500
|
-
existing_run: dict | None = None
|
|
2501
|
-
if not use_analysis_root_as_target:
|
|
2502
|
-
entry = self._lookup_page(agg_project_key, "", "aggregate")
|
|
2503
|
-
if entry:
|
|
2504
|
-
reg_page_id = str(entry["confluence_page_id"])
|
|
2505
|
-
if await self._page_exists(reg_page_id):
|
|
2506
|
-
existing_run = {"id": reg_page_id}
|
|
2507
|
-
agg_registry_hit = True
|
|
2508
|
-
else:
|
|
2509
|
-
self._delete_registry_entry(agg_project_key, "", "aggregate")
|
|
2510
|
-
|
|
2511
|
-
if not use_analysis_root_as_target and not agg_registry_hit:
|
|
2512
|
-
existing_run = await self._find_child_page(publish_parent_id, page_title)
|
|
2513
|
-
reused_existing_page = bool(existing_run and existing_run.get("id")) or bool(use_analysis_root_as_target)
|
|
2514
|
-
with tempfile.NamedTemporaryFile(
|
|
2515
|
-
mode="w", suffix=".project-aggregate.storage.html", delete=False, encoding="utf-8"
|
|
2516
|
-
) as f:
|
|
2517
|
-
f.write(rendered_html)
|
|
2518
|
-
body_path = Path(f.name)
|
|
2519
|
-
try:
|
|
2520
|
-
if use_analysis_root_as_target and analysis_root_id is not None:
|
|
2521
|
-
page_id = analysis_root_id
|
|
2522
|
-
page = await self._retry(
|
|
2523
|
-
lambda: self.client.get_content_page(page_id, expand="title,version"),
|
|
2524
|
-
title=page_title,
|
|
2525
|
-
page_id=page_id,
|
|
2526
|
-
action="get_content_page",
|
|
2527
|
-
)
|
|
2528
|
-
resolved_title = self._resolve_update_title(page if isinstance(page, dict) else {}, page_title)
|
|
2529
|
-
await self._retry(
|
|
2530
|
-
lambda: self.client.update_page(
|
|
2531
|
-
page_id,
|
|
2532
|
-
title=resolved_title,
|
|
2533
|
-
body_file=body_path,
|
|
2534
|
-
version=self._extract_version(page if isinstance(page, dict) else {}),
|
|
2535
|
-
),
|
|
2536
|
-
page_id=page_id,
|
|
2537
|
-
title=resolved_title,
|
|
2538
|
-
action="update_project_aggregate_page",
|
|
2539
|
-
)
|
|
2540
|
-
elif existing_run and existing_run.get("id"):
|
|
2541
|
-
page_id = str(existing_run.get("id"))
|
|
2542
|
-
page = await self._retry(
|
|
2543
|
-
lambda: self.client.get_content_page(page_id, expand="title,version"),
|
|
2544
|
-
title=page_title,
|
|
2545
|
-
page_id=page_id,
|
|
2546
|
-
action="get_content_page",
|
|
2547
|
-
)
|
|
2548
|
-
resolved_title = self._resolve_update_title(page if isinstance(page, dict) else {}, page_title)
|
|
2549
|
-
await self._retry(
|
|
2550
|
-
lambda: self.client.update_page(
|
|
2551
|
-
page_id,
|
|
2552
|
-
title=resolved_title,
|
|
2553
|
-
body_file=body_path,
|
|
2554
|
-
version=self._extract_version(page if isinstance(page, dict) else {}),
|
|
2555
|
-
),
|
|
2556
|
-
page_id=page_id,
|
|
2557
|
-
title=resolved_title,
|
|
2558
|
-
action="update_project_aggregate_page",
|
|
2559
|
-
)
|
|
2560
|
-
else:
|
|
2561
|
-
space_key = await self._get_space_key(publish_parent_id)
|
|
2562
|
-
upserted = await self._retry(
|
|
2563
|
-
lambda: self.client.create_or_update_page(
|
|
2564
|
-
space_key=space_key,
|
|
2565
|
-
title=page_title,
|
|
2566
|
-
body_file=body_path,
|
|
2567
|
-
parent_id=publish_parent_id,
|
|
2568
|
-
),
|
|
2569
|
-
space_key=space_key,
|
|
2570
|
-
title=page_title,
|
|
2571
|
-
parent_id=publish_parent_id,
|
|
2572
|
-
action="create_or_update_project_aggregate_page",
|
|
2573
|
-
)
|
|
2574
|
-
page_id_raw = upserted.get("id") if isinstance(upserted, dict) else None
|
|
2575
|
-
if not page_id_raw:
|
|
2576
|
-
raise ValueError("Confluence create_or_update_page returned no page id for project aggregate page.")
|
|
2577
|
-
page_id = str(page_id_raw)
|
|
2578
|
-
# Register immediately so concurrent uploads find the page via
|
|
2579
|
-
# registry instead of racing through CQL. The final registration
|
|
2580
|
-
# at the end is an idempotent upsert.
|
|
2581
|
-
self._register_page(
|
|
2582
|
-
agg_project_key,
|
|
2583
|
-
"",
|
|
2584
|
-
"aggregate",
|
|
2585
|
-
page_id,
|
|
2586
|
-
page_title,
|
|
2587
|
-
publish_parent_id,
|
|
2588
|
-
)
|
|
2589
|
-
finally:
|
|
2590
|
-
if body_path.exists():
|
|
2591
|
-
body_path.unlink()
|
|
2592
|
-
|
|
2593
|
-
# Phase 156: Move aggregate page to correct parent when reused from
|
|
2594
|
-
# registry under a different Confluence parent.
|
|
2595
|
-
if reused_existing_page:
|
|
2596
|
-
await self._move_page_if_parent_mismatched(page_id, publish_parent_id)
|
|
2597
|
-
|
|
2598
|
-
uploaded: list[str] = []
|
|
2599
|
-
if include_attachments:
|
|
2600
|
-
for path in report_dir.iterdir():
|
|
2601
|
-
if not path.is_file():
|
|
2602
|
-
continue
|
|
2603
|
-
if path.name.lower() not in {
|
|
2604
|
-
"project-readiness.json",
|
|
2605
|
-
"project-readiness.xlsx",
|
|
2606
|
-
"project-aggregate.json",
|
|
2607
|
-
"project-aggregate.xlsx",
|
|
2608
|
-
}:
|
|
2609
|
-
continue
|
|
2610
|
-
try:
|
|
2611
|
-
payload_upload = await self._retry(
|
|
2612
|
-
lambda path=path: self.client.upload_attachment(page_id=page_id, file_path=path),
|
|
2613
|
-
page_id=page_id,
|
|
2614
|
-
file_path=str(path),
|
|
2615
|
-
action="upload_attachment_project_aggregate",
|
|
2616
|
-
)
|
|
2617
|
-
except DataSourceError as e:
|
|
2618
|
-
if self._is_permission_error(e):
|
|
2619
|
-
raise DataSourceError(
|
|
2620
|
-
self._build_permission_error_message("upload_attachment", page_id),
|
|
2621
|
-
context={"page_id": page_id, "file_path": str(path), "operation": "attachment_upload"},
|
|
2622
|
-
) from e
|
|
2623
|
-
raise
|
|
2624
|
-
|
|
2625
|
-
attachment_id = payload_upload.get("id") if isinstance(payload_upload, dict) else None
|
|
2626
|
-
if attachment_id:
|
|
2627
|
-
uploaded.append(str(attachment_id))
|
|
2628
|
-
|
|
2629
|
-
logger.info(
|
|
2630
|
-
"project_aggregate_published",
|
|
2631
|
-
page_id=page_id,
|
|
2632
|
-
parent_id=publish_parent_id,
|
|
2633
|
-
attachments_uploaded=len(uploaded),
|
|
2634
|
-
)
|
|
2635
|
-
|
|
2636
|
-
# Phase 154: Register the aggregate page in the registry
|
|
2637
|
-
self._register_page(
|
|
2638
|
-
agg_project_key,
|
|
2639
|
-
"",
|
|
2640
|
-
"aggregate",
|
|
2641
|
-
page_id,
|
|
2642
|
-
page_title,
|
|
2643
|
-
publish_parent_id,
|
|
2644
|
-
)
|
|
2645
|
-
|
|
2646
|
-
return UploadResult(
|
|
2647
|
-
page_id=page_id,
|
|
2648
|
-
page_url=None,
|
|
2649
|
-
attachments=uploaded,
|
|
2650
|
-
reused_existing_page=reused_existing_page,
|
|
2651
|
-
project_root_page_id=hierarchy_lineage.get("project_root_page_id"),
|
|
2652
|
-
project_analysis_root_page_id=hierarchy_lineage.get("project_analysis_root_page_id"),
|
|
2653
|
-
project_aggregate_page_id=page_id,
|
|
2654
|
-
)
|
|
2655
|
-
|
|
2656
|
-
async def upload_results(
|
|
2657
|
-
self,
|
|
2658
|
-
*,
|
|
2659
|
-
report_dir: Path,
|
|
2660
|
-
parent_page: str,
|
|
2661
|
-
page_title: str,
|
|
2662
|
-
include_attachments: bool = False,
|
|
2663
|
-
thread_id: str | None = None,
|
|
2664
|
-
publish_mode: PublishMode = PublishMode.UPDATE,
|
|
2665
|
-
update_mode: UpdateMode = UpdateMode.FULL,
|
|
2666
|
-
include_attachments_index: bool = False,
|
|
2667
|
-
priority_actions_limit: int = 10,
|
|
2668
|
-
checklist_collapsed: bool = False,
|
|
2669
|
-
project_storage_key: str | None = None,
|
|
2670
|
-
repo_storage_key: str | None = None,
|
|
2671
|
-
hierarchy_target: str | None = None,
|
|
2672
|
-
project_name_hint: str | None = None,
|
|
2673
|
-
) -> UploadResult:
|
|
2674
|
-
"""Upload audit results to Confluence.
|
|
2675
|
-
|
|
2676
|
-
TSK-170B: Enhanced with publish_mode and attachments_index options.
|
|
2677
|
-
Phase 127: Default changed to skip attachments (page-only mode).
|
|
2678
|
-
|
|
2679
|
-
Args:
|
|
2680
|
-
report_dir: Directory containing audit reports.
|
|
2681
|
-
parent_page: Confluence parent page URL or ID.
|
|
2682
|
-
page_title: Title for the audit result page.
|
|
2683
|
-
include_attachments: Upload report attachments. Default is False (page-only mode).
|
|
2684
|
-
Use --upload-attachments flag to enable.
|
|
2685
|
-
thread_id: Workflow thread ID for provenance tracking.
|
|
2686
|
-
publish_mode: 'update' (default) or 'create-only' (fails if page exists).
|
|
2687
|
-
update_mode: 'full' (default) or 'incremental' (delta/meta scoped updates).
|
|
2688
|
-
include_attachments_index: Add attachments index section to page body.
|
|
2689
|
-
project_storage_key: Canonical project key for deterministic hierarchy placement.
|
|
2690
|
-
repo_storage_key: Canonical repo key for deterministic hierarchy placement.
|
|
2691
|
-
hierarchy_target: Optional hierarchy target ('repo' or 'project').
|
|
2692
|
-
project_name_hint: Human-readable project name used to build descriptive hierarchy
|
|
2693
|
-
node titles such as ``"Project Audit - Core Payment (88718943)"``.
|
|
2694
|
-
|
|
2695
|
-
Returns:
|
|
2696
|
-
UploadResult with page_id, attachments, and attachments_index.
|
|
2697
|
-
|
|
2698
|
-
Raises:
|
|
2699
|
-
ValueError: If publish_mode is 'create-only' and page already exists.
|
|
2700
|
-
"""
|
|
2701
|
-
if self.client.is_degraded:
|
|
2702
|
-
logger.warning(
|
|
2703
|
-
"confluence_upload_skipped_degraded",
|
|
2704
|
-
method="upload_results",
|
|
2705
|
-
note="Confluence client is degraded — skipping upload",
|
|
2706
|
-
)
|
|
2707
|
-
return UploadResult(page_id="", page_url=None, attachments=[], fallback_reason="degraded")
|
|
2708
|
-
parent_id = self.extract_page_id(parent_page)
|
|
2709
|
-
publish_parent_id, hierarchy_lineage = await self._resolve_publish_parent(
|
|
2710
|
-
parent_id=parent_id,
|
|
2711
|
-
page_title=page_title,
|
|
2712
|
-
project_storage_key=project_storage_key,
|
|
2713
|
-
repo_storage_key=repo_storage_key,
|
|
2714
|
-
hierarchy_target=hierarchy_target,
|
|
2715
|
-
project_name_hint=project_name_hint,
|
|
2716
|
-
)
|
|
2717
|
-
markdown_files = sorted(report_dir.rglob("*.md"), key=lambda p: p.stat().st_mtime, reverse=True)
|
|
2718
|
-
if not markdown_files:
|
|
2719
|
-
raise ValueError(f"No markdown report found in {report_dir}")
|
|
2720
|
-
markdown_path = markdown_files[0]
|
|
2721
|
-
|
|
2722
|
-
repo_page_mode = bool(hierarchy_lineage.get("repo_root_page_id"))
|
|
2723
|
-
deterministic_repo_title = str(hierarchy_lineage.get("repo_report_title") or "").strip() or page_title
|
|
2724
|
-
|
|
2725
|
-
# Phase 154: Registry-first resolution for repo_run pages
|
|
2726
|
-
project_key = self._normalize_hierarchy_key(project_storage_key, fallback=parent_id)
|
|
2727
|
-
repo_key = self._normalize_hierarchy_key(repo_storage_key, fallback=page_title)
|
|
2728
|
-
registry_hit = False
|
|
2729
|
-
existing_run: dict | None = None
|
|
2730
|
-
entry = self._lookup_page(project_key, repo_key, "repo_run")
|
|
2731
|
-
if entry:
|
|
2732
|
-
reg_page_id = str(entry["confluence_page_id"])
|
|
2733
|
-
if await self._page_exists(reg_page_id):
|
|
2734
|
-
existing_run = {"id": reg_page_id}
|
|
2735
|
-
registry_hit = True
|
|
2736
|
-
else:
|
|
2737
|
-
self._delete_registry_entry(project_key, repo_key, "repo_run")
|
|
2738
|
-
|
|
2739
|
-
if not registry_hit:
|
|
2740
|
-
existing_run = await self._find_child_page(
|
|
2741
|
-
publish_parent_id,
|
|
2742
|
-
deterministic_repo_title if repo_page_mode else page_title,
|
|
2743
|
-
)
|
|
2744
|
-
reused_existing_page = bool(existing_run and existing_run.get("id"))
|
|
2745
|
-
|
|
2746
|
-
effective_update_mode = update_mode
|
|
2747
|
-
fallback_reason: str | None = None
|
|
2748
|
-
counters: dict[str, int | str] = {
|
|
2749
|
-
"rows_changed": 0,
|
|
2750
|
-
"rows_unchanged": 0,
|
|
2751
|
-
"attachments_updated": 0,
|
|
2752
|
-
"attachments_skipped": 0,
|
|
2753
|
-
"publish_mode": publish_mode.value,
|
|
2754
|
-
"update_mode": update_mode.value,
|
|
2755
|
-
}
|
|
2756
|
-
incremental_plan: _IncrementalPlan | None = None
|
|
2757
|
-
if update_mode == UpdateMode.INCREMENTAL:
|
|
2758
|
-
incremental_plan, fallback_reason = self._load_incremental_plan(report_dir)
|
|
2759
|
-
if incremental_plan is None:
|
|
2760
|
-
effective_update_mode = UpdateMode.FULL
|
|
2761
|
-
logger.warning(
|
|
2762
|
-
"incremental_fallback_to_full",
|
|
2763
|
-
reason=fallback_reason,
|
|
2764
|
-
page_title=page_title,
|
|
2765
|
-
)
|
|
2766
|
-
else:
|
|
2767
|
-
counters["rows_changed"] = incremental_plan.rows_changed
|
|
2768
|
-
counters["rows_unchanged"] = incremental_plan.rows_unchanged
|
|
2769
|
-
|
|
2770
|
-
# TSK-170B: Enforce publish mode
|
|
2771
|
-
if existing_run and existing_run.get("id"):
|
|
2772
|
-
if publish_mode == PublishMode.CREATE_ONLY:
|
|
2773
|
-
raise ValueError(
|
|
2774
|
-
f"Page '{deterministic_repo_title if repo_page_mode else page_title}' already exists "
|
|
2775
|
-
f"(ID: {existing_run.get('id')}). "
|
|
2776
|
-
f"Use --publish-mode update to overwrite, or choose a different title."
|
|
2777
|
-
)
|
|
2778
|
-
if effective_update_mode == UpdateMode.INCREMENTAL:
|
|
2779
|
-
updated, patch_fallback_reason = await self.update_run_page_incremental(
|
|
2780
|
-
page_id=str(existing_run.get("id")),
|
|
2781
|
-
title=deterministic_repo_title if repo_page_mode else page_title,
|
|
2782
|
-
report_dir=report_dir,
|
|
2783
|
-
markdown_path=markdown_path,
|
|
2784
|
-
thread_id=thread_id,
|
|
2785
|
-
priority_actions_limit=priority_actions_limit,
|
|
2786
|
-
checklist_collapsed=checklist_collapsed,
|
|
2787
|
-
)
|
|
2788
|
-
if updated:
|
|
2789
|
-
run_page_id = str(existing_run.get("id"))
|
|
2790
|
-
else:
|
|
2791
|
-
effective_update_mode = UpdateMode.FULL
|
|
2792
|
-
fallback_reason = patch_fallback_reason or "incremental_section_patch_failed"
|
|
2793
|
-
logger.warning(
|
|
2794
|
-
"incremental_fallback_to_full",
|
|
2795
|
-
reason=fallback_reason,
|
|
2796
|
-
page_id=str(existing_run.get("id")),
|
|
2797
|
-
page_title=deterministic_repo_title if repo_page_mode else page_title,
|
|
2798
|
-
)
|
|
2799
|
-
run_page_id = await self.update_run_page(
|
|
2800
|
-
page_id=str(existing_run.get("id")),
|
|
2801
|
-
title=deterministic_repo_title if repo_page_mode else page_title,
|
|
2802
|
-
report_dir=report_dir,
|
|
2803
|
-
markdown_path=markdown_path,
|
|
2804
|
-
thread_id=thread_id,
|
|
2805
|
-
priority_actions_limit=priority_actions_limit,
|
|
2806
|
-
checklist_collapsed=checklist_collapsed,
|
|
2807
|
-
)
|
|
2808
|
-
else:
|
|
2809
|
-
run_page_id = await self.update_run_page(
|
|
2810
|
-
page_id=str(existing_run.get("id")),
|
|
2811
|
-
title=deterministic_repo_title if repo_page_mode else page_title,
|
|
2812
|
-
report_dir=report_dir,
|
|
2813
|
-
markdown_path=markdown_path,
|
|
2814
|
-
thread_id=thread_id,
|
|
2815
|
-
priority_actions_limit=priority_actions_limit,
|
|
2816
|
-
checklist_collapsed=checklist_collapsed,
|
|
2817
|
-
)
|
|
2818
|
-
else:
|
|
2819
|
-
if effective_update_mode == UpdateMode.INCREMENTAL:
|
|
2820
|
-
effective_update_mode = UpdateMode.FULL
|
|
2821
|
-
fallback_reason = "incremental_requires_existing_page"
|
|
2822
|
-
logger.warning(
|
|
2823
|
-
"incremental_fallback_to_full",
|
|
2824
|
-
reason=fallback_reason,
|
|
2825
|
-
page_title=deterministic_repo_title if repo_page_mode else page_title,
|
|
2826
|
-
)
|
|
2827
|
-
run_page_id = await self.create_run_page(
|
|
2828
|
-
parent_id=publish_parent_id,
|
|
2829
|
-
title=deterministic_repo_title if repo_page_mode else page_title,
|
|
2830
|
-
report_dir=report_dir,
|
|
2831
|
-
markdown_path=markdown_path,
|
|
2832
|
-
thread_id=thread_id,
|
|
2833
|
-
priority_actions_limit=priority_actions_limit,
|
|
2834
|
-
checklist_collapsed=checklist_collapsed,
|
|
2835
|
-
)
|
|
2836
|
-
# Register immediately after creation so concurrent profiles find it
|
|
2837
|
-
# via registry lookup instead of hitting CQL eventual-consistency lag
|
|
2838
|
-
# and attempting a duplicate create_page call. The final registration
|
|
2839
|
-
# at the end of upload_results is an idempotent upsert, so this early
|
|
2840
|
-
# registration is safe.
|
|
2841
|
-
self._register_page(
|
|
2842
|
-
project_key,
|
|
2843
|
-
repo_key,
|
|
2844
|
-
"repo_run",
|
|
2845
|
-
run_page_id,
|
|
2846
|
-
deterministic_repo_title if repo_page_mode else page_title,
|
|
2847
|
-
publish_parent_id,
|
|
2848
|
-
)
|
|
2849
|
-
|
|
2850
|
-
# Phase 156: Move page to correct parent when registry hit found
|
|
2851
|
-
# under a different Confluence parent than the resolved publish target.
|
|
2852
|
-
# Only needed for reused pages — freshly created pages are already
|
|
2853
|
-
# placed under publish_parent_id by create_run_page.
|
|
2854
|
-
if reused_existing_page:
|
|
2855
|
-
await self._move_page_if_parent_mismatched(run_page_id, publish_parent_id)
|
|
2856
|
-
|
|
2857
|
-
uploaded: list[str] = []
|
|
2858
|
-
attachments_index: list[dict[str, str]] = []
|
|
2859
|
-
|
|
2860
|
-
if include_attachments:
|
|
2861
|
-
try:
|
|
2862
|
-
attachments = await self._retry(
|
|
2863
|
-
lambda: self.client.list_attachments(run_page_id),
|
|
2864
|
-
page_id=run_page_id,
|
|
2865
|
-
action="list_attachments",
|
|
2866
|
-
)
|
|
2867
|
-
except DataSourceError as e:
|
|
2868
|
-
if self._is_permission_error(e):
|
|
2869
|
-
raise DataSourceError(
|
|
2870
|
-
self._build_permission_error_message("list_attachments", run_page_id),
|
|
2871
|
-
context={"page_id": run_page_id, "operation": "list_attachments"},
|
|
2872
|
-
) from e
|
|
2873
|
-
raise
|
|
2874
|
-
|
|
2875
|
-
attachment_map: dict[str, dict] = {}
|
|
2876
|
-
for attachment in attachments:
|
|
2877
|
-
if not isinstance(attachment, dict):
|
|
2878
|
-
continue
|
|
2879
|
-
name = (
|
|
2880
|
-
attachment.get("title")
|
|
2881
|
-
or attachment.get("name")
|
|
2882
|
-
or attachment.get("filename")
|
|
2883
|
-
or attachment.get("fileName")
|
|
2884
|
-
)
|
|
2885
|
-
if isinstance(name, str):
|
|
2886
|
-
attachment_map[name.lower()] = attachment
|
|
2887
|
-
for path in report_dir.rglob("*"):
|
|
2888
|
-
if not path.is_file():
|
|
2889
|
-
continue
|
|
2890
|
-
# Default attachment set + explicit audit-checklist.* artifacts.
|
|
2891
|
-
suffix = path.suffix.lower()
|
|
2892
|
-
name_lc = path.name.lower()
|
|
2893
|
-
is_default = suffix in {".xlsx", ".json", ".sarif"} or name_lc.endswith(".sarif.json")
|
|
2894
|
-
is_checklist = name_lc.startswith("audit-checklist.")
|
|
2895
|
-
if is_default or is_checklist:
|
|
2896
|
-
existing = attachment_map.get(path.name.lower())
|
|
2897
|
-
local_hash = self._file_sha256(path)
|
|
2898
|
-
if effective_update_mode == UpdateMode.INCREMENTAL:
|
|
2899
|
-
name_lc = path.name.lower()
|
|
2900
|
-
changed_set = incremental_plan.changed_attachments if incremental_plan else set()
|
|
2901
|
-
unchanged_set = incremental_plan.unchanged_attachments if incremental_plan else set()
|
|
2902
|
-
if changed_set and name_lc not in changed_set:
|
|
2903
|
-
counters["attachments_skipped"] = int(counters["attachments_skipped"]) + 1
|
|
2904
|
-
continue
|
|
2905
|
-
if name_lc in unchanged_set:
|
|
2906
|
-
counters["attachments_skipped"] = int(counters["attachments_skipped"]) + 1
|
|
2907
|
-
continue
|
|
2908
|
-
existing_hash = self._attachment_payload_hash(existing) if isinstance(existing, dict) else None
|
|
2909
|
-
if existing_hash and existing_hash == local_hash:
|
|
2910
|
-
counters["attachments_skipped"] = int(counters["attachments_skipped"]) + 1
|
|
2911
|
-
continue
|
|
2912
|
-
try:
|
|
2913
|
-
if existing and existing.get("id"):
|
|
2914
|
-
payload = await self._retry(
|
|
2915
|
-
lambda existing=existing, path=path: self.client.update_attachment(
|
|
2916
|
-
page_id=run_page_id,
|
|
2917
|
-
attachment_id=str(existing.get("id")),
|
|
2918
|
-
file_path=path,
|
|
2919
|
-
),
|
|
2920
|
-
page_id=run_page_id,
|
|
2921
|
-
attachment_id=str(existing.get("id")),
|
|
2922
|
-
file_path=str(path),
|
|
2923
|
-
action="update_attachment",
|
|
2924
|
-
)
|
|
2925
|
-
else:
|
|
2926
|
-
payload = await self._retry(
|
|
2927
|
-
lambda path=path: self.client.upload_attachment(page_id=run_page_id, file_path=path),
|
|
2928
|
-
page_id=run_page_id,
|
|
2929
|
-
file_path=str(path),
|
|
2930
|
-
action="upload_attachment",
|
|
2931
|
-
)
|
|
2932
|
-
except DataSourceError as e:
|
|
2933
|
-
if self._is_permission_error(e):
|
|
2934
|
-
raise DataSourceError(
|
|
2935
|
-
self._build_permission_error_message("upload_attachment", run_page_id),
|
|
2936
|
-
context={
|
|
2937
|
-
"page_id": run_page_id,
|
|
2938
|
-
"file_path": str(path),
|
|
2939
|
-
"operation": "attachment_upload",
|
|
2940
|
-
},
|
|
2941
|
-
) from e
|
|
2942
|
-
raise
|
|
2943
|
-
attachment_id = None
|
|
2944
|
-
if isinstance(payload, dict):
|
|
2945
|
-
attachment_id = payload.get("id") or payload.get("attachment_id")
|
|
2946
|
-
if not attachment_id:
|
|
2947
|
-
results = payload.get("results")
|
|
2948
|
-
if isinstance(results, list) and results and isinstance(results[0], dict):
|
|
2949
|
-
attachment_id = results[0].get("id") or results[0].get("attachment_id")
|
|
2950
|
-
if attachment_id:
|
|
2951
|
-
uploaded.append(str(attachment_id))
|
|
2952
|
-
counters["attachments_updated"] = int(counters["attachments_updated"]) + 1
|
|
2953
|
-
# TSK-170B: Track for attachments index
|
|
2954
|
-
attachments_index.append({"name": path.name, "id": str(attachment_id)})
|
|
2955
|
-
|
|
2956
|
-
# TSK-170B: Update page with attachments index if requested
|
|
2957
|
-
if include_attachments_index and attachments_index:
|
|
2958
|
-
try:
|
|
2959
|
-
# Get current page content and append attachments index
|
|
2960
|
-
current_content = await self._retry(
|
|
2961
|
-
lambda: self.client.get_page(run_page_id, expand="body.storage"),
|
|
2962
|
-
page_id=run_page_id,
|
|
2963
|
-
action="get_page",
|
|
2964
|
-
)
|
|
2965
|
-
if current_content and isinstance(current_content, dict):
|
|
2966
|
-
body = current_content.get("body", {})
|
|
2967
|
-
storage = body.get("storage", {}) if isinstance(body, dict) else {}
|
|
2968
|
-
content = storage.get("value", "") if isinstance(storage, dict) else ""
|
|
2969
|
-
|
|
2970
|
-
if not content.strip():
|
|
2971
|
-
logger.warning(
|
|
2972
|
-
"attachments_index_skipped_empty_body",
|
|
2973
|
-
page_id=run_page_id,
|
|
2974
|
-
reason="get_page returned empty body.storage; skipping to avoid overwriting rich content",
|
|
2975
|
-
)
|
|
2976
|
-
else:
|
|
2977
|
-
# Append attachments index section
|
|
2978
|
-
index_html = self._render_attachments_index_html(attachments_index)
|
|
2979
|
-
if index_html and index_html not in content:
|
|
2980
|
-
updated_content = content + "\n\n" + index_html
|
|
2981
|
-
|
|
2982
|
-
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False) as f:
|
|
2983
|
-
f.write(updated_content)
|
|
2984
|
-
temp_path = Path(f.name)
|
|
2985
|
-
|
|
2986
|
-
try:
|
|
2987
|
-
resolved_title = self._resolve_update_title(
|
|
2988
|
-
current_content if isinstance(current_content, dict) else {}, page_title
|
|
2989
|
-
)
|
|
2990
|
-
await self._retry(
|
|
2991
|
-
lambda: self.client.update_page(
|
|
2992
|
-
page_id=run_page_id,
|
|
2993
|
-
title=resolved_title,
|
|
2994
|
-
body_file=temp_path,
|
|
2995
|
-
),
|
|
2996
|
-
page_id=run_page_id,
|
|
2997
|
-
title=resolved_title,
|
|
2998
|
-
action="update_page_with_index",
|
|
2999
|
-
)
|
|
3000
|
-
finally:
|
|
3001
|
-
if temp_path.exists():
|
|
3002
|
-
temp_path.unlink()
|
|
3003
|
-
except Exception as e:
|
|
3004
|
-
# Non-critical - log but don't fail
|
|
3005
|
-
logger.warning("attachments_index_update_failed", error=str(e), page_id=run_page_id)
|
|
3006
|
-
|
|
3007
|
-
counters["effective_update_mode"] = effective_update_mode.value
|
|
3008
|
-
logger.info(
|
|
3009
|
-
"upload_results_completed",
|
|
3010
|
-
page_id=run_page_id,
|
|
3011
|
-
publish_mode=publish_mode.value,
|
|
3012
|
-
update_mode=update_mode.value,
|
|
3013
|
-
effective_update_mode=effective_update_mode.value,
|
|
3014
|
-
rows_changed=counters["rows_changed"],
|
|
3015
|
-
rows_unchanged=counters["rows_unchanged"],
|
|
3016
|
-
attachments_updated=counters["attachments_updated"],
|
|
3017
|
-
attachments_skipped=counters["attachments_skipped"],
|
|
3018
|
-
fallback_reason=fallback_reason,
|
|
3019
|
-
)
|
|
3020
|
-
|
|
3021
|
-
# Phase 154: Register the repo run page in the registry
|
|
3022
|
-
self._register_page(
|
|
3023
|
-
project_key,
|
|
3024
|
-
repo_key,
|
|
3025
|
-
"repo_run",
|
|
3026
|
-
run_page_id,
|
|
3027
|
-
deterministic_repo_title if repo_page_mode else page_title,
|
|
3028
|
-
publish_parent_id,
|
|
3029
|
-
)
|
|
3030
|
-
|
|
3031
|
-
return UploadResult(
|
|
3032
|
-
page_id=run_page_id,
|
|
3033
|
-
page_url=None,
|
|
3034
|
-
reused_existing_page=reused_existing_page,
|
|
3035
|
-
attachments=uploaded,
|
|
3036
|
-
attachments_index=attachments_index,
|
|
3037
|
-
update_mode=update_mode.value,
|
|
3038
|
-
effective_update_mode=effective_update_mode.value,
|
|
3039
|
-
counters=counters,
|
|
3040
|
-
fallback_reason=fallback_reason,
|
|
3041
|
-
project_root_page_id=hierarchy_lineage.get("project_root_page_id"),
|
|
3042
|
-
audit_root_page_id=hierarchy_lineage.get("audit_root_page_id"),
|
|
3043
|
-
repo_root_page_id=hierarchy_lineage.get("repo_root_page_id"),
|
|
3044
|
-
repo_run_page_id=run_page_id if hierarchy_lineage.get("repo_root_page_id") else None,
|
|
3045
|
-
project_analysis_root_page_id=hierarchy_lineage.get("project_analysis_root_page_id"),
|
|
3046
|
-
project_aggregate_page_id=run_page_id if hierarchy_lineage.get("project_analysis_root_page_id") else None,
|
|
3047
|
-
sections_rendered=list(self._last_sections_rendered),
|
|
3048
|
-
)
|