@ngocsangairvds/vsaf 4.1.9 → 4.1.11
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/package.json +1 -1
- package/packages/cli/dist/commands/install.d.ts +1 -1
- package/packages/cli/dist/commands/install.d.ts.map +1 -1
- package/packages/cli/dist/commands/install.js +25 -13
- package/packages/cli/dist/commands/install.js.map +1 -1
- package/packages/cli/dist/commands/skill.d.ts.map +1 -1
- package/packages/cli/dist/commands/skill.js +5 -1
- package/packages/cli/dist/commands/skill.js.map +1 -1
- package/skills/vds-skill/install-deps.mjs +252 -0
- package/skills/vds-skill/pack.yaml +8 -0
- package/skills/vds-skill/runtime/.claude/phase7-CLOSURE.md +100 -0
- package/skills/vds-skill/runtime/.dockerignore +62 -0
- package/skills/vds-skill/runtime/.github/ISSUE_TEMPLATE/cli-change.md +92 -0
- package/skills/vds-skill/runtime/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +48 -0
- package/skills/vds-skill/runtime/.github/workflows/chaos-smoke.yml +266 -0
- package/skills/vds-skill/runtime/.github/workflows/confluence-sync.yml +44 -0
- package/skills/vds-skill/runtime/.github/workflows/docs-confluence-evidence.yml +170 -0
- package/skills/vds-skill/runtime/.github/workflows/docs-quality.yml +59 -0
- package/skills/vds-skill/runtime/.github/workflows/lint-and-test.yml +90 -0
- package/skills/vds-skill/runtime/.github/workflows/scheduler-load-smoke.yml +104 -0
- package/skills/vds-skill/runtime/.github/workflows/telegram-bridge-ci.yml +131 -0
- package/skills/vds-skill/runtime/.graphifyignore +29 -0
- package/skills/vds-skill/runtime/.importlinter +86 -0
- package/skills/vds-skill/runtime/.mcp.json +11 -0
- package/skills/vds-skill/runtime/.pre-commit-config.yaml +62 -0
- package/skills/vds-skill/runtime/.ruffignore +3 -0
- package/skills/vds-skill/runtime/AGENTS.md +250 -0
- package/skills/vds-skill/runtime/AGENTS.vi.md +92 -0
- package/skills/vds-skill/runtime/ECOSYSTEM-CHANGELOG.md +52 -0
- package/skills/vds-skill/runtime/ECOSYSTEM-DOCS.md +602 -0
- package/skills/vds-skill/runtime/ECOSYSTEM_ALIGNMENT.md +133 -0
- package/skills/vds-skill/runtime/Makefile +119 -0
- package/skills/vds-skill/runtime/README.md +103 -0
- package/skills/vds-skill/runtime/bitbucket_manifest_mapping.toml +34 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/ARCHITECTURE_ANALYSIS.md +258 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/BITBUCKET_API_PRACTICES.md +393 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/EVALUATION_REPORT.md +61 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/FEATURES.md +908 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/README.md +817 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/pyproject.toml +49 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/__init__.py +50 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/async_client.py +641 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/cli.py +2271 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/client.py +2693 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/config.py +185 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/errors.py +34 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/factory.py +185 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/parsers.py +113 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/protocols.py +244 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/repo_ops.py +325 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/__init__.py +8 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/conftest.py +65 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_advanced_search.py +155 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_async_client.py +505 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_branch_permissions.py +172 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_cli.py +113 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_cli_archive.py +122 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_cli_clone.py +131 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_client.py +207 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_client_archive.py +73 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_client_branch_conditions.py +101 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_client_code_advanced.py +180 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_client_code_file.py +33 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_client_deployment_environments.py +193 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_client_issues.py +163 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_client_pipelines_advanced.py +171 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_client_pr_blockers.py +118 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_client_repository_variables.py +155 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_code.py +98 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_code_advanced.py +279 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_code_insights.py +334 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_conditions.py +149 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_config.py +297 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_deployment_env.py +352 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_errors.py +67 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_factory.py +352 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_fork_operations.py +203 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_issue_cli.py +262 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_pipeline_advanced.py +265 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_pr_blocker.py +206 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_protocols.py +336 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_repo_ops_archive.py +169 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_repo_ops_clone.py +115 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_repo_ops_parsing.py +149 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_repo_settings.py +336 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_repo_variables.py +266 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_webhooks.py +188 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/test_workspace.py +234 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/unit/__init__.py +0 -0
- package/skills/vds-skill/runtime/bitbucket_orchestrator/tests/unit/test_parsers.py +254 -0
- package/skills/vds-skill/runtime/brd_orchestrator/README.md +29 -0
- package/skills/vds-skill/runtime/brd_orchestrator/pyproject.toml +63 -0
- package/skills/vds-skill/runtime/brd_orchestrator/src/vds_brd_orchestrator/__init__.py +17 -0
- package/skills/vds-skill/runtime/brd_orchestrator/src/vds_brd_orchestrator/cli.py +187 -0
- package/skills/vds-skill/runtime/brd_orchestrator/src/vds_brd_orchestrator/validator.py +121 -0
- package/skills/vds-skill/runtime/brd_orchestrator/tests/__init__.py +0 -0
- package/skills/vds-skill/runtime/brd_orchestrator/tests/test_cli.py +62 -0
- package/skills/vds-skill/runtime/brd_orchestrator/tests/test_validator.py +33 -0
- package/skills/vds-skill/runtime/code/code_evidence_pack.json +435 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/Dockerfile +19 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/README.md +479 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/SYNC_SCRIPTS.md +127 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/SYNC_STANDARDIZATION.md +108 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/pyproject.toml +50 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/__init__.py +56 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/async_client.py +100 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/cli.py +3160 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/config.py +213 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/content.py +368 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/content_v2.py +144 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/crawl_tree.py +1833 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/errors.py +44 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/eventing.py +111 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/http.py +1850 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/orchestration.py +166 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/protocols.py +61 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/reporting.py +78 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/tree.py +122 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/src/confluence_orchestrator/tree_copier.py +431 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/sync_pdfs_from_markdown.py +203 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/sync_pdfs_to_confluence.py +299 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/sync_png_attachments.py +299 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/conftest.py +46 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_advanced_content.py +252 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_advanced_search.py +193 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_async_client.py +104 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_cache_management.py +246 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_cli.py +716 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_config.py +130 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_content.py +192 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_content_flags.py +27 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_content_labels.py +94 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_crawl_tree.py +2252 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_degraded_write_safety.py +176 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_draft_management.py +225 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_errors.py +75 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_eventing.py +73 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_eventing_chaos.py +37 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_eventing_rate_limit.py +44 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_eventing_timeout.py +49 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_export.py +231 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_history.py +217 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_http.py +375 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_orchestration.py +93 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_reporting.py +24 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_search_cql.py +36 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_space_management.py +236 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_space_permissions.py +384 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_tree_copier.py +644 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_tree_copier_remap.py +289 -0
- package/skills/vds-skill/runtime/confluence_orchestrator/tests/test_user_group_management.py +387 -0
- package/skills/vds-skill/runtime/diagram_generator/README.md +663 -0
- package/skills/vds-skill/runtime/diagram_generator/ci_validate.sh +16 -0
- package/skills/vds-skill/runtime/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-component.png +0 -0
- package/skills/vds-skill/runtime/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-component.puml +23 -0
- package/skills/vds-skill/runtime/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-sequence.png +0 -0
- package/skills/vds-skill/runtime/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-sequence.puml +21 -0
- package/skills/vds-skill/runtime/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-usecase.png +0 -0
- package/skills/vds-skill/runtime/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-usecase.puml +14 -0
- package/skills/vds-skill/runtime/diagram_generator/examples/github-actions-validate.yml +39 -0
- package/skills/vds-skill/runtime/diagram_generator/generate_all_diagrams.py +827 -0
- package/skills/vds-skill/runtime/diagram_generator/generate_insurance_c4_diagrams.py +261 -0
- package/skills/vds-skill/runtime/diagram_generator/generate_insurance_c4_quick.py +486 -0
- package/skills/vds-skill/runtime/diagram_generator/pyproject.toml +28 -0
- package/skills/vds-skill/runtime/diagram_generator/render_png.py +59 -0
- package/skills/vds-skill/runtime/diagram_generator/src/vds_diagram_generator/__init__.py +3 -0
- package/skills/vds-skill/runtime/diagram_generator/src/vds_diagram_generator/cli.py +50 -0
- package/skills/vds-skill/runtime/diagram_generator/test_c4_hierarchical.py +142 -0
- package/skills/vds-skill/runtime/diagram_generator/test_c4_quick.py +131 -0
- package/skills/vds-skill/runtime/diagram_generator/tests/__init__.py +0 -0
- package/skills/vds-skill/runtime/diagram_generator/tests/test_analyzer_completeness.py +260 -0
- package/skills/vds-skill/runtime/diagram_generator/tests/test_c4_syntax_correctness.py +138 -0
- package/skills/vds-skill/runtime/diagram_generator/tests/test_component_coverage.py +182 -0
- package/skills/vds-skill/runtime/diagram_generator/tests/test_mermaid_output.py +80 -0
- package/skills/vds-skill/runtime/diagram_generator/tests/test_png_generation.py +112 -0
- package/skills/vds-skill/runtime/diagram_generator/tests/test_scenario_templates.py +15 -0
- package/skills/vds-skill/runtime/diagram_generator/tests/test_sequence_accuracy.py +93 -0
- package/skills/vds-skill/runtime/diagram_generator/tests/test_structurizr_export.py +177 -0
- package/skills/vds-skill/runtime/diagram_generator/tests/test_style_consistency.py +174 -0
- package/skills/vds-skill/runtime/diagram_generator/tests/test_usecase_generator.py +201 -0
- package/skills/vds-skill/runtime/diagram_generator/tests/test_usecase_integration.py +124 -0
- package/skills/vds-skill/runtime/docker/.dockerignore +38 -0
- package/skills/vds-skill/runtime/docker/ADR.md +392 -0
- package/skills/vds-skill/runtime/docker/Dockerfile +68 -0
- package/skills/vds-skill/runtime/docker/MIGRATION.md +453 -0
- package/skills/vds-skill/runtime/docker/README.md +347 -0
- package/skills/vds-skill/runtime/docker/ROLLBACK.md +596 -0
- package/skills/vds-skill/runtime/docker/compose.phase2-verification.yml +31 -0
- package/skills/vds-skill/runtime/docker/docker-compose.cli.yml +206 -0
- package/skills/vds-skill/runtime/docker/docker-compose.infra.yml +276 -0
- package/skills/vds-skill/runtime/docker/docker-compose.services.yml +425 -0
- package/skills/vds-skill/runtime/docker/infrastructure/init-schemas.sql +177 -0
- package/skills/vds-skill/runtime/docker/infrastructure/pgbouncer/pgbouncer.ini +75 -0
- package/skills/vds-skill/runtime/docker/infrastructure/pgbouncer/userlist.txt +50 -0
- package/skills/vds-skill/runtime/docker/infrastructure/pgbouncer/userlist.txt.template +36 -0
- package/skills/vds-skill/runtime/docs/.confluence-evidence/.gitkeep +0 -0
- package/skills/vds-skill/runtime/docs/.confluence-evidence/PREFLIGHT.md +132 -0
- package/skills/vds-skill/runtime/docs/.confluence-evidence/README.md +84 -0
- package/skills/vds-skill/runtime/docs/.confluence.yaml +156 -0
- package/skills/vds-skill/runtime/docs/.freshness.yaml +54 -0
- package/skills/vds-skill/runtime/docs/README.md +235 -0
- package/skills/vds-skill/runtime/docs/agents/README.md +33 -0
- package/skills/vds-skill/runtime/docs/agents/explanation/data-flow.md +132 -0
- package/skills/vds-skill/runtime/docs/agents/explanation/development-roadmap.md +49 -0
- package/skills/vds-skill/runtime/docs/agents/explanation/features-overview.md +62 -0
- package/skills/vds-skill/runtime/docs/agents/explanation/index.md +36 -0
- package/skills/vds-skill/runtime/docs/agents/explanation/runtime-verification-and-gap-reporting.md +127 -0
- package/skills/vds-skill/runtime/docs/agents/explanation/system-architecture.md +139 -0
- package/skills/vds-skill/runtime/docs/agents/explanation/whats-new.md +75 -0
- package/skills/vds-skill/runtime/docs/agents/explanation/who-ecosystem-introduction.md +65 -0
- package/skills/vds-skill/runtime/docs/agents/explanation/who-ecosystem-model.md +41 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/02-using-vds-ai-memory.md +98 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/03-memory-cross-agent.md +241 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/04-using-progress-reports.md +240 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/08-semantic-search.md +46 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/apply-phase3-migration.md +148 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/choose-the-right-command-or-skill.md +34 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/contribute-new-orchestrator.md +149 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/decision-tree.md +63 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/first-audit-run.md +83 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/index.md +49 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/install-and-bootstrap-who-scripts-and-skills.md +314 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/orchestrator-workflows/analytics-pipeline-workflow.md +165 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/orchestrator-workflows/code-quality-gate-workflow.md +138 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/orchestrator-workflows/confluence-bitbucket-sync-workflow.md +130 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/orchestrator-workflows/document-delivery-workflow.md +142 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/orchestrator-workflows/memory-progress-workflow.md +140 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/orchestrator-workflows/research-spec-audit-workflow.md +135 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/phase131-all-project-preparation.md +211 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/phase131-bounded-parallel-analysis.md +123 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/phase131-confluence-upload-recovery.md +204 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/phase132-department-preparation.md +144 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/run-ecosystem-daily-report.md +213 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/tips-and-tricks.md +138 -0
- package/skills/vds-skill/runtime/docs/agents/how-to/troubleshooting-guide.md +221 -0
- package/skills/vds-skill/runtime/docs/agents/reference/agent-operational-contract.md +162 -0
- package/skills/vds-skill/runtime/docs/agents/reference/alignment-phase179-report.md +144 -0
- package/skills/vds-skill/runtime/docs/agents/reference/audit-triage-playbook.md +256 -0
- package/skills/vds-skill/runtime/docs/agents/reference/backup-restore.md +132 -0
- package/skills/vds-skill/runtime/docs/agents/reference/bitbucket-orchestrator.md +56 -0
- package/skills/vds-skill/runtime/docs/agents/reference/brd-orchestrator.md +52 -0
- package/skills/vds-skill/runtime/docs/agents/reference/capability-coverage-review.md +51 -0
- package/skills/vds-skill/runtime/docs/agents/reference/ci-workflows.md +98 -0
- package/skills/vds-skill/runtime/docs/agents/reference/circular-dependency-orchestrator.md +55 -0
- package/skills/vds-skill/runtime/docs/agents/reference/cli-commands.md +583 -0
- package/skills/vds-skill/runtime/docs/agents/reference/cli-development-standards.md +41 -0
- package/skills/vds-skill/runtime/docs/agents/reference/cli-help-matrix.md +84 -0
- package/skills/vds-skill/runtime/docs/agents/reference/common-errors.md +126 -0
- package/skills/vds-skill/runtime/docs/agents/reference/configuration-reference.md +128 -0
- package/skills/vds-skill/runtime/docs/agents/reference/confluence-orchestrator.md +56 -0
- package/skills/vds-skill/runtime/docs/agents/reference/confluence-sync-target.md +111 -0
- package/skills/vds-skill/runtime/docs/agents/reference/confluence-sync.md +46 -0
- package/skills/vds-skill/runtime/docs/agents/reference/db-query-orchestrator.md +93 -0
- package/skills/vds-skill/runtime/docs/agents/reference/diagrams-orchestrator.md +52 -0
- package/skills/vds-skill/runtime/docs/agents/reference/ecosystem-daily-report.md +229 -0
- package/skills/vds-skill/runtime/docs/agents/reference/elastic-orchestrator.md +57 -0
- package/skills/vds-skill/runtime/docs/agents/reference/env-git-helper.md +216 -0
- package/skills/vds-skill/runtime/docs/agents/reference/evolution-orchestrator.md +113 -0
- package/skills/vds-skill/runtime/docs/agents/reference/excel-orchestrator.md +51 -0
- package/skills/vds-skill/runtime/docs/agents/reference/git-orchestrator.md +62 -0
- package/skills/vds-skill/runtime/docs/agents/reference/google-sheets-orchestrator.md +51 -0
- package/skills/vds-skill/runtime/docs/agents/reference/grafana-orchestrator.md +52 -0
- package/skills/vds-skill/runtime/docs/agents/reference/hexagonal-orchestrator.md +64 -0
- package/skills/vds-skill/runtime/docs/agents/reference/index.md +36 -0
- package/skills/vds-skill/runtime/docs/agents/reference/infrastructure-v2.15.md +67 -0
- package/skills/vds-skill/runtime/docs/agents/reference/intellij-orchestrator.md +50 -0
- package/skills/vds-skill/runtime/docs/agents/reference/jira-orchestrator.md +60 -0
- package/skills/vds-skill/runtime/docs/agents/reference/links-orchestrator.md +57 -0
- package/skills/vds-skill/runtime/docs/agents/reference/lint-cli.md +99 -0
- package/skills/vds-skill/runtime/docs/agents/reference/lsp-orchestrator.md +51 -0
- package/skills/vds-skill/runtime/docs/agents/reference/markdown-orchestrator.md +53 -0
- package/skills/vds-skill/runtime/docs/agents/reference/mcp-orchestrator.md +88 -0
- package/skills/vds-skill/runtime/docs/agents/reference/memory-orchestrator.md +53 -0
- package/skills/vds-skill/runtime/docs/agents/reference/metabase-orchestrator.md +51 -0
- package/skills/vds-skill/runtime/docs/agents/reference/migration-playbook.md +71 -0
- package/skills/vds-skill/runtime/docs/agents/reference/multi-agent-orchestrator.md +52 -0
- package/skills/vds-skill/runtime/docs/agents/reference/openapi-orchestrator.md +57 -0
- package/skills/vds-skill/runtime/docs/agents/reference/orchestrator-architecture.md +194 -0
- package/skills/vds-skill/runtime/docs/agents/reference/orchestrator-comparison-matrix.md +79 -0
- package/skills/vds-skill/runtime/docs/agents/reference/orchestrator-index.md +73 -0
- package/skills/vds-skill/runtime/docs/agents/reference/pdf-orchestrator.md +57 -0
- package/skills/vds-skill/runtime/docs/agents/reference/portable-paths-and-config.md +0 -0
- package/skills/vds-skill/runtime/docs/agents/reference/portable-paths-validation-matrix.md +129 -0
- package/skills/vds-skill/runtime/docs/agents/reference/progress-orchestrator.md +51 -0
- package/skills/vds-skill/runtime/docs/agents/reference/progress-report-cli.md +215 -0
- package/skills/vds-skill/runtime/docs/agents/reference/public-interface-orchestrator.md +73 -0
- package/skills/vds-skill/runtime/docs/agents/reference/research-orchestrator.md +53 -0
- package/skills/vds-skill/runtime/docs/agents/reference/schema-orchestrator.md +57 -0
- package/skills/vds-skill/runtime/docs/agents/reference/search-tools.md +34 -0
- package/skills/vds-skill/runtime/docs/agents/reference/skills-commands.md +256 -0
- package/skills/vds-skill/runtime/docs/agents/reference/skills-reference.md +32 -0
- package/skills/vds-skill/runtime/docs/agents/reference/sonarqube-orchestrator.md +62 -0
- package/skills/vds-skill/runtime/docs/agents/reference/spec-orchestrator.md +56 -0
- package/skills/vds-skill/runtime/docs/agents/reference/structure-orchestrator.md +69 -0
- package/skills/vds-skill/runtime/docs/agents/reference/system-requirements.md +76 -0
- package/skills/vds-skill/runtime/docs/agents/reference/tasks-orchestrator.md +53 -0
- package/skills/vds-skill/runtime/docs/agents/reference/validation-and-sync-notes.md +54 -0
- package/skills/vds-skill/runtime/docs/agents/reference/vds-ai-memory-api.md +51 -0
- package/skills/vds-skill/runtime/docs/agents/reference/vds-cli-reference.md +34 -0
- package/skills/vds-skill/runtime/docs/agents/reference/who-capability-inventory.md +96 -0
- package/skills/vds-skill/runtime/docs/agents/reference/who-capability-routing-matrix.md +14 -0
- package/skills/vds-skill/runtime/docs/agents/tutorials/feature-progression-guide.md +112 -0
- package/skills/vds-skill/runtime/docs/agents/tutorials/index.md +36 -0
- package/skills/vds-skill/runtime/docs/agents/tutorials/quick-start.md +50 -0
- package/skills/vds-skill/runtime/docs/agents/tutorials/who-skills-and-scripts-onboarding.md +47 -0
- package/skills/vds-skill/runtime/docs/agents/tutorials/zero-to-productive-developer.md +339 -0
- package/skills/vds-skill/runtime/docs/confluence/IMPLEMENTATION-SUMMARY.md +78 -0
- package/skills/vds-skill/runtime/docs/confluence/SYNC-GUIDE.md +47 -0
- package/skills/vds-skill/runtime/docs/deployment/offline-docker-image-load.md +59 -0
- package/skills/vds-skill/runtime/docs/evolution-auto-run-rollout.md +325 -0
- package/skills/vds-skill/runtime/docs/evolution-loop-deep-integration.md +496 -0
- package/skills/vds-skill/runtime/docs/evolution-loop-integration-guide.md +359 -0
- package/skills/vds-skill/runtime/docs/openspace-schema-snapshot.md +73 -0
- package/skills/vds-skill/runtime/docs/operations/sla-mttr-policy.md +44 -0
- package/skills/vds-skill/runtime/docs/p0-closure-evidence/SUMMARY.md +58 -0
- package/skills/vds-skill/runtime/docs/p4-closure-evidence/.gitkeep +0 -0
- package/skills/vds-skill/runtime/docs/p4-closure-evidence/smoke-20260427T024137Z-b95b586b.json +15 -0
- package/skills/vds-skill/runtime/docs/p8-preflight-evidence/alembic-and-runtime-advisory-locks.md +45 -0
- package/skills/vds-skill/runtime/docs/p8-preflight-evidence/dbos-listen-notify.md +54 -0
- package/skills/vds-skill/runtime/docs/p8-preflight-evidence/pgbouncer-search-path-empirical.md +110 -0
- package/skills/vds-skill/runtime/docs/p8-preflight-evidence/pgvector-set-local-audit.md +51 -0
- package/skills/vds-skill/runtime/docs/p8-preflight-evidence/topology-decision-session-mode.md +57 -0
- package/skills/vds-skill/runtime/docs/phases/CHANGELOG.md +103 -0
- package/skills/vds-skill/runtime/docs/phases/PHASE_125_COMPLETION_AND_MERGE.md +212 -0
- package/skills/vds-skill/runtime/docs/phases/phase125/IMPLEMENTATION_REPORT.md +227 -0
- package/skills/vds-skill/runtime/docs/phases/phase125/TSK-125.10-11-implementation-summary.md +196 -0
- package/skills/vds-skill/runtime/docs/phases/phase125/profile-patch-ollama-local-anthropic.md +122 -0
- package/skills/vds-skill/runtime/docs/phases/phase125_completion_summary.md +369 -0
- package/skills/vds-skill/runtime/docs/phases/phase125_llm_analysis_skill.md +164 -0
- package/skills/vds-skill/runtime/docs/phases/phase125_merge_complete.md +147 -0
- package/skills/vds-skill/runtime/docs/phases/phase125_skill_runtime_closure_20260321.md +91 -0
- package/skills/vds-skill/runtime/docs/phases/phase2-portable-paths/closure-handoff-summary-2026-03-23.md +290 -0
- package/skills/vds-skill/runtime/docs/phases/phase2-portable-paths/remaining-risk-register-2026-03-25.md +143 -0
- package/skills/vds-skill/runtime/docs/phases/phase2-portable-paths/verification-evidence-2026-03-23.md +135 -0
- package/skills/vds-skill/runtime/docs/v0-sunset-known-issues.md +88 -0
- package/skills/vds-skill/runtime/docs/vi/TRANSLATION-BACKLOG.md +72 -0
- package/skills/vds-skill/runtime/docs/vi/agents/README.md +41 -0
- package/skills/vds-skill/runtime/docs/vi/agents/explanation/features-overview.md +29 -0
- package/skills/vds-skill/runtime/docs/vi/agents/explanation/index.md +14 -0
- package/skills/vds-skill/runtime/docs/vi/agents/explanation/runtime-verification-and-gap-reporting.md +129 -0
- package/skills/vds-skill/runtime/docs/vi/agents/explanation/whats-new.md +37 -0
- package/skills/vds-skill/runtime/docs/vi/agents/explanation/who-ecosystem-introduction.md +21 -0
- package/skills/vds-skill/runtime/docs/vi/agents/explanation/who-ecosystem-model.md +36 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/02-using-vds-ai-memory.md +100 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/03-memory-cross-agent.md +243 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/04-using-progress-reports.md +242 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/08-semantic-search.md +16 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/choose-the-right-command-or-skill.md +36 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/decision-tree.md +77 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/first-audit-run.md +85 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/index.md +21 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/install-and-bootstrap-who-scripts-and-skills.md +156 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/orchestrator-workflows/analytics-pipeline-workflow.md +174 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/orchestrator-workflows/code-quality-gate-workflow.md +147 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/orchestrator-workflows/confluence-bitbucket-sync-workflow.md +139 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/orchestrator-workflows/document-delivery-workflow.md +151 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/orchestrator-workflows/memory-progress-workflow.md +149 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/orchestrator-workflows/research-spec-audit-workflow.md +144 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/phase131-all-project-preparation.md +213 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/phase131-bounded-parallel-analysis.md +125 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/phase131-confluence-upload-recovery.md +206 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/phase132-department-preparation.md +146 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/tips-and-tricks.md +34 -0
- package/skills/vds-skill/runtime/docs/vi/agents/how-to/troubleshooting-guide.md +36 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/agent-operational-contract.md +98 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/audit-triage-playbook.md +258 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/bitbucket-orchestrator.md +30 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/brd-orchestrator.md +29 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/capability-coverage-review.md +46 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/circular-dependency-orchestrator.md +29 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/cli-commands.md +409 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/cli-development-standards.md +19 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/cli-help-matrix.md +71 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/common-errors.md +133 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/configuration-reference.md +25 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/confluence-orchestrator.md +30 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/db-query-orchestrator.md +34 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/diagrams-orchestrator.md +31 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/elastic-orchestrator.md +30 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/evolution-orchestrator.md +31 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/excel-orchestrator.md +60 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/git-orchestrator.md +31 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/google-sheets-orchestrator.md +60 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/grafana-orchestrator.md +30 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/hexagonal-orchestrator.md +73 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/index.md +25 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/intellij-orchestrator.md +59 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/jira-orchestrator.md +32 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/links-orchestrator.md +66 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/lsp-orchestrator.md +60 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/markdown-orchestrator.md +62 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/mcp-orchestrator.md +34 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/memory-orchestrator.md +45 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/metabase-orchestrator.md +30 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/multi-agent-orchestrator.md +61 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/openapi-orchestrator.md +66 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/orchestrator-architecture.md +24 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/orchestrator-index.md +73 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/pdf-orchestrator.md +30 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/portable-paths-and-config.md +123 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/portable-paths-validation-matrix.md +131 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/progress-orchestrator.md +43 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/progress-report-cli.md +217 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/public-interface-orchestrator.md +82 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/research-orchestrator.md +45 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/schema-orchestrator.md +66 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/search-tools.md +19 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/skills-reference.md +27 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/sonarqube-orchestrator.md +71 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/spec-orchestrator.md +56 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/structure-orchestrator.md +78 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/system-requirements.md +30 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/tasks-orchestrator.md +45 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/validation-and-sync-notes.md +26 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/vds-ai-memory-api.md +53 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/vds-cli-reference.md +34 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/who-capability-inventory.md +98 -0
- package/skills/vds-skill/runtime/docs/vi/agents/reference/who-capability-routing-matrix.md +16 -0
- package/skills/vds-skill/runtime/docs/vi/agents/tutorials/feature-progression-guide.md +124 -0
- package/skills/vds-skill/runtime/docs/vi/agents/tutorials/index.md +13 -0
- package/skills/vds-skill/runtime/docs/vi/agents/tutorials/quick-start.md +30 -0
- package/skills/vds-skill/runtime/docs/vi/agents/tutorials/who-skills-and-scripts-onboarding.md +42 -0
- package/skills/vds-skill/runtime/docs/vi/agents/tutorials/zero-to-productive-developer.md +137 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/README.md +450 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/pyproject.toml +97 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/src/vds_elastic_orchestrator/__init__.py +81 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/src/vds_elastic_orchestrator/cli.py +652 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/src/vds_elastic_orchestrator/client.py +743 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/src/vds_elastic_orchestrator/config.py +208 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/src/vds_elastic_orchestrator/errors.py +34 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/src/vds_elastic_orchestrator/py.typed +0 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/tests/__init__.py +0 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/tests/conftest.py +227 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/tests/test_client.py +990 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/tests/test_config.py +268 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/tests/test_e2e_verification.py +272 -0
- package/skills/vds-skill/runtime/elastic_orchestrator/tests/test_errors.py +78 -0
- package/skills/vds-skill/runtime/excel_orchestrator/README.md +288 -0
- package/skills/vds-skill/runtime/excel_orchestrator/RESEARCH_BASED_UPDATES_REPORT.md +261 -0
- package/skills/vds-skill/runtime/excel_orchestrator/add_essential_missing_effort.py +255 -0
- package/skills/vds-skill/runtime/excel_orchestrator/adjust_effort_complexity.py +184 -0
- package/skills/vds-skill/runtime/excel_orchestrator/brd_analysis_and_task_breakdown.py +632 -0
- package/skills/vds-skill/runtime/excel_orchestrator/brd_analysis_comprehensive.py +1029 -0
- package/skills/vds-skill/runtime/excel_orchestrator/check_overlaps_and_brd_coverage.py +570 -0
- package/skills/vds-skill/runtime/excel_orchestrator/clean_remarks_column.py +127 -0
- package/skills/vds-skill/runtime/excel_orchestrator/comprehensive_brd_check.py +322 -0
- package/skills/vds-skill/runtime/excel_orchestrator/create_buffered_summary.py +119 -0
- package/skills/vds-skill/runtime/excel_orchestrator/create_service_totals_sheet.py +118 -0
- package/skills/vds-skill/runtime/excel_orchestrator/examples/basic_operations.py +85 -0
- package/skills/vds-skill/runtime/excel_orchestrator/expand_all_tasks.py +341 -0
- package/skills/vds-skill/runtime/excel_orchestrator/expand_tasks.py +304 -0
- package/skills/vds-skill/runtime/excel_orchestrator/fill_brd_references.py +347 -0
- package/skills/vds-skill/runtime/excel_orchestrator/fill_remarks_and_colors.py +132 -0
- package/skills/vds-skill/runtime/excel_orchestrator/finalize_brd_and_cleanup.py +295 -0
- package/skills/vds-skill/runtime/excel_orchestrator/finalize_brd_coverage.py +327 -0
- package/skills/vds-skill/runtime/excel_orchestrator/fix_all_formulas.py +99 -0
- package/skills/vds-skill/runtime/excel_orchestrator/fix_detail_presentation.py +113 -0
- package/skills/vds-skill/runtime/excel_orchestrator/fix_presentation_and_effort.py +116 -0
- package/skills/vds-skill/runtime/excel_orchestrator/fix_presentation_consistency.py +231 -0
- package/skills/vds-skill/runtime/excel_orchestrator/fix_remarks_matching.py +179 -0
- package/skills/vds-skill/runtime/excel_orchestrator/group_tasks_by_service_id.py +210 -0
- package/skills/vds-skill/runtime/excel_orchestrator/increase_brd_coverage.py +497 -0
- package/skills/vds-skill/runtime/excel_orchestrator/increase_effort_complexity.py +155 -0
- package/skills/vds-skill/runtime/excel_orchestrator/organize_and_deduplicate.py +273 -0
- package/skills/vds-skill/runtime/excel_orchestrator/pyproject.toml +64 -0
- package/skills/vds-skill/runtime/excel_orchestrator/rebuild_all_formulas.py +146 -0
- package/skills/vds-skill/runtime/excel_orchestrator/remove_base_multiplier_and_check_duplicates.py +310 -0
- package/skills/vds-skill/runtime/excel_orchestrator/remove_duplicate_brd_tasks.py +137 -0
- package/skills/vds-skill/runtime/excel_orchestrator/research_based_updates.py +457 -0
- package/skills/vds-skill/runtime/excel_orchestrator/restore_e_values.py +172 -0
- package/skills/vds-skill/runtime/excel_orchestrator/src/vds_excel_orchestrator/__init__.py +5 -0
- package/skills/vds-skill/runtime/excel_orchestrator/src/vds_excel_orchestrator/cli.py +746 -0
- package/skills/vds-skill/runtime/excel_orchestrator/src/vds_excel_orchestrator/config.py +74 -0
- package/skills/vds-skill/runtime/excel_orchestrator/src/vds_excel_orchestrator/converters.py +226 -0
- package/skills/vds-skill/runtime/excel_orchestrator/src/vds_excel_orchestrator/errors.py +88 -0
- package/skills/vds-skill/runtime/excel_orchestrator/src/vds_excel_orchestrator/excel_client.py +443 -0
- package/skills/vds-skill/runtime/excel_orchestrator/src/vds_excel_orchestrator/formatters.py +211 -0
- package/skills/vds-skill/runtime/excel_orchestrator/src/vds_excel_orchestrator/logging.py +57 -0
- package/skills/vds-skill/runtime/excel_orchestrator/src/vds_excel_orchestrator/source_contract.py +29 -0
- package/skills/vds-skill/runtime/excel_orchestrator/src/vds_excel_orchestrator/target_state_status.py +837 -0
- package/skills/vds-skill/runtime/excel_orchestrator/src/vds_excel_orchestrator/ulnc_alignment.py +1291 -0
- package/skills/vds-skill/runtime/excel_orchestrator/src/vds_excel_orchestrator/validators.py +164 -0
- package/skills/vds-skill/runtime/excel_orchestrator/sync_detail_and_total_sheets.py +211 -0
- package/skills/vds-skill/runtime/excel_orchestrator/tests/__init__.py +1 -0
- package/skills/vds-skill/runtime/excel_orchestrator/tests/conftest.py +36 -0
- package/skills/vds-skill/runtime/excel_orchestrator/tests/test_cli.py +383 -0
- package/skills/vds-skill/runtime/excel_orchestrator/tests/test_excel_client.py +129 -0
- package/skills/vds-skill/runtime/excel_orchestrator/tests/test_ulnc_alignment.py +373 -0
- package/skills/vds-skill/runtime/excel_orchestrator/tests/test_validators.py +64 -0
- package/skills/vds-skill/runtime/excel_orchestrator/update_api_database_effort.py +261 -0
- package/skills/vds-skill/runtime/excel_orchestrator/update_buffers_inline.py +115 -0
- package/skills/vds-skill/runtime/excel_orchestrator/update_complex_services_and_add_new.py +336 -0
- package/skills/vds-skill/runtime/excel_orchestrator/update_responsibility_and_fix_rows.py +208 -0
- package/skills/vds-skill/runtime/excel_orchestrator/update_task_breakdown_vietnamese.py +309 -0
- package/skills/vds-skill/runtime/excel_orchestrator/update_vietnamese_and_responsibility.py +415 -0
- package/skills/vds-skill/runtime/excel_orchestrator/verify_brd_coverage_comprehensive.py +401 -0
- package/skills/vds-skill/runtime/git_orchestrator/ENHANCEMENT_SUMMARY.md +119 -0
- package/skills/vds-skill/runtime/git_orchestrator/README.md +286 -0
- package/skills/vds-skill/runtime/git_orchestrator/VERIFICATION_REPORT.md +152 -0
- package/skills/vds-skill/runtime/git_orchestrator/pyproject.toml +37 -0
- package/skills/vds-skill/runtime/git_orchestrator/src/vds_git_orchestrator/__init__.py +30 -0
- package/skills/vds-skill/runtime/git_orchestrator/src/vds_git_orchestrator/__main__.py +4 -0
- package/skills/vds-skill/runtime/git_orchestrator/src/vds_git_orchestrator/branch_probe.py +271 -0
- package/skills/vds-skill/runtime/git_orchestrator/src/vds_git_orchestrator/cli.py +892 -0
- package/skills/vds-skill/runtime/git_orchestrator/src/vds_git_orchestrator/logging_config.py +63 -0
- package/skills/vds-skill/runtime/git_orchestrator/src/vds_git_orchestrator/manifest.py +249 -0
- package/skills/vds-skill/runtime/git_orchestrator/src/vds_git_orchestrator/orchestrator.py +1647 -0
- package/skills/vds-skill/runtime/git_orchestrator/src/vds_git_orchestrator/protocols.py +35 -0
- package/skills/vds-skill/runtime/git_orchestrator/src/vds_git_orchestrator/reporting.py +55 -0
- package/skills/vds-skill/runtime/git_orchestrator/tests/__init__.py +0 -0
- package/skills/vds-skill/runtime/git_orchestrator/tests/test_cli_settings.py +19 -0
- package/skills/vds-skill/runtime/git_orchestrator/tests/test_integration.py +79 -0
- package/skills/vds-skill/runtime/git_orchestrator/tests/test_manifest.py +79 -0
- package/skills/vds-skill/runtime/git_orchestrator/tests/test_orchestrator.py +207 -0
- package/skills/vds-skill/runtime/git_orchestrator/tests/test_public_api.py +235 -0
- package/skills/vds-skill/runtime/git_orchestrator/tests/test_resilience.py +343 -0
- package/skills/vds-skill/runtime/git_orchestrator/tests/unit/__init__.py +0 -0
- package/skills/vds-skill/runtime/git_orchestrator/tests/unit/test_branch_probe.py +327 -0
- package/skills/vds-skill/runtime/git_orchestrator/tests/unit/test_protocols.py +132 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/README.md +241 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/pyproject.toml +45 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/__init__.py +69 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/cli.py +568 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/client.py +186 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/config.py +46 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/errors.py +41 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/tests/__init__.py +1 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/tests/conftest.py +1 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/tests/unit/__init__.py +1 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/tests/unit/test_cli.py +212 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/tests/unit/test_client.py +24 -0
- package/skills/vds-skill/runtime/google_sheets_orchestrator/tests/unit/test_config.py +16 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/README.md +572 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/pyproject.toml +102 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/src/vds_grafana_orchestrator/__init__.py +78 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/src/vds_grafana_orchestrator/cli.py +455 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/src/vds_grafana_orchestrator/client.py +700 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/src/vds_grafana_orchestrator/config.py +243 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/src/vds_grafana_orchestrator/errors.py +34 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/src/vds_grafana_orchestrator/py.typed +1 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/tests/__init__.py +1 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/tests/conftest.py +308 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/tests/test_client.py +458 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/tests/test_config.py +203 -0
- package/skills/vds-skill/runtime/grafana_orchestrator/tests/test_errors.py +78 -0
- package/skills/vds-skill/runtime/jira_orchestrator/README.md +864 -0
- package/skills/vds-skill/runtime/jira_orchestrator/pyproject.toml +43 -0
- package/skills/vds-skill/runtime/jira_orchestrator/src/vds_jira_orchestrator/__init__.py +65 -0
- package/skills/vds-skill/runtime/jira_orchestrator/src/vds_jira_orchestrator/adapter.py +1685 -0
- package/skills/vds-skill/runtime/jira_orchestrator/src/vds_jira_orchestrator/cli.py +2806 -0
- package/skills/vds-skill/runtime/jira_orchestrator/src/vds_jira_orchestrator/config.py +168 -0
- package/skills/vds-skill/runtime/jira_orchestrator/src/vds_jira_orchestrator/errors.py +34 -0
- package/skills/vds-skill/runtime/jira_orchestrator/src/vds_jira_orchestrator/reporting.py +66 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/__init__.py +1 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/conftest.py +86 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_agile_list_payloads.py +54 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_bulk_operations.py +91 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_components.py +56 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_createmeta.py +45 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_dashboard.py +119 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_issue_properties.py +53 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_permissions_compat.py +41 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_reindex.py +42 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_remote_links.py +75 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_transitions.py +90 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_user_management.py +116 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_version_management.py +181 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_adapter_watchers.py +43 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_advanced_search.py +179 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_agile.py +304 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_application_properties.py +243 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_backlog.py +91 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_bulk_operations.py +403 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_cli.py +108 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_components.py +119 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_config.py +166 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_dashboard.py +122 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_discover_fields.py +207 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_errors.py +72 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_filter_management.py +411 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_issue_archiving.py +179 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_issue_links.py +257 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_issue_properties.py +189 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_link_types.py +407 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_parse_set.py +37 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_permissions.py +343 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_reindex.py +81 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_remote_links.py +269 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_security_schemes.py +202 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_transitions_changelog.py +109 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_user_management.py +246 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_version_management.py +503 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_watchers.py +116 -0
- package/skills/vds-skill/runtime/jira_orchestrator/tests/test_worklog.py +243 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/README.md +864 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/pyproject.toml +43 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/__init__.py +65 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/adapter.py +1689 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/cli.py +2799 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/config.py +135 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/errors.py +34 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/reporting.py +65 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/__init__.py +1 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/conftest.py +86 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_agile_list_payloads.py +54 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_bulk_operations.py +101 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_components.py +64 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_createmeta.py +45 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_dashboard.py +135 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_issue_properties.py +63 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_permissions_compat.py +42 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_reindex.py +42 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_remote_links.py +89 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_transitions.py +91 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_user_management.py +130 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_version_management.py +189 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_adapter_watchers.py +49 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_advanced_search.py +213 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_agile.py +334 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_application_properties.py +261 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_backlog.py +91 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_bulk_operations.py +443 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_cli.py +106 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_components.py +133 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_config.py +166 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_dashboard.py +130 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_discover_fields.py +207 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_errors.py +61 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_filter_management.py +478 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_issue_archiving.py +181 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_issue_links.py +257 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_issue_properties.py +203 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_link_types.py +426 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_parse_set.py +37 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_permissions.py +358 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_reindex.py +81 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_remote_links.py +292 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_security_schemes.py +218 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_transitions_changelog.py +121 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_user_management.py +283 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_version_management.py +561 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_watchers.py +128 -0
- package/skills/vds-skill/runtime/jira_viettelmoney_orchestrator/tests/test_worklog.py +265 -0
- package/skills/vds-skill/runtime/llms.txt +159 -0
- package/skills/vds-skill/runtime/markdown_orchestrator/README.md +72 -0
- package/skills/vds-skill/runtime/markdown_orchestrator/pyproject.toml +39 -0
- package/skills/vds-skill/runtime/markdown_orchestrator/src/vds_markdown_orchestrator/__init__.py +5 -0
- package/skills/vds-skill/runtime/markdown_orchestrator/src/vds_markdown_orchestrator/cli.py +102 -0
- package/skills/vds-skill/runtime/mcp_server/Dockerfile +63 -0
- package/skills/vds-skill/runtime/mcp_server/README.md +140 -0
- package/skills/vds-skill/runtime/mcp_server/pyproject.toml +41 -0
- package/skills/vds-skill/runtime/mcp_server/src/vds_mcp_server/__init__.py +3 -0
- package/skills/vds-skill/runtime/mcp_server/src/vds_mcp_server/config.py +36 -0
- package/skills/vds-skill/runtime/mcp_server/src/vds_mcp_server/server.py +111 -0
- package/skills/vds-skill/runtime/mcp_server/src/vds_mcp_server/tools/__init__.py +15 -0
- package/skills/vds-skill/runtime/mcp_server/src/vds_mcp_server/tools/bitbucket_tools.py +47 -0
- package/skills/vds-skill/runtime/mcp_server/src/vds_mcp_server/tools/confluence_tools.py +53 -0
- package/skills/vds-skill/runtime/mcp_server/src/vds_mcp_server/tools/git_tools.py +71 -0
- package/skills/vds-skill/runtime/mcp_server/src/vds_mcp_server/tools/jira_tools.py +63 -0
- package/skills/vds-skill/runtime/mcp_server/src/vds_mcp_server/tools/vidp_tools.py +64 -0
- package/skills/vds-skill/runtime/mcp_server/tests/__init__.py +1 -0
- package/skills/vds-skill/runtime/mcp_server/tests/conftest.py +31 -0
- package/skills/vds-skill/runtime/mcp_server/tests/unit/__init__.py +1 -0
- package/skills/vds-skill/runtime/mcp_server/tests/unit/test_bitbucket_tools.py +28 -0
- package/skills/vds-skill/runtime/mcp_server/tests/unit/test_confluence_tools.py +28 -0
- package/skills/vds-skill/runtime/mcp_server/tests/unit/test_git_tools.py +35 -0
- package/skills/vds-skill/runtime/mcp_server/tests/unit/test_jira_tools.py +35 -0
- package/skills/vds-skill/runtime/mcp_server/tests/verification/__init__.py +6 -0
- package/skills/vds-skill/runtime/mcp_server/tests/verification/conftest.py +51 -0
- package/skills/vds-skill/runtime/mcp_server/tests/verification/test_mcp_confluence_tools.py +40 -0
- package/skills/vds-skill/runtime/mcp_server/tests/verification/test_mcp_jira_tools.py +39 -0
- package/skills/vds-skill/runtime/mcp_server/tests/verification/test_mcp_tool_registration.py +50 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/.dockerignore +93 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/.env.example +40 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/.ruff_rules.py +350 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/.yamllint.yml +43 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/DEVELOPMENT_PLAN.md +80 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/Dockerfile +87 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/README.md +608 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/cli_verification_test/test.md +6 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/cli_verification_test/test.pdf +0 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/config/alertmanager.yml +83 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/config/prometheus.prod.yml +98 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/config/prometheus.yml +40 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/config/redis.conf +78 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/docs/COMPETITIVE_ANALYSIS_REPORT.md +309 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/docs/FEATURES_GUIDE.md +518 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/docs/MULTI_USER_DEPLOYMENT_GUIDE.md +615 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/docs/USER_GUIDE.md +829 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/pyproject.toml +87 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/pytest.ini +71 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/ruff.toml +6 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/scripts/debug_security_report.py +59 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/scripts/demo_library_selector.py +109 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/scripts/generate_project_stats.py +52 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/scripts/generate_styled_pdf.py +95 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/scripts/migrate_render_pdfs.py +285 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/scripts/setup_team.bat +283 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/scripts/setup_team.sh +324 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/src/vds_pdf_orchestrator/__init__.py +5 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/src/vds_pdf_orchestrator/cli.py +542 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/src/vds_pdf_orchestrator/config.py +33 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/tests/README.md +650 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/tests/__init__.py +0 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/tests/conftest.py +520 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/tests/requirements.txt +51 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/tests/run_tests.py +659 -0
- package/skills/vds-skill/runtime/pdf_orchestrator/tests/test_config.py +36 -0
- package/skills/vds-skill/runtime/platform_core/pyproject.toml +49 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/__init__.py +16 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/alembic/__init__.py +18 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/alembic/runtime.py +139 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/config.py +88 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/credentials.py +40 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/env.py +24 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/errors.py +127 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/http/__init__.py +18 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/http/auth.py +32 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/http/errors.py +47 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/http/pagination.py +65 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/http/retry.py +62 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/http/stack.py +61 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/logging.py +132 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/protocols.py +77 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/serialization.py +80 -0
- package/skills/vds-skill/runtime/platform_core/src/vds_platform_core/severity.py +175 -0
- package/skills/vds-skill/runtime/platform_core/tests/__init__.py +0 -0
- package/skills/vds-skill/runtime/platform_core/tests/conftest.py +1 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_alembic_runtime.py +300 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_auth.py +84 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_config.py +83 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_credentials.py +73 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_env.py +56 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_errors.py +201 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_errors_http.py +74 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_http_settings.py +116 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_logging.py +148 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_pagination.py +153 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_protocols.py +132 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_retry.py +151 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_serialization.py +92 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_severity.py +178 -0
- package/skills/vds-skill/runtime/platform_core/tests/test_stack.py +130 -0
- package/skills/vds-skill/runtime/platform_core/uv.lock +341 -0
- package/skills/vds-skill/runtime/pyproject.toml +145 -0
- package/skills/vds-skill/runtime/pyrightconfig.json +82 -0
- package/skills/vds-skill/runtime/repo-manifest.yaml +380 -0
- package/skills/vds-skill/runtime/repo-manifest.yaml.example +25 -0
- package/skills/vds-skill/runtime/ruff.toml +100 -0
- package/skills/vds-skill/runtime/scripts/BRD-Validation-API.postman_collection.json +706 -0
- package/skills/vds-skill/runtime/scripts/BRD-Validation-README.md +308 -0
- package/skills/vds-skill/runtime/scripts/README.md +271 -0
- package/skills/vds-skill/runtime/scripts/_validate_alias_phase2.py +137 -0
- package/skills/vds-skill/runtime/scripts/audit-cli-patterns.sh +135 -0
- package/skills/vds-skill/runtime/scripts/audit-dashboard.sh +525 -0
- package/skills/vds-skill/runtime/scripts/backup.sh +123 -0
- package/skills/vds-skill/runtime/scripts/bootstrap_uv.sh +69 -0
- package/skills/vds-skill/runtime/scripts/brd-validation-environment.json +51 -0
- package/skills/vds-skill/runtime/scripts/brd-validation-test-results.json +13023 -0
- package/skills/vds-skill/runtime/scripts/brd_coverage_report.json +276 -0
- package/skills/vds-skill/runtime/scripts/check-future-annotations.py +22 -0
- package/skills/vds-skill/runtime/scripts/check-invalid-symlinks.py +183 -0
- package/skills/vds-skill/runtime/scripts/check-no-debug-markers.py +21 -0
- package/skills/vds-skill/runtime/scripts/check-no-unittest.py +21 -0
- package/skills/vds-skill/runtime/scripts/ci/assert_no_openspace_commits.sh +37 -0
- package/skills/vds-skill/runtime/scripts/ci/verify_branch_protection.sh +64 -0
- package/skills/vds-skill/runtime/scripts/closure/phase1_check.sh +483 -0
- package/skills/vds-skill/runtime/scripts/closure/phase2_check.sh +500 -0
- package/skills/vds-skill/runtime/scripts/create_memory_session.py +36 -0
- package/skills/vds-skill/runtime/scripts/deploy-bootstrap.sh +201 -0
- package/skills/vds-skill/runtime/scripts/deployment/load_docker_images_offline.sh +90 -0
- package/skills/vds-skill/runtime/scripts/dev/cli_smoke.sh +259 -0
- package/skills/vds-skill/runtime/scripts/final_completion_report.md +139 -0
- package/skills/vds-skill/runtime/scripts/folder_structure_report.json +321 -0
- package/skills/vds-skill/runtime/scripts/generate_completion_report.py +132 -0
- package/skills/vds-skill/runtime/scripts/generate_intellij_modules.py +154 -0
- package/skills/vds-skill/runtime/scripts/init-pgbouncer-userlist.sh +154 -0
- package/skills/vds-skill/runtime/scripts/link_integrity_report.json +807 -0
- package/skills/vds-skill/runtime/scripts/move_audit_artifact_pages.py +252 -0
- package/skills/vds-skill/runtime/scripts/move_audit_artifact_pages_rest.py +165 -0
- package/skills/vds-skill/runtime/scripts/move_wrong_dept_pages.py +235 -0
- package/skills/vds-skill/runtime/scripts/openspace_bootstrap.sh +56 -0
- package/skills/vds-skill/runtime/scripts/openspace_common.sh +75 -0
- package/skills/vds-skill/runtime/scripts/openspace_doctor.sh +61 -0
- package/skills/vds-skill/runtime/scripts/openspace_sync_shadow.sh +65 -0
- package/skills/vds-skill/runtime/scripts/phase7-baseline.sh +77 -0
- package/skills/vds-skill/runtime/scripts/preflight/env_check.sh +102 -0
- package/skills/vds-skill/runtime/scripts/repair_autopay_reports.sh +173 -0
- package/skills/vds-skill/runtime/scripts/rollback_drill.sh +659 -0
- package/skills/vds-skill/runtime/scripts/run-audit-in-tmux.sh +286 -0
- package/skills/vds-skill/runtime/scripts/run-department-audit.sh +495 -0
- package/skills/vds-skill/runtime/scripts/run-project-audit.sh +267 -0
- package/skills/vds-skill/runtime/scripts/save_intellij_memories.py +112 -0
- package/skills/vds-skill/runtime/scripts/save_memories_to_vds_ai.py +81 -0
- package/skills/vds-skill/runtime/scripts/save_memories_vds_style.py +133 -0
- package/skills/vds-skill/runtime/scripts/search_intellij_memories.py +48 -0
- package/skills/vds-skill/runtime/scripts/setup_intellij_workspace.py +71 -0
- package/skills/vds-skill/runtime/scripts/smoke-test-deploy.sh +137 -0
- package/skills/vds-skill/runtime/scripts/smoke_deploy_lib.py +205 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/README.md +89 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/confluence_sync_coordinator.sh +27 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/coordination.sh +114 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/diagram_coordinator.sh +25 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/docs_root.sh +22 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/generate_diagrams.sh +22 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/markdown_coordinator.sh +25 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/progress_dashboard.sh +17 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/schema_coordinator.sh +25 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/sync_confluence.sh +30 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/update_dependencies.sh +19 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/validate_links.sh +86 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/validate_markdown.sh +52 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/validate_schemas.sh +26 -0
- package/skills/vds-skill/runtime/scripts/target-state-automation/validate_structure.sh +98 -0
- package/skills/vds-skill/runtime/scripts/tests/__init__.py +1 -0
- package/skills/vds-skill/runtime/scripts/tests/test_dockerfile_correctness.py +815 -0
- package/skills/vds-skill/runtime/scripts/tests/test_makefile_loadouts.py +560 -0
- package/skills/vds-skill/runtime/scripts/tests/test_smoke_deploy.py +313 -0
- package/skills/vds-skill/runtime/scripts/tests/test_verify_alembic.py +581 -0
- package/skills/vds-skill/runtime/scripts/tests/test_verify_infra_topology.py +254 -0
- package/skills/vds-skill/runtime/scripts/update_modules_xml.py +194 -0
- package/skills/vds-skill/runtime/scripts/uv-workspace-alignment-verification-2026-03-25.md +128 -0
- package/skills/vds-skill/runtime/scripts/uv-workspace-alignment-verification-2026-04-18.md +100 -0
- package/skills/vds-skill/runtime/scripts/validate-cli-standardization.sh +188 -0
- package/skills/vds-skill/runtime/scripts/validate_brd_coverage.py +197 -0
- package/skills/vds-skill/runtime/scripts/validate_folder_structure.py +234 -0
- package/skills/vds-skill/runtime/scripts/validate_link_integrity.py +274 -0
- package/skills/vds-skill/runtime/scripts/vami017-caller-compat-report.md +62 -0
- package/skills/vds-skill/runtime/scripts/vami017-phase-b-scaffold-notes.md +79 -0
- package/skills/vds-skill/runtime/scripts/vds_sh_helpers.sh +180 -0
- package/skills/vds-skill/runtime/scripts/verification/phase2_portable_paths_ubuntu_docker.sh +26 -0
- package/skills/vds-skill/runtime/scripts/verify-infra-topology.py +868 -0
- package/skills/vds-skill/runtime/scripts/verify-memory-cli-e2e.sh +598 -0
- package/skills/vds-skill/runtime/scripts/verify-worktree-features.sh +306 -0
- package/skills/vds-skill/runtime/scripts/worktree-add.sh +128 -0
- package/skills/vds-skill/runtime/scripts/worktree-remove.sh +112 -0
- package/skills/vds-skill/runtime/scripts/worktree_compose.sh +269 -0
- package/skills/vds-skill/runtime/scripts/worktree_uv.sh +77 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/IMPLEMENTATION_AUDIT.md +376 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/README.md +507 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/pyproject.toml +106 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/scripts/ensure_symlink.sh +38 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/__init__.py +164 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/batch.py +212 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/cli.py +1407 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/client.py +608 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/config.py +260 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/diff.py +220 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/errors.py +34 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/external_sca.py +932 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/portfolio.py +225 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/pr.py +505 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/reports.py +342 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/scanner.py +351 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/webhooks.py +269 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/tests/__init__.py +0 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/tests/conftest.py +134 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/tests/test_batch.py +419 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/tests/test_config.py +145 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/tests/test_errors.py +78 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/tests/test_external_sca.py +466 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/tests/test_pr.py +471 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/tests/test_reports.py +511 -0
- package/skills/vds-skill/runtime/sonarqube_orchestrator/tests/test_webhooks.py +660 -0
- package/skills/vds-skill/runtime/uv.lock +5046 -0
- package/skills/vds-skill/runtime/vds_agent_core/CHANGELOG.md +36 -0
- package/skills/vds-skill/runtime/vds_agent_core/README.md +453 -0
- package/skills/vds-skill/runtime/vds_agent_core/docs/PHASE9A_ASSESSMENT.md +50 -0
- package/skills/vds-skill/runtime/vds_agent_core/docs/embedding.md +468 -0
- package/skills/vds-skill/runtime/vds_agent_core/pyproject.toml +51 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/__init__.py +29 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/agents/__init__.py +26 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/agents/hooks.py +119 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/agents/loop.py +864 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/agents/tools.py +41 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/config.py +252 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/llm/__init__.py +55 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/llm/_cascade.py +143 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/llm/budget.py +353 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/llm/cache.py +373 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/llm/embedding.py +815 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/llm/provider.py +173 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/llm/schemas.py +45 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/observability/__init__.py +77 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/observability/decorators.py +258 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/observability/jsonl_exporter.py +236 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/observability/tracer.py +497 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/profiles.py +2015 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/runtime/__init__.py +0 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/runtime/agent_id.py +60 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/security/__init__.py +13 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/security/credentials.py +106 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/skills/__init__.py +1 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/skills/executor.py +238 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/skills/manager.py +381 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/skills/policy.py +568 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/workflows/__init__.py +19 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/workflows/langgraph_runner.py +102 -0
- package/skills/vds-skill/runtime/vds_agent_core/src/vds_agent_core/workflows/protocols.py +81 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/__init__.py +0 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/conftest.py +62 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/integration/__init__.py +0 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/integration/test_audit_loop_hooks_integration.py +135 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/integration/test_audit_observability_integration.py +246 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/integration/test_public_api_stability.py +91 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/__init__.py +0 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/llm/__init__.py +0 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/llm/test_call_site_parallelism.py +30 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/llm/test_dimension_guardrail.py +25 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/llm/test_drop_in_provider_extensibility.py +76 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/llm/test_embedding.py +393 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/llm/test_embedding_cache.py +302 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/llm/test_embedding_extra.py +696 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/llm/test_embedding_subclass.py +49 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/llm/test_no_provider_leakage_in_env.py +34 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/llm/test_provider_auto_route.py +48 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/llm/test_runtime_log_clean.py +111 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/llm/test_w7_logic_fixes.py +219 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/profiles/__init__.py +0 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/profiles/test_embedding_block_parser.py +194 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/profiles/test_env_resolver_allowlist.py +141 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/profiles/test_profile_authorization.py +158 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/profiles/test_profiles_w3_extra.py +547 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/profiles/test_real_audit_profile_compat.py +129 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/runtime/__init__.py +0 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/runtime/test_for_agent.py +322 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/runtime/test_w9_cascade_edges.py +369 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/security/__init__.py +0 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/security/test_credentials.py +132 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_agent_loop.py +663 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_agent_loop_coverage.py +429 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_agents_hooks_defaults.py +22 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_budget.py +155 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_budget_coverage.py +264 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_budget_tracking_only.py +71 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_cache.py +251 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_cache_context.py +62 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_config.py +155 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_langgraph_runner.py +45 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_langgraph_runner_coverage.py +98 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_llm_cache_deep.py +113 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_observability_decorators.py +697 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_observability_hooks.py +217 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_observability_jsonl_exporter.py +542 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_observability_jsonl_wiring.py +313 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_observability_tracer.py +896 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_profiles.py +1571 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_profiles_coverage.py +444 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_provider.py +316 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_schemas.py +63 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_skill_executor.py +297 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_skill_manager.py +370 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_skill_manager_coverage.py +364 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_skill_policy.py +402 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_skill_rubric.py +47 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_tools.py +51 -0
- package/skills/vds-skill/runtime/vds_agent_core/tests/unit/test_workflow_protocols.py +136 -0
- package/skills/vds-skill/runtime/vds_cli/README.md +201 -0
- package/skills/vds-skill/runtime/vds_cli/VERIFICATION_REPORT.md +41 -0
- package/skills/vds-skill/runtime/vds_cli/pyproject.toml +50 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/__init__.py +3 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/assets/git-credential-helper.py +235 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/cli.py +1126 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/commands/__init__.py +1 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/commands/lint_cli.py +389 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/confluence_sync.py +855 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/docs/consumption/__init__.py +7 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/docs/consumption/funnel.py +105 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/docs/consumption/scanner.py +211 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/docs/freshness/report.py +90 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/docs/types.py +27 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/docs_cmd.py +672 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/docs_metrics.py +75 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/docs_sync.py +1171 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/ecosystem/__init__.py +39 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/ecosystem/report.py +439 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/ecosystem_docs.py +164 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/env.py +111 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/env_git_helper.py +281 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/google_sheets_orchestrator/__init__.py +3 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/google_sheets_orchestrator/google_sheets_orchestrator.py +173 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/router.py +232 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/skills_cmd.py +274 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/sync_api.py +613 -0
- package/skills/vds-skill/runtime/vds_cli/src/vds_cli/sync_service.py +283 -0
- package/skills/vds-skill/runtime/vds_cli/tests/conftest.py +62 -0
- package/skills/vds-skill/runtime/vds_cli/tests/test_env_git_helper_lifecycle.py +261 -0
- package/skills/vds-skill/runtime/vds_cli/tests/test_git_credential_helper.py +240 -0
- package/skills/vds-skill/runtime/vds_cli/tests/test_router_help_proxy.py +43 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_cli.py +241 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_cli_DOC004.py +110 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_confluence_sync.py +315 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_confluence_sync_wave7.py +375 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_consumption_funnel.py +106 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_consumption_scanner.py +144 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_docs_cmd.py +89 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_docs_cmd_wave8.py +161 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_docs_metrics.py +16 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_docs_quality_score.py +61 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_docs_sync.py +417 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_ecosystem_cli_dashboard.py +667 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_ecosystem_cli_dashboard_rendering.py +143 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_ecosystem_docs.py +63 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_env.py +85 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_freshness_report.py +125 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_lint_cli.py +224 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_router.py +101 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_skills_cmd.py +419 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_sync_api.py +357 -0
- package/skills/vds-skill/runtime/vds_cli/tests/unit/test_sync_service.py +170 -0
- package/skills/vds-skill/runtime/vds_cli/tests/verification/conftest.py +51 -0
- package/skills/vds-skill/runtime/vds_cli/tests/verification/test_bitbucket_real.py +32 -0
- package/skills/vds-skill/runtime/vds_cli/tests/verification/test_confluence_real.py +32 -0
- package/skills/vds-skill/runtime/vds_cli/tests/verification/test_jira_real.py +40 -0
- package/skills/vds-skill/runtime/vds_cli_common/README.md +190 -0
- package/skills/vds-skill/runtime/vds_cli_common/pyproject.toml +96 -0
- package/skills/vds-skill/runtime/vds_cli_common/src/vds_cli_common/__init__.py +36 -0
- package/skills/vds-skill/runtime/vds_cli_common/src/vds_cli_common/app.py +55 -0
- package/skills/vds-skill/runtime/vds_cli_common/src/vds_cli_common/completers.py +139 -0
- package/skills/vds-skill/runtime/vds_cli_common/src/vds_cli_common/context.py +201 -0
- package/skills/vds-skill/runtime/vds_cli_common/src/vds_cli_common/env.py +163 -0
- package/skills/vds-skill/runtime/vds_cli_common/src/vds_cli_common/errors.py +440 -0
- package/skills/vds-skill/runtime/vds_cli_common/src/vds_cli_common/output.py +284 -0
- package/skills/vds-skill/runtime/vds_cli_common/src/vds_cli_common/paths.py +78 -0
- package/skills/vds-skill/runtime/vds_cli_common/src/vds_cli_common/testing.py +211 -0
- package/skills/vds-skill/runtime/vds_cli_common/src/vds_cli_common/version.py +85 -0
- package/skills/vds-skill/runtime/vds_cli_common/tests/__init__.py +0 -0
- package/skills/vds-skill/runtime/vds_cli_common/tests/test_app.py +126 -0
- package/skills/vds-skill/runtime/vds_cli_common/tests/test_completers.py +148 -0
- package/skills/vds-skill/runtime/vds_cli_common/tests/test_context.py +192 -0
- package/skills/vds-skill/runtime/vds_cli_common/tests/test_env.py +235 -0
- package/skills/vds-skill/runtime/vds_cli_common/tests/test_errors.py +275 -0
- package/skills/vds-skill/runtime/vds_cli_common/tests/test_output.py +229 -0
- package/skills/vds-skill/runtime/vds_cli_common/tests/test_paths.py +61 -0
- package/skills/vds-skill/runtime/vds_cli_common/tests/test_testing.py +138 -0
- package/skills/vds-skill/runtime/vds_cli_common/tests/test_version.py +64 -0
- package/skills/vds-skill/runtime/vidp_orchestrator/README.md +31 -0
- package/skills/vds-skill/runtime/vidp_orchestrator/pyproject.toml +50 -0
- package/skills/vds-skill/runtime/vidp_orchestrator/src/vds_vidp_orchestrator/__init__.py +26 -0
- package/skills/vds-skill/runtime/vidp_orchestrator/src/vds_vidp_orchestrator/cli.py +246 -0
- package/skills/vds-skill/runtime/vidp_orchestrator/src/vds_vidp_orchestrator/client.py +104 -0
- package/skills/vds-skill/runtime/vidp_orchestrator/src/vds_vidp_orchestrator/config.py +82 -0
- package/skills/vds-skill/runtime/vidp_orchestrator/src/vds_vidp_orchestrator/workflows.json +3 -0
- package/skills/vds-skill/runtime/vidp_orchestrator/src/vds_vidp_orchestrator/workflows.py +130 -0
- package/skills/vds-skill/vds-scripts-skill/SKILL.md +129 -0
- package/skills/vds-skill/vds-scripts-skill/references/audit-commands.md +171 -0
- package/skills/vds-skill/vds-scripts-skill/references/capability-index.md +34 -0
- package/skills/vds-skill/vds-scripts-skill/references/development-commands.md +12 -0
- package/skills/vds-skill/vds-scripts-skill/references/google-sheets.md +71 -0
- package/skills/vds-skill/vds-scripts-skill/references/integration-commands.md +17 -0
- package/skills/vds-skill/vds-scripts-skill/references/platform-bootstrap.md +25 -0
- package/skills/vds-skill/vds-scripts-skill/references/specialist-routing.md +14 -0
- package/skills/vds-skill/vds-scripts-skill/references/validation-commands.md +15 -0
|
@@ -0,0 +1,1850 @@
|
|
|
1
|
+
"""Confluence client wrapper using atlassian-python-api for consistency."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from datetime import UTC, datetime, timedelta
|
|
9
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
10
|
+
from urllib.parse import parse_qs, urlparse
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
import structlog
|
|
14
|
+
from vds_platform_core.credentials import resolve_secret
|
|
15
|
+
|
|
16
|
+
try: # Prefer new-style Atlassian clients when available
|
|
17
|
+
from atlassian.confluence import ConfluenceCloud as AtlassianConfluenceCloud # type: ignore[attr-defined]
|
|
18
|
+
from atlassian.confluence import ConfluenceServer as AtlassianConfluenceServer # type: ignore[attr-defined]
|
|
19
|
+
except ImportError: # Fall back to legacy class
|
|
20
|
+
from atlassian import Confluence as AtlassianConfluenceCloud # type: ignore
|
|
21
|
+
|
|
22
|
+
AtlassianConfluenceServer = AtlassianConfluenceCloud
|
|
23
|
+
|
|
24
|
+
from .errors import (
|
|
25
|
+
AuthError,
|
|
26
|
+
ClientPermissionError,
|
|
27
|
+
ConflictError,
|
|
28
|
+
ConfluenceDegradedWriteError,
|
|
29
|
+
RateLimitError,
|
|
30
|
+
ResponseError,
|
|
31
|
+
RetryError,
|
|
32
|
+
TransportError,
|
|
33
|
+
VDSClientError,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from .config import ConfluenceSettings
|
|
38
|
+
|
|
39
|
+
T = TypeVar("T")
|
|
40
|
+
|
|
41
|
+
# Write operations blocked when the client is in degraded mode.
|
|
42
|
+
_WRITE_OPS: frozenset[str] = frozenset(
|
|
43
|
+
{
|
|
44
|
+
"create_page",
|
|
45
|
+
"update_page",
|
|
46
|
+
"delete_page",
|
|
47
|
+
"upload_attachment",
|
|
48
|
+
"update_attachment",
|
|
49
|
+
"delete_attachment",
|
|
50
|
+
"add_attachment",
|
|
51
|
+
"add_comment",
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _escape_cql_literal(value: str) -> str:
|
|
57
|
+
return value.replace("\\", "\\\\").replace('"', '\\"')
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _coerce_utc_datetime(value: datetime | str) -> datetime:
|
|
61
|
+
if isinstance(value, datetime):
|
|
62
|
+
dt_value = value
|
|
63
|
+
else:
|
|
64
|
+
normalized = value.strip()
|
|
65
|
+
if normalized.endswith("Z"):
|
|
66
|
+
normalized = normalized[:-1] + "+00:00"
|
|
67
|
+
try:
|
|
68
|
+
dt_value = datetime.fromisoformat(normalized)
|
|
69
|
+
except ValueError as exc:
|
|
70
|
+
raise VDSClientError(
|
|
71
|
+
"Invalid incremental high-water-mark timestamp",
|
|
72
|
+
context={"value": value},
|
|
73
|
+
) from exc
|
|
74
|
+
if dt_value.tzinfo is None:
|
|
75
|
+
return dt_value.replace(tzinfo=UTC)
|
|
76
|
+
return dt_value.astimezone(UTC)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _build_lastmodified_cql_clause(
|
|
80
|
+
last_modified_after: datetime | str | None,
|
|
81
|
+
*,
|
|
82
|
+
overlap_minutes: int = 0,
|
|
83
|
+
) -> str | None:
|
|
84
|
+
if last_modified_after is None:
|
|
85
|
+
return None
|
|
86
|
+
overlap = max(int(overlap_minutes), 0)
|
|
87
|
+
cursor = _coerce_utc_datetime(last_modified_after)
|
|
88
|
+
if overlap:
|
|
89
|
+
cursor = cursor - timedelta(minutes=overlap)
|
|
90
|
+
return f'lastmodified >= "{cursor.strftime("%Y-%m-%d %H:%M")}"'
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _build_space_type_lastmodified_cql(
|
|
94
|
+
*,
|
|
95
|
+
space_key: str | None = None,
|
|
96
|
+
content_type: str | None = None,
|
|
97
|
+
last_modified_after: datetime | str | None = None,
|
|
98
|
+
overlap_minutes: int = 0,
|
|
99
|
+
) -> str:
|
|
100
|
+
cql_parts: list[str] = []
|
|
101
|
+
if space_key:
|
|
102
|
+
cql_parts.append(f'space = "{_escape_cql_literal(space_key)}"')
|
|
103
|
+
if content_type:
|
|
104
|
+
cql_parts.append(f'type = "{_escape_cql_literal(content_type)}"')
|
|
105
|
+
last_modified_clause = _build_lastmodified_cql_clause(
|
|
106
|
+
last_modified_after,
|
|
107
|
+
overlap_minutes=overlap_minutes,
|
|
108
|
+
)
|
|
109
|
+
if last_modified_clause:
|
|
110
|
+
cql_parts.append(last_modified_clause)
|
|
111
|
+
if not cql_parts:
|
|
112
|
+
raise VDSClientError(
|
|
113
|
+
"At least one of space_key, content_type, or last_modified_after must be provided",
|
|
114
|
+
context={},
|
|
115
|
+
)
|
|
116
|
+
query = " AND ".join(cql_parts)
|
|
117
|
+
if last_modified_clause:
|
|
118
|
+
query = f"{query} ORDER BY lastmodified ASC"
|
|
119
|
+
return query
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ConfluenceHttpClient:
|
|
123
|
+
def __init__(
|
|
124
|
+
self,
|
|
125
|
+
settings: ConfluenceSettings,
|
|
126
|
+
*,
|
|
127
|
+
server: str = "internal",
|
|
128
|
+
client: httpx.Client | None = None,
|
|
129
|
+
) -> None:
|
|
130
|
+
token = settings.token_for(server)
|
|
131
|
+
if not token:
|
|
132
|
+
raise AuthError(
|
|
133
|
+
f"Token for server '{server}' is missing.",
|
|
134
|
+
context={"server": server},
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
self._settings = settings
|
|
138
|
+
self._server = server
|
|
139
|
+
self._token = token
|
|
140
|
+
if client is None:
|
|
141
|
+
base_url = str(settings.url_for(server))
|
|
142
|
+
client = httpx.Client(
|
|
143
|
+
base_url=base_url,
|
|
144
|
+
headers={
|
|
145
|
+
"Authorization": f"Bearer {token}",
|
|
146
|
+
"Accept": "application/json",
|
|
147
|
+
},
|
|
148
|
+
timeout=30,
|
|
149
|
+
)
|
|
150
|
+
self._client = client
|
|
151
|
+
|
|
152
|
+
def close(self) -> None:
|
|
153
|
+
self._client.close()
|
|
154
|
+
|
|
155
|
+
def __enter__(self) -> ConfluenceHttpClient:
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
def __exit__(self, exc_type, exc, tb) -> None: # type: ignore[override]
|
|
159
|
+
self.close()
|
|
160
|
+
|
|
161
|
+
def get_runtime_telemetry(self) -> dict[str, Any]:
|
|
162
|
+
"""Return telemetry snapshot (stub for crawl_tree compatibility).
|
|
163
|
+
|
|
164
|
+
ConfluenceClient operates in degraded-mode (read-only) or normal mode.
|
|
165
|
+
This stub provides a compatible interface for telemetry consumers.
|
|
166
|
+
Actual retry/telemetry instrumentation would require adopting
|
|
167
|
+
platform_core.with_retry (httpx.AsyncClient), which is a larger
|
|
168
|
+
refactor beyond Phase 7 scope.
|
|
169
|
+
"""
|
|
170
|
+
return {
|
|
171
|
+
"adaptive_throttle": None,
|
|
172
|
+
"recent_retry_events": [],
|
|
173
|
+
"latency_ms": None,
|
|
174
|
+
"mode": "degraded" if self._degraded else "normal",
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
def request(self, method: str, path: str, **kwargs: Any) -> httpx.Response:
|
|
178
|
+
try:
|
|
179
|
+
response = self._client.request(method, path, **kwargs)
|
|
180
|
+
except httpx.HTTPError as exc: # pragma: no cover - network errors
|
|
181
|
+
raise TransportError(f"HTTP transport error: {exc}") from exc
|
|
182
|
+
|
|
183
|
+
if response.status_code < 400:
|
|
184
|
+
return response
|
|
185
|
+
|
|
186
|
+
if response.status_code in {401, 403}:
|
|
187
|
+
raise AuthError(
|
|
188
|
+
"Authentication failed",
|
|
189
|
+
context={"status_code": response.status_code},
|
|
190
|
+
)
|
|
191
|
+
if response.status_code == 409:
|
|
192
|
+
raise ConflictError(
|
|
193
|
+
"Request conflicted with existing state",
|
|
194
|
+
context={"status_code": response.status_code},
|
|
195
|
+
)
|
|
196
|
+
raise ResponseError(
|
|
197
|
+
f"Unexpected status {response.status_code}",
|
|
198
|
+
status_code=response.status_code,
|
|
199
|
+
context={"status_code": response.status_code, "body": response.text[:256]},
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class ConfluenceClient:
|
|
204
|
+
"""Confluence client wrapper using atlassian-python-api SDK."""
|
|
205
|
+
|
|
206
|
+
def __init__(
|
|
207
|
+
self,
|
|
208
|
+
settings: ConfluenceSettings,
|
|
209
|
+
*,
|
|
210
|
+
server: str | None = None,
|
|
211
|
+
timeout: int | None = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
self._settings = settings
|
|
214
|
+
self._server = server or settings.default_server
|
|
215
|
+
self._timeout = timeout or settings.http_timeout
|
|
216
|
+
|
|
217
|
+
base_url = str(settings.url_for(self._server))
|
|
218
|
+
token = settings.token_for(self._server)
|
|
219
|
+
|
|
220
|
+
# Decide whether this endpoint should be treated as Cloud (v2) or Server (v1)
|
|
221
|
+
self._is_cloud = self._looks_like_cloud(base_url)
|
|
222
|
+
client_cls = AtlassianConfluenceCloud if self._is_cloud else AtlassianConfluenceServer
|
|
223
|
+
|
|
224
|
+
# Configure authentication based on available credentials
|
|
225
|
+
# Token (Bearer/PAT) takes priority over username+password to avoid 401s
|
|
226
|
+
# when Basic auth is disabled on the server.
|
|
227
|
+
auth_kwargs: dict[str, Any] = {"url": base_url, "timeout": self._timeout}
|
|
228
|
+
if self._is_cloud:
|
|
229
|
+
if token:
|
|
230
|
+
auth_kwargs["token"] = token
|
|
231
|
+
elif settings.username and settings.password:
|
|
232
|
+
auth_kwargs["username"] = settings.username
|
|
233
|
+
auth_kwargs["password"] = resolve_secret(settings.password)
|
|
234
|
+
else:
|
|
235
|
+
raise AuthError(
|
|
236
|
+
f"No credentials available for cloud server '{self._server}'. "
|
|
237
|
+
"Provide an API token or VDS_USERNAME+VDS_PASSWORD.",
|
|
238
|
+
context={"server": self._server},
|
|
239
|
+
)
|
|
240
|
+
elif token:
|
|
241
|
+
# PATs supported on Confluence Server/Data Center
|
|
242
|
+
auth_kwargs["token"] = token
|
|
243
|
+
elif settings.username and settings.password:
|
|
244
|
+
auth_kwargs["username"] = settings.username
|
|
245
|
+
auth_kwargs["password"] = resolve_secret(settings.password)
|
|
246
|
+
else:
|
|
247
|
+
raise AuthError(
|
|
248
|
+
f"No credentials available for server '{self._server}'. "
|
|
249
|
+
"Provide a CONFLUENCE token or VDS_USERNAME+VDS_PASSWORD.",
|
|
250
|
+
context={"server": self._server},
|
|
251
|
+
)
|
|
252
|
+
client_params = set(self._safe_signature_params(client_cls))
|
|
253
|
+
supports_kwargs = False
|
|
254
|
+
try:
|
|
255
|
+
signature = inspect.signature(client_cls)
|
|
256
|
+
supports_kwargs = any(
|
|
257
|
+
param.kind is inspect.Parameter.VAR_KEYWORD for param in signature.parameters.values()
|
|
258
|
+
)
|
|
259
|
+
except (TypeError, ValueError):
|
|
260
|
+
supports_kwargs = False
|
|
261
|
+
if "cloud" in client_params or supports_kwargs:
|
|
262
|
+
# Legacy Confluence() requires explicit cloud flag when URL already includes /wiki.
|
|
263
|
+
auth_kwargs["cloud"] = self._is_cloud
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
self._client = client_cls(**auth_kwargs)
|
|
267
|
+
except Exception as exc:
|
|
268
|
+
raise TransportError(
|
|
269
|
+
f"Failed to initialize Confluence client: {exc}", context={"server": self._server, "base_url": base_url}
|
|
270
|
+
) from exc
|
|
271
|
+
|
|
272
|
+
self._log = structlog.get_logger(__name__).bind(server=self._server, base_url=base_url)
|
|
273
|
+
|
|
274
|
+
# Store retry settings
|
|
275
|
+
self._max_retries = settings.max_retries
|
|
276
|
+
self._retry_backoff_factor = settings.retry_backoff_factor
|
|
277
|
+
self._retry_max_wait = 30.0
|
|
278
|
+
self._create_params = set(self._safe_signature_params(self._client.create_page))
|
|
279
|
+
self._update_params = set(self._safe_signature_params(self._client.update_page))
|
|
280
|
+
# FR-172: degraded mode — set True on transport errors to block writes.
|
|
281
|
+
self._degraded: bool = False
|
|
282
|
+
|
|
283
|
+
# ------------------------------------------------------------------
|
|
284
|
+
# FR-172: Degraded mode API
|
|
285
|
+
# ------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def is_degraded(self) -> bool:
|
|
289
|
+
"""True when the client has been marked degraded due to transport errors."""
|
|
290
|
+
return self._degraded
|
|
291
|
+
|
|
292
|
+
def mark_degraded(self) -> None:
|
|
293
|
+
"""Mark the client as degraded, blocking all subsequent write operations."""
|
|
294
|
+
self._degraded = True
|
|
295
|
+
|
|
296
|
+
def get_runtime_telemetry(self) -> dict[str, Any]:
|
|
297
|
+
"""Return telemetry snapshot (stub for crawl_tree compatibility).
|
|
298
|
+
|
|
299
|
+
ConfluenceClient operates in degraded-mode (read-only) or normal mode.
|
|
300
|
+
This stub provides a compatible interface for telemetry consumers.
|
|
301
|
+
Actual retry/telemetry instrumentation would require adopting
|
|
302
|
+
platform_core.with_retry (httpx.AsyncClient), which is a larger
|
|
303
|
+
refactor beyond Phase 7 scope.
|
|
304
|
+
"""
|
|
305
|
+
return {
|
|
306
|
+
"adaptive_throttle": None,
|
|
307
|
+
"recent_retry_events": [],
|
|
308
|
+
"latency_ms": None,
|
|
309
|
+
"mode": "degraded" if self._degraded else "normal",
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
def supports_api_version(self, version: str) -> bool:
|
|
313
|
+
version = (version or "v1").lower()
|
|
314
|
+
if version == "v1":
|
|
315
|
+
return True
|
|
316
|
+
if version == "v2":
|
|
317
|
+
return self._is_cloud
|
|
318
|
+
return False
|
|
319
|
+
|
|
320
|
+
@staticmethod
|
|
321
|
+
def _looks_like_cloud(base_url: str) -> bool:
|
|
322
|
+
lowered = base_url.lower()
|
|
323
|
+
return any(indicator in lowered for indicator in ("atlassian.net", "atlassian.com"))
|
|
324
|
+
|
|
325
|
+
@staticmethod
|
|
326
|
+
def _safe_signature_params(method) -> list[str]:
|
|
327
|
+
try:
|
|
328
|
+
return list(inspect.signature(method).parameters.keys())
|
|
329
|
+
except (TypeError, ValueError):
|
|
330
|
+
return []
|
|
331
|
+
|
|
332
|
+
@staticmethod
|
|
333
|
+
def _extract_pagination_marker(payload: dict[str, Any]) -> tuple[int | None, str | None]:
|
|
334
|
+
"""Extract pagination start/token markers from SDK payloads."""
|
|
335
|
+
next_start: int | None = None
|
|
336
|
+
next_token: str | None = None
|
|
337
|
+
|
|
338
|
+
links = payload.get("_links")
|
|
339
|
+
next_link: str | None = None
|
|
340
|
+
if isinstance(links, dict) and isinstance(links.get("next"), str):
|
|
341
|
+
next_link = links["next"]
|
|
342
|
+
elif isinstance(payload.get("next"), str):
|
|
343
|
+
next_link = payload["next"]
|
|
344
|
+
|
|
345
|
+
if next_link:
|
|
346
|
+
next_token = next_link
|
|
347
|
+
query = urlparse(next_link).query
|
|
348
|
+
if not query and "=" in next_link and "?" not in next_link:
|
|
349
|
+
query = next_link
|
|
350
|
+
params = parse_qs(query)
|
|
351
|
+
start_values = params.get("start")
|
|
352
|
+
if start_values:
|
|
353
|
+
try:
|
|
354
|
+
next_start = int(start_values[0])
|
|
355
|
+
except (TypeError, ValueError):
|
|
356
|
+
next_start = None
|
|
357
|
+
cursor_values = params.get("cursor")
|
|
358
|
+
if cursor_values and cursor_values[0]:
|
|
359
|
+
next_token = cursor_values[0]
|
|
360
|
+
|
|
361
|
+
for key in ("nextCursor", "next_cursor", "cursor"):
|
|
362
|
+
value = payload.get(key)
|
|
363
|
+
if isinstance(value, str) and value.strip():
|
|
364
|
+
next_token = value.strip()
|
|
365
|
+
break
|
|
366
|
+
|
|
367
|
+
raw_start = payload.get("start")
|
|
368
|
+
if next_start is None and isinstance(raw_start, int):
|
|
369
|
+
next_start = raw_start
|
|
370
|
+
|
|
371
|
+
return next_start, next_token
|
|
372
|
+
|
|
373
|
+
@staticmethod
|
|
374
|
+
def _require_dict(value: Any, operation: str) -> dict[str, Any]:
|
|
375
|
+
if isinstance(value, dict):
|
|
376
|
+
return value
|
|
377
|
+
raise VDSClientError(
|
|
378
|
+
f"{operation} returned unexpected payload type",
|
|
379
|
+
context={"operation": operation, "payload_type": type(value).__name__},
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
@staticmethod
|
|
383
|
+
def _coerce_dict(value: Any) -> dict[str, Any]:
|
|
384
|
+
if isinstance(value, dict):
|
|
385
|
+
return value
|
|
386
|
+
return {"value": value}
|
|
387
|
+
|
|
388
|
+
@staticmethod
|
|
389
|
+
def _coerce_list_of_dicts(value: Any) -> list[dict[str, Any]]:
|
|
390
|
+
if isinstance(value, list):
|
|
391
|
+
return [item for item in value if isinstance(item, dict)]
|
|
392
|
+
return []
|
|
393
|
+
|
|
394
|
+
@staticmethod
|
|
395
|
+
def _coerce_bytes(value: Any) -> bytes:
|
|
396
|
+
if isinstance(value, bytes):
|
|
397
|
+
return value
|
|
398
|
+
if isinstance(value, str):
|
|
399
|
+
return value.encode("utf-8")
|
|
400
|
+
raise VDSClientError(
|
|
401
|
+
"Unexpected binary payload type",
|
|
402
|
+
context={"payload_type": type(value).__name__},
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
@staticmethod
|
|
406
|
+
def _coerce_str(value: Any) -> str:
|
|
407
|
+
if isinstance(value, str):
|
|
408
|
+
return value
|
|
409
|
+
return str(value)
|
|
410
|
+
|
|
411
|
+
def _handle_api_error(self, error: Exception, operation: str) -> None:
|
|
412
|
+
"""Convert atlassian-python-api errors to our standardized errors."""
|
|
413
|
+
error_str = str(error).lower()
|
|
414
|
+
|
|
415
|
+
if "unauthorized" in error_str or "authentication" in error_str or "401" in error_str:
|
|
416
|
+
raise AuthError(f"Authentication failed during {operation}", context={"error": str(error)})
|
|
417
|
+
elif "forbidden" in error_str or "403" in error_str:
|
|
418
|
+
raise AuthError(f"Access forbidden during {operation}", context={"error": str(error)})
|
|
419
|
+
elif "rate limit" in error_str or "429" in error_str:
|
|
420
|
+
raise RateLimitError(
|
|
421
|
+
f"Rate limited during {operation}",
|
|
422
|
+
retry_after=None,
|
|
423
|
+
context={"error": str(error)},
|
|
424
|
+
)
|
|
425
|
+
elif "conflict" in error_str or "409" in error_str:
|
|
426
|
+
raise ConflictError(f"Conflict during {operation}", context={"error": str(error)})
|
|
427
|
+
elif "server error" in error_str or "500" in error_str:
|
|
428
|
+
raise ResponseError(
|
|
429
|
+
f"Server error during {operation}",
|
|
430
|
+
status_code=500,
|
|
431
|
+
context={"error": str(error)},
|
|
432
|
+
)
|
|
433
|
+
else:
|
|
434
|
+
raise VDSClientError(f"API error during {operation}: {error}", context={"error": str(error)})
|
|
435
|
+
|
|
436
|
+
def _with_retry(self, operation: Callable[[], T], operation_name: str) -> T:
|
|
437
|
+
"""Execute an operation with retry logic using instance settings."""
|
|
438
|
+
op_base = (operation_name or "").strip().lower().split(" ", 1)[0]
|
|
439
|
+
if self._degraded and op_base in _WRITE_OPS:
|
|
440
|
+
raise ConfluenceDegradedWriteError(operation_name)
|
|
441
|
+
for attempt in range(self._max_retries + 1):
|
|
442
|
+
try:
|
|
443
|
+
return operation()
|
|
444
|
+
except Exception as exc:
|
|
445
|
+
try:
|
|
446
|
+
self._handle_api_error(exc, operation_name)
|
|
447
|
+
except (TransportError, RateLimitError) as retry_exc:
|
|
448
|
+
if attempt == self._max_retries:
|
|
449
|
+
self._log.error(
|
|
450
|
+
"retry_attempts_exhausted",
|
|
451
|
+
operation=operation_name,
|
|
452
|
+
attempts=self._max_retries,
|
|
453
|
+
final_error=str(retry_exc),
|
|
454
|
+
)
|
|
455
|
+
raise RetryError(
|
|
456
|
+
f"Retry attempts exhausted: {retry_exc}",
|
|
457
|
+
attempts=self._max_retries,
|
|
458
|
+
context={"original_error": str(retry_exc), "operation": operation_name},
|
|
459
|
+
) from retry_exc
|
|
460
|
+
|
|
461
|
+
wait_time = min(
|
|
462
|
+
max(self._retry_backoff_factor, 0.0) * (2**attempt),
|
|
463
|
+
self._retry_max_wait,
|
|
464
|
+
)
|
|
465
|
+
self._log.warning(
|
|
466
|
+
"retry_attempt",
|
|
467
|
+
operation=operation_name,
|
|
468
|
+
attempt=attempt + 1,
|
|
469
|
+
max_retries=self._max_retries,
|
|
470
|
+
wait_time=wait_time,
|
|
471
|
+
error=str(retry_exc),
|
|
472
|
+
)
|
|
473
|
+
time.sleep(wait_time)
|
|
474
|
+
except VDSClientError:
|
|
475
|
+
raise
|
|
476
|
+
raise VDSClientError(
|
|
477
|
+
"Retry loop exited without returning result",
|
|
478
|
+
context={"operation": operation_name},
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
|
|
482
|
+
"""Get a page by ID."""
|
|
483
|
+
|
|
484
|
+
def operation() -> dict[str, Any]:
|
|
485
|
+
params = {}
|
|
486
|
+
if expand:
|
|
487
|
+
params["expand"] = ",".join(expand)
|
|
488
|
+
result = self._client.get_page_by_id(page_id, expand=",".join(expand) if expand else None)
|
|
489
|
+
self._log.debug("page_retrieved", page_id=page_id)
|
|
490
|
+
return self._require_dict(result, "get_page")
|
|
491
|
+
|
|
492
|
+
return self._with_retry(operation, f"get_page {page_id}")
|
|
493
|
+
|
|
494
|
+
def search_cql(
|
|
495
|
+
self, cql: str, limit: int = 25, start: int = 0, expand: list[str] | None = None, **kwargs: Any
|
|
496
|
+
) -> dict[str, Any]:
|
|
497
|
+
"""Search using CQL.
|
|
498
|
+
|
|
499
|
+
Bypasses the ``atlassian`` SDK's ``cql()`` method entirely because it
|
|
500
|
+
uses ``if start:`` which is falsy for ``start=0``, causing the param to
|
|
501
|
+
be omitted. Instead we call ``rest/api/search`` directly with an
|
|
502
|
+
explicit ``params`` dict that always includes ``start``.
|
|
503
|
+
|
|
504
|
+
Without ``start`` in the request, some Confluence Server versions respond
|
|
505
|
+
with ``java.lang.IllegalArgumentException: parameters should not be empty``.
|
|
506
|
+
"""
|
|
507
|
+
|
|
508
|
+
def operation() -> dict[str, Any]:
|
|
509
|
+
params: dict[str, Any] = {"cql": cql, "start": int(start)}
|
|
510
|
+
if limit is not None:
|
|
511
|
+
params["limit"] = int(limit)
|
|
512
|
+
if expand:
|
|
513
|
+
params["expand"] = ",".join(expand)
|
|
514
|
+
params.update(kwargs)
|
|
515
|
+
result = self._client.get("rest/api/search", params=params)
|
|
516
|
+
self._log.debug("cql_search_completed", cql=cql, limit=limit, start=start)
|
|
517
|
+
return self._require_dict(result, "search_cql")
|
|
518
|
+
|
|
519
|
+
return self._with_retry(operation, f"cql_search {cql}")
|
|
520
|
+
|
|
521
|
+
def probe_reachable(self) -> dict[str, Any]:
|
|
522
|
+
"""Run a minimal SDK-backed reachability probe against the selected base URL.
|
|
523
|
+
|
|
524
|
+
The probe intentionally uses the same REST search path as normal CQL
|
|
525
|
+
reads so Phase 153's explicit ``start=0`` handling stays exercised.
|
|
526
|
+
Callers should treat success as "the configured Confluence base URL
|
|
527
|
+
answered an authenticated SDK request", not as a guarantee that every
|
|
528
|
+
later content operation will be fast.
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
return self.search_cql("type=page", limit=1, start=0)
|
|
532
|
+
|
|
533
|
+
def cql_advanced(
|
|
534
|
+
self,
|
|
535
|
+
cql: str,
|
|
536
|
+
*,
|
|
537
|
+
limit: int = 25,
|
|
538
|
+
start: int = 0,
|
|
539
|
+
expand: list[str] | None = None,
|
|
540
|
+
excerpt: str | None = None,
|
|
541
|
+
**kwargs: Any,
|
|
542
|
+
) -> dict[str, Any]:
|
|
543
|
+
"""Advanced CQL search with additional options.
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
cql: CQL query string
|
|
547
|
+
limit: Maximum number of results (default: 25)
|
|
548
|
+
start: Start index for pagination (default: 0)
|
|
549
|
+
expand: Optional list of fields to expand
|
|
550
|
+
excerpt: Optional excerpt strategy (e.g., "highlighted", "indexed")
|
|
551
|
+
**kwargs: Additional options passed to SDK cql() method
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
def operation() -> dict[str, Any]:
|
|
555
|
+
# Bypass self._client.cql() which has `if start:` falsy check.
|
|
556
|
+
params: dict[str, Any] = {"cql": cql, "start": int(start)}
|
|
557
|
+
if limit is not None:
|
|
558
|
+
params["limit"] = int(limit)
|
|
559
|
+
expand_str = ",".join(expand) if expand else None
|
|
560
|
+
if expand_str:
|
|
561
|
+
params["expand"] = expand_str
|
|
562
|
+
if excerpt:
|
|
563
|
+
params["excerpt"] = excerpt
|
|
564
|
+
params.update(kwargs)
|
|
565
|
+
result = self._client.get("rest/api/search", params=params)
|
|
566
|
+
self._log.debug("cql_advanced_search_completed", cql=cql, limit=limit, start=start)
|
|
567
|
+
return self._require_dict(result, "cql_advanced")
|
|
568
|
+
|
|
569
|
+
return self._with_retry(operation, f"cql_advanced_search {cql}")
|
|
570
|
+
|
|
571
|
+
def search_by_space_and_type(
|
|
572
|
+
self,
|
|
573
|
+
space_key: str | None = None,
|
|
574
|
+
content_type: str | None = None,
|
|
575
|
+
*,
|
|
576
|
+
limit: int = 25,
|
|
577
|
+
start: int = 0,
|
|
578
|
+
expand: list[str] | None = None,
|
|
579
|
+
last_modified_after: datetime | str | None = None,
|
|
580
|
+
overlap_minutes: int = 0,
|
|
581
|
+
**kwargs: Any,
|
|
582
|
+
) -> dict[str, Any]:
|
|
583
|
+
"""Search by space and content type (helper method that builds CQL).
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
space_key: Optional space key to filter by
|
|
587
|
+
content_type: Optional content type (e.g., "page", "blogpost", "comment")
|
|
588
|
+
limit: Maximum number of results (default: 25)
|
|
589
|
+
start: Start index for pagination (default: 0)
|
|
590
|
+
expand: Optional list of fields to expand
|
|
591
|
+
last_modified_after: Optional high-water-mark timestamp for incremental crawl
|
|
592
|
+
overlap_minutes: Optional overlap buffer minutes for high-water-mark queries
|
|
593
|
+
**kwargs: Additional options passed to CQL search
|
|
594
|
+
"""
|
|
595
|
+
cql = _build_space_type_lastmodified_cql(
|
|
596
|
+
space_key=space_key,
|
|
597
|
+
content_type=content_type,
|
|
598
|
+
last_modified_after=last_modified_after,
|
|
599
|
+
overlap_minutes=overlap_minutes,
|
|
600
|
+
)
|
|
601
|
+
return self.cql_advanced(cql, limit=limit, start=start, expand=expand, **kwargs)
|
|
602
|
+
|
|
603
|
+
def create_page(
|
|
604
|
+
self, space_key: str, title: str, body: str, parent_id: str | None = None, **kwargs: Any
|
|
605
|
+
) -> dict[str, Any]:
|
|
606
|
+
"""Create a new page."""
|
|
607
|
+
|
|
608
|
+
def operation() -> dict[str, Any]:
|
|
609
|
+
call_kwargs: dict[str, Any] = {}
|
|
610
|
+
if parent_id is not None:
|
|
611
|
+
call_kwargs["parent_id"] = parent_id
|
|
612
|
+
for key, value in kwargs.items():
|
|
613
|
+
if key in self._create_params and value is not None:
|
|
614
|
+
call_kwargs[key] = value
|
|
615
|
+
result = self._client.create_page(space=space_key, title=title, body=body, **call_kwargs)
|
|
616
|
+
payload = self._require_dict(result, "create_page")
|
|
617
|
+
self._log.info("page_created", space_key=space_key, title=title, page_id=payload.get("id"))
|
|
618
|
+
return payload
|
|
619
|
+
|
|
620
|
+
return self._with_retry(operation, f"create_page {title}")
|
|
621
|
+
|
|
622
|
+
def update_page(
|
|
623
|
+
self,
|
|
624
|
+
page_id: str,
|
|
625
|
+
title: str,
|
|
626
|
+
body: str | None = None,
|
|
627
|
+
version: int | None = None,
|
|
628
|
+
parent_id: str | None = None,
|
|
629
|
+
**kwargs: Any,
|
|
630
|
+
) -> dict[str, Any]:
|
|
631
|
+
"""Update an existing page."""
|
|
632
|
+
|
|
633
|
+
def operation() -> dict[str, Any]:
|
|
634
|
+
call_kwargs: dict[str, Any] = {}
|
|
635
|
+
for key, value in kwargs.items():
|
|
636
|
+
if key in self._update_params and value is not None:
|
|
637
|
+
call_kwargs[key] = value
|
|
638
|
+
if version is not None and "version" in self._update_params:
|
|
639
|
+
call_kwargs["version"] = version
|
|
640
|
+
if parent_id is not None and "parent_id" in self._update_params:
|
|
641
|
+
call_kwargs["parent_id"] = parent_id
|
|
642
|
+
result = self._client.update_page(page_id, title, body, **call_kwargs)
|
|
643
|
+
self._log.info("page_updated", page_id=page_id, title=title)
|
|
644
|
+
return self._require_dict(result, "update_page")
|
|
645
|
+
|
|
646
|
+
return self._with_retry(operation, f"update_page {page_id}")
|
|
647
|
+
|
|
648
|
+
def delete_page(self, page_id: str) -> None:
|
|
649
|
+
"""Delete a page."""
|
|
650
|
+
if self._degraded:
|
|
651
|
+
raise ConfluenceDegradedWriteError(f"delete_page {page_id}")
|
|
652
|
+
try:
|
|
653
|
+
# Use remove_page method from atlassian library
|
|
654
|
+
self._client.remove_page(page_id=page_id)
|
|
655
|
+
self._log.info("page_deleted", page_id=page_id)
|
|
656
|
+
|
|
657
|
+
except Exception as exc:
|
|
658
|
+
# Check if it's a permission issue
|
|
659
|
+
if "Unable to trash content" in str(exc) or "Unable to purge content" in str(exc):
|
|
660
|
+
self._log.warning("page_delete_permission_denied", page_id=page_id, error=str(exc))
|
|
661
|
+
raise ClientPermissionError(
|
|
662
|
+
f"Permission denied when deleting page {page_id}. The page may be restricted or you may not have delete permissions.",
|
|
663
|
+
context={"page_id": page_id, "original_error": str(exc)},
|
|
664
|
+
) from exc
|
|
665
|
+
else:
|
|
666
|
+
self._handle_api_error(exc, f"delete_page {page_id}")
|
|
667
|
+
|
|
668
|
+
def request(self, method: str, path: str, **kwargs: Any) -> Any:
|
|
669
|
+
"""Make a raw HTTP request using the atlassian client."""
|
|
670
|
+
try:
|
|
671
|
+
# Use the underlying request method from atlassian library
|
|
672
|
+
response = self._client.request(method=method, path=path, **kwargs)
|
|
673
|
+
self._log.debug("http_request", method=method, path=path)
|
|
674
|
+
return response
|
|
675
|
+
|
|
676
|
+
except Exception as exc:
|
|
677
|
+
self._handle_api_error(exc, f"request {method} {path}")
|
|
678
|
+
raise
|
|
679
|
+
|
|
680
|
+
def add_attachment(
|
|
681
|
+
self,
|
|
682
|
+
page_id: str,
|
|
683
|
+
filename: str,
|
|
684
|
+
content: bytes,
|
|
685
|
+
content_type: str | None = None,
|
|
686
|
+
*,
|
|
687
|
+
comment: str | None = None,
|
|
688
|
+
) -> dict[str, Any]:
|
|
689
|
+
"""Add an attachment to a page.
|
|
690
|
+
|
|
691
|
+
According to atlassian-python-api docs, attach_content automatically updates
|
|
692
|
+
if the file exists, versioning the new file and keeping the old one.
|
|
693
|
+
See: https://atlassian-python-api.readthedocs.io/confluence.html#
|
|
694
|
+
"""
|
|
695
|
+
|
|
696
|
+
def operation() -> dict[str, Any]:
|
|
697
|
+
# Use attach_content from the official SDK (handles create vs update internally)
|
|
698
|
+
result = self._client.attach_content(
|
|
699
|
+
content=content,
|
|
700
|
+
name=filename,
|
|
701
|
+
content_type=content_type or "application/octet-stream",
|
|
702
|
+
page_id=page_id,
|
|
703
|
+
comment=comment,
|
|
704
|
+
)
|
|
705
|
+
self._log.info("attachment_added", page_id=page_id, filename=filename)
|
|
706
|
+
return self._coerce_dict(result)
|
|
707
|
+
|
|
708
|
+
return self._with_retry(operation, f"add_attachment {filename}")
|
|
709
|
+
|
|
710
|
+
def get_space(self, space_key: str) -> dict[str, Any]:
|
|
711
|
+
"""Get space information."""
|
|
712
|
+
try:
|
|
713
|
+
result = self._client.get_space(space_key)
|
|
714
|
+
self._log.debug("space_retrieved", space_key=space_key)
|
|
715
|
+
return self._require_dict(result, "get_space")
|
|
716
|
+
|
|
717
|
+
except Exception as exc:
|
|
718
|
+
self._handle_api_error(exc, f"get_space {space_key}")
|
|
719
|
+
raise
|
|
720
|
+
|
|
721
|
+
def get_space_permissions(self, space_key: str) -> dict[str, Any]:
|
|
722
|
+
"""Get all permissions configured for a space."""
|
|
723
|
+
|
|
724
|
+
def operation() -> dict[str, Any]:
|
|
725
|
+
result = self._client.get_space_permissions(space_key)
|
|
726
|
+
count = len(result) if isinstance(result, (list, tuple)) else len(result or {})
|
|
727
|
+
self._log.debug("space_permissions_retrieved", space_key=space_key, count=count)
|
|
728
|
+
return self._coerce_dict(result)
|
|
729
|
+
|
|
730
|
+
return self._with_retry(operation, f"get_space_permissions {space_key}")
|
|
731
|
+
|
|
732
|
+
def set_permissions_to_user_for_space(
|
|
733
|
+
self,
|
|
734
|
+
space_key: str,
|
|
735
|
+
user_key: str,
|
|
736
|
+
*,
|
|
737
|
+
operations: list[str] | None = None,
|
|
738
|
+
) -> dict[str, Any]:
|
|
739
|
+
"""Grant permissions to a specific user for a space."""
|
|
740
|
+
|
|
741
|
+
def operation() -> dict[str, Any]:
|
|
742
|
+
result = self._client.set_permissions_to_user_for_space(
|
|
743
|
+
space_key,
|
|
744
|
+
user_key,
|
|
745
|
+
operations=operations,
|
|
746
|
+
)
|
|
747
|
+
self._log.info(
|
|
748
|
+
"space_permissions_user_set",
|
|
749
|
+
space_key=space_key,
|
|
750
|
+
user_key=user_key,
|
|
751
|
+
operations=operations or [],
|
|
752
|
+
)
|
|
753
|
+
return self._coerce_dict(result)
|
|
754
|
+
|
|
755
|
+
return self._with_retry(operation, f"set_permissions_to_user_for_space {space_key} {user_key}")
|
|
756
|
+
|
|
757
|
+
def set_permissions_to_group_for_space(
|
|
758
|
+
self,
|
|
759
|
+
space_key: str,
|
|
760
|
+
group_name: str,
|
|
761
|
+
*,
|
|
762
|
+
operations: list[str] | None = None,
|
|
763
|
+
) -> dict[str, Any]:
|
|
764
|
+
"""Grant permissions to a group for a space."""
|
|
765
|
+
|
|
766
|
+
def operation() -> dict[str, Any]:
|
|
767
|
+
result = self._client.set_permissions_to_group_for_space(
|
|
768
|
+
space_key,
|
|
769
|
+
group_name,
|
|
770
|
+
operations=operations,
|
|
771
|
+
)
|
|
772
|
+
self._log.info(
|
|
773
|
+
"space_permissions_group_set",
|
|
774
|
+
space_key=space_key,
|
|
775
|
+
group_name=group_name,
|
|
776
|
+
operations=operations or [],
|
|
777
|
+
)
|
|
778
|
+
return self._coerce_dict(result)
|
|
779
|
+
|
|
780
|
+
return self._with_retry(operation, f"set_permissions_to_group_for_space {space_key} {group_name}")
|
|
781
|
+
|
|
782
|
+
def set_permissions_to_anonymous_for_space(
|
|
783
|
+
self,
|
|
784
|
+
space_key: str,
|
|
785
|
+
*,
|
|
786
|
+
operations: list[str] | None = None,
|
|
787
|
+
) -> dict[str, Any]:
|
|
788
|
+
"""Grant permissions to anonymous users for a space."""
|
|
789
|
+
|
|
790
|
+
def operation() -> dict[str, Any]:
|
|
791
|
+
result = self._client.set_permissions_to_anonymous_for_space(space_key, operations=operations)
|
|
792
|
+
self._log.info(
|
|
793
|
+
"space_permissions_anonymous_set",
|
|
794
|
+
space_key=space_key,
|
|
795
|
+
operations=operations or [],
|
|
796
|
+
)
|
|
797
|
+
return self._coerce_dict(result)
|
|
798
|
+
|
|
799
|
+
return self._with_retry(operation, f"set_permissions_to_anonymous_for_space {space_key}")
|
|
800
|
+
|
|
801
|
+
def set_permissions_to_multiple_items_for_space(
|
|
802
|
+
self,
|
|
803
|
+
space_key: str,
|
|
804
|
+
items: list[dict[str, Any]],
|
|
805
|
+
) -> dict[str, Any]:
|
|
806
|
+
"""Grant permissions to multiple users/groups in a single request."""
|
|
807
|
+
|
|
808
|
+
if not items:
|
|
809
|
+
raise VDSClientError(
|
|
810
|
+
"Bulk permissions payload is empty",
|
|
811
|
+
context={"space_key": space_key},
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
def _normalize_item(item: dict[str, Any], index: int) -> tuple[str, str | None, list[str]]:
|
|
815
|
+
raw_operations = item.get("operations")
|
|
816
|
+
if raw_operations is None:
|
|
817
|
+
operations: list[str] = []
|
|
818
|
+
elif isinstance(raw_operations, list) and all(isinstance(op, str) for op in raw_operations):
|
|
819
|
+
operations = [op.strip() for op in raw_operations if op.strip()]
|
|
820
|
+
else:
|
|
821
|
+
raise VDSClientError(
|
|
822
|
+
"Invalid operations in bulk permissions item",
|
|
823
|
+
context={"space_key": space_key, "index": index},
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
target_type: str | None = None
|
|
827
|
+
target_value: str | None = None
|
|
828
|
+
|
|
829
|
+
target = item.get("target")
|
|
830
|
+
if isinstance(target, dict):
|
|
831
|
+
raw_type = target.get("type")
|
|
832
|
+
if isinstance(raw_type, str):
|
|
833
|
+
target_type = raw_type.strip().lower()
|
|
834
|
+
raw_value = target.get("value")
|
|
835
|
+
if raw_value is None:
|
|
836
|
+
raw_value = target.get("key")
|
|
837
|
+
if raw_value is None:
|
|
838
|
+
raw_value = target.get("name")
|
|
839
|
+
if raw_value is not None:
|
|
840
|
+
target_value = str(raw_value).strip()
|
|
841
|
+
|
|
842
|
+
if target_type is None and isinstance(item.get("target_type"), str):
|
|
843
|
+
target_type = str(item["target_type"]).strip().lower()
|
|
844
|
+
if target_type is None and isinstance(item.get("user_key"), str):
|
|
845
|
+
target_type = "user"
|
|
846
|
+
target_value = item["user_key"].strip()
|
|
847
|
+
if target_type is None and isinstance(item.get("group_name"), str):
|
|
848
|
+
target_type = "group"
|
|
849
|
+
target_value = item["group_name"].strip()
|
|
850
|
+
if target_type is None and item.get("anonymous") is True:
|
|
851
|
+
target_type = "anonymous"
|
|
852
|
+
|
|
853
|
+
if target_type not in {"user", "group", "anonymous"}:
|
|
854
|
+
raise VDSClientError(
|
|
855
|
+
"Bulk permission item target is invalid",
|
|
856
|
+
context={"space_key": space_key, "index": index},
|
|
857
|
+
)
|
|
858
|
+
if target_type in {"user", "group"} and not target_value:
|
|
859
|
+
raise VDSClientError(
|
|
860
|
+
"Bulk permission item requires target value for user/group",
|
|
861
|
+
context={"space_key": space_key, "index": index, "target_type": target_type},
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
return target_type, target_value, operations
|
|
865
|
+
|
|
866
|
+
normalized_items = [_normalize_item(item, index) for index, item in enumerate(items)]
|
|
867
|
+
|
|
868
|
+
def operation() -> dict[str, Any]:
|
|
869
|
+
bulk_method = getattr(self._client, "set_permissions_to_multiple_items_for_space", None)
|
|
870
|
+
bulk_params = set(self._safe_signature_params(bulk_method)) if callable(bulk_method) else set()
|
|
871
|
+
|
|
872
|
+
mode: str
|
|
873
|
+
if callable(bulk_method) and "items" in bulk_params:
|
|
874
|
+
result = bulk_method(space_key, items=items)
|
|
875
|
+
mode = "native-items"
|
|
876
|
+
elif callable(bulk_method) and ("user_key" in bulk_params or "group_name" in bulk_params):
|
|
877
|
+
compat_results: list[dict[str, Any]] = []
|
|
878
|
+
for target_type, target_value, operations in normalized_items:
|
|
879
|
+
kwargs: dict[str, Any] = {"operations": operations}
|
|
880
|
+
if target_type == "user":
|
|
881
|
+
kwargs["user_key"] = target_value
|
|
882
|
+
elif target_type == "group":
|
|
883
|
+
kwargs["group_name"] = target_value
|
|
884
|
+
compat_results.append(self._coerce_dict(bulk_method(space_key, **kwargs)))
|
|
885
|
+
result = {
|
|
886
|
+
"mode": "legacy-bulk",
|
|
887
|
+
"items_processed": len(normalized_items),
|
|
888
|
+
"results": compat_results,
|
|
889
|
+
}
|
|
890
|
+
mode = "legacy-bulk"
|
|
891
|
+
else:
|
|
892
|
+
compat_results = []
|
|
893
|
+
for target_type, target_value, operations in normalized_items:
|
|
894
|
+
if target_type == "user":
|
|
895
|
+
compat_results.append(
|
|
896
|
+
self._coerce_dict(
|
|
897
|
+
self._client.set_permissions_to_user_for_space( # type: ignore[attr-defined]
|
|
898
|
+
space_key,
|
|
899
|
+
target_value,
|
|
900
|
+
operations=operations,
|
|
901
|
+
)
|
|
902
|
+
)
|
|
903
|
+
)
|
|
904
|
+
elif target_type == "group":
|
|
905
|
+
compat_results.append(
|
|
906
|
+
self._coerce_dict(
|
|
907
|
+
self._client.set_permissions_to_group_for_space( # type: ignore[attr-defined]
|
|
908
|
+
space_key,
|
|
909
|
+
target_value,
|
|
910
|
+
operations=operations,
|
|
911
|
+
)
|
|
912
|
+
)
|
|
913
|
+
)
|
|
914
|
+
else:
|
|
915
|
+
compat_results.append(
|
|
916
|
+
self._coerce_dict(
|
|
917
|
+
self._client.set_permissions_to_anonymous_for_space( # type: ignore[attr-defined]
|
|
918
|
+
space_key,
|
|
919
|
+
operations=operations,
|
|
920
|
+
)
|
|
921
|
+
)
|
|
922
|
+
)
|
|
923
|
+
result = {
|
|
924
|
+
"mode": "expanded-fallback",
|
|
925
|
+
"items_processed": len(normalized_items),
|
|
926
|
+
"results": compat_results,
|
|
927
|
+
}
|
|
928
|
+
mode = "expanded-fallback"
|
|
929
|
+
|
|
930
|
+
self._log.info(
|
|
931
|
+
"space_permissions_bulk_set",
|
|
932
|
+
space_key=space_key,
|
|
933
|
+
items=len(normalized_items),
|
|
934
|
+
mode=mode,
|
|
935
|
+
)
|
|
936
|
+
return self._coerce_dict(result)
|
|
937
|
+
|
|
938
|
+
return self._with_retry(operation, f"set_permissions_to_multiple_items_for_space {space_key}")
|
|
939
|
+
|
|
940
|
+
def remove_permissions_from_user_for_space(self, space_key: str, user_key: str) -> dict[str, Any]:
|
|
941
|
+
"""Remove all permissions granted to a user for a space."""
|
|
942
|
+
|
|
943
|
+
def operation() -> dict[str, Any]:
|
|
944
|
+
result = self._client.remove_permissions_from_user_for_space(space_key, user_key)
|
|
945
|
+
self._log.info("space_permissions_user_removed", space_key=space_key, user_key=user_key)
|
|
946
|
+
return self._coerce_dict(result)
|
|
947
|
+
|
|
948
|
+
return self._with_retry(operation, f"remove_permissions_from_user_for_space {space_key} {user_key}")
|
|
949
|
+
|
|
950
|
+
def remove_permissions_from_group_for_space(self, space_key: str, group_name: str) -> dict[str, Any]:
|
|
951
|
+
"""Remove all permissions granted to a group for a space."""
|
|
952
|
+
|
|
953
|
+
def operation() -> dict[str, Any]:
|
|
954
|
+
result = self._client.remove_permissions_from_group_for_space(space_key, group_name)
|
|
955
|
+
self._log.info("space_permissions_group_removed", space_key=space_key, group_name=group_name)
|
|
956
|
+
return self._coerce_dict(result)
|
|
957
|
+
|
|
958
|
+
return self._with_retry(operation, f"remove_permissions_from_group_for_space {space_key} {group_name}")
|
|
959
|
+
|
|
960
|
+
def remove_permissions_from_anonymous_for_space(self, space_key: str) -> dict[str, Any]:
|
|
961
|
+
"""Remove permissions granted to anonymous users for a space."""
|
|
962
|
+
|
|
963
|
+
def operation() -> dict[str, Any]:
|
|
964
|
+
result = self._client.remove_permissions_from_anonymous_for_space(space_key)
|
|
965
|
+
self._log.info("space_permissions_anonymous_removed", space_key=space_key)
|
|
966
|
+
return self._coerce_dict(result)
|
|
967
|
+
|
|
968
|
+
return self._with_retry(operation, f"remove_permissions_from_anonymous_for_space {space_key}")
|
|
969
|
+
|
|
970
|
+
# --- User & Group Management ---
|
|
971
|
+
|
|
972
|
+
def get_all_groups(self, start: int = 0, limit: int = 1000) -> list[dict[str, Any]]:
|
|
973
|
+
"""Get all groups in Confluence."""
|
|
974
|
+
|
|
975
|
+
def operation() -> list[dict[str, Any]]:
|
|
976
|
+
result = self._client.get_all_groups(start=start, limit=limit)
|
|
977
|
+
self._log.debug(
|
|
978
|
+
"groups_retrieved", count=len(result) if isinstance(result, list) else 0, start=start, limit=limit
|
|
979
|
+
)
|
|
980
|
+
return result if isinstance(result, list) else []
|
|
981
|
+
|
|
982
|
+
return self._with_retry(operation, f"get_all_groups start={start} limit={limit}")
|
|
983
|
+
|
|
984
|
+
def get_group_members(self, group_name: str, start: int = 0, limit: int = 1000) -> list[dict[str, Any]]:
|
|
985
|
+
"""Get members of a group."""
|
|
986
|
+
|
|
987
|
+
def operation() -> list[dict[str, Any]]:
|
|
988
|
+
result = self._client.get_group_members(group_name, start=start, limit=limit)
|
|
989
|
+
self._log.debug(
|
|
990
|
+
"group_members_retrieved", group_name=group_name, count=len(result) if isinstance(result, list) else 0
|
|
991
|
+
)
|
|
992
|
+
return result if isinstance(result, list) else []
|
|
993
|
+
|
|
994
|
+
return self._with_retry(operation, f"get_group_members {group_name} start={start} limit={limit}")
|
|
995
|
+
|
|
996
|
+
def get_user_details_by_username(self, username: str, expand: str | None = None) -> dict[str, Any]:
|
|
997
|
+
"""Get user details by username."""
|
|
998
|
+
|
|
999
|
+
def operation() -> dict[str, Any]:
|
|
1000
|
+
result = self._client.get_user_details_by_username(username, expand=expand)
|
|
1001
|
+
self._log.debug("user_retrieved_by_username", username=username)
|
|
1002
|
+
return self._require_dict(result, "get_user_details_by_username")
|
|
1003
|
+
|
|
1004
|
+
return self._with_retry(operation, f"get_user_details_by_username {username}")
|
|
1005
|
+
|
|
1006
|
+
def get_user_details_by_userkey(self, userkey: str, expand: str | None = None) -> dict[str, Any]:
|
|
1007
|
+
"""Get user details by user key."""
|
|
1008
|
+
|
|
1009
|
+
def operation() -> dict[str, Any]:
|
|
1010
|
+
result = self._client.get_user_details_by_userkey(userkey, expand=expand)
|
|
1011
|
+
self._log.debug("user_retrieved_by_userkey", userkey=userkey)
|
|
1012
|
+
return self._require_dict(result, "get_user_details_by_userkey")
|
|
1013
|
+
|
|
1014
|
+
return self._with_retry(operation, f"get_user_details_by_userkey {userkey}")
|
|
1015
|
+
|
|
1016
|
+
def change_user_password(self, username: str, password: str) -> None:
|
|
1017
|
+
"""Change user password."""
|
|
1018
|
+
|
|
1019
|
+
def operation() -> None:
|
|
1020
|
+
self._client.change_user_password(username, password)
|
|
1021
|
+
self._log.info("user_password_changed", username=username)
|
|
1022
|
+
|
|
1023
|
+
return self._with_retry(operation, f"change_user_password {username}")
|
|
1024
|
+
|
|
1025
|
+
def add_user_to_group(self, username: str, group_name: str) -> None:
|
|
1026
|
+
"""Add user to group."""
|
|
1027
|
+
|
|
1028
|
+
def operation() -> None:
|
|
1029
|
+
self._client.add_user_to_group(username, group_name)
|
|
1030
|
+
self._log.info("user_added_to_group", username=username, group_name=group_name)
|
|
1031
|
+
|
|
1032
|
+
return self._with_retry(operation, f"add_user_to_group {username} {group_name}")
|
|
1033
|
+
|
|
1034
|
+
def remove_user_from_group(self, username: str, group_name: str) -> None:
|
|
1035
|
+
"""Remove user from group."""
|
|
1036
|
+
|
|
1037
|
+
def operation():
|
|
1038
|
+
self._client.remove_user_from_group(username, group_name)
|
|
1039
|
+
self._log.info("user_removed_from_group", username=username, group_name=group_name)
|
|
1040
|
+
|
|
1041
|
+
return self._with_retry(operation, f"remove_user_from_group {username} {group_name}")
|
|
1042
|
+
|
|
1043
|
+
# --- Export Operations ---
|
|
1044
|
+
|
|
1045
|
+
def export_page(self, page_id: str, api_version: str | None = None) -> bytes:
|
|
1046
|
+
"""Export page as PDF.
|
|
1047
|
+
|
|
1048
|
+
Note: The SDK auto-detects Cloud/Server mode based on client initialization.
|
|
1049
|
+
The api_version parameter is accepted for CLI compatibility but not passed to SDK.
|
|
1050
|
+
"""
|
|
1051
|
+
|
|
1052
|
+
def operation():
|
|
1053
|
+
# SDK auto-detects Cloud/Server mode, so we don't pass api_version
|
|
1054
|
+
# The parameter is kept for CLI compatibility but ignored here
|
|
1055
|
+
result = self._client.export_page(page_id) # type: ignore[call-arg]
|
|
1056
|
+
detected_mode = "cloud" if self._is_cloud else "server"
|
|
1057
|
+
self._log.info("page_exported", page_id=page_id, detected_mode=detected_mode)
|
|
1058
|
+
# SDK returns bytes or file path; ensure we return bytes
|
|
1059
|
+
if isinstance(result, bytes):
|
|
1060
|
+
return self._coerce_bytes(result)
|
|
1061
|
+
elif isinstance(result, str):
|
|
1062
|
+
# If it's a file path, read it
|
|
1063
|
+
with open(result, "rb") as f:
|
|
1064
|
+
return f.read()
|
|
1065
|
+
else:
|
|
1066
|
+
raise VDSClientError(
|
|
1067
|
+
f"Unexpected export_page return type: {type(result)}", context={"page_id": page_id}
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
return self._with_retry(operation, f"export_page {page_id}")
|
|
1071
|
+
|
|
1072
|
+
def get_space_export(self, space_key: str, export_type: str) -> str:
|
|
1073
|
+
"""Get space export download URL."""
|
|
1074
|
+
|
|
1075
|
+
def operation() -> str:
|
|
1076
|
+
result = self._client.get_space_export(space_key, export_type)
|
|
1077
|
+
self._log.info("space_export_url_retrieved", space_key=space_key, export_type=export_type)
|
|
1078
|
+
return result if isinstance(result, str) else str(result)
|
|
1079
|
+
|
|
1080
|
+
return self._with_retry(operation, f"get_space_export {space_key} {export_type}")
|
|
1081
|
+
|
|
1082
|
+
# --- Draft Management (Server-only) ---
|
|
1083
|
+
|
|
1084
|
+
def get_draft_page_by_id(self, page_id: str) -> dict[str, Any]:
|
|
1085
|
+
"""Get draft page by ID (Server-only)."""
|
|
1086
|
+
if self._is_cloud:
|
|
1087
|
+
raise VDSClientError(
|
|
1088
|
+
"get_draft_page_by_id is only available in Confluence Server/Data Center mode",
|
|
1089
|
+
context={"page_id": page_id},
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
def operation() -> dict[str, Any]:
|
|
1093
|
+
result = self._client.get_draft_page_by_id(page_id) # type: ignore[attr-defined]
|
|
1094
|
+
self._log.debug("draft_page_retrieved", page_id=page_id)
|
|
1095
|
+
return self._require_dict(result, "get_draft_page_by_id")
|
|
1096
|
+
|
|
1097
|
+
return self._with_retry(operation, f"get_draft_page_by_id {page_id}")
|
|
1098
|
+
|
|
1099
|
+
def get_all_draft_pages_from_space(self, space_key: str, limit: int = 25) -> list[dict[str, Any]]:
|
|
1100
|
+
"""Get all draft pages from a space (Server-only)."""
|
|
1101
|
+
if self._is_cloud:
|
|
1102
|
+
raise VDSClientError(
|
|
1103
|
+
"get_all_draft_pages_from_space is only available in Confluence Server/Data Center mode",
|
|
1104
|
+
context={"space_key": space_key},
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1107
|
+
def operation() -> list[dict[str, Any]]:
|
|
1108
|
+
result = self._client.get_all_draft_pages_from_space(space_key, limit=limit) # type: ignore[attr-defined]
|
|
1109
|
+
self._log.debug(
|
|
1110
|
+
"draft_pages_retrieved", space_key=space_key, count=len(result) if isinstance(result, list) else 0
|
|
1111
|
+
)
|
|
1112
|
+
return result if isinstance(result, list) else []
|
|
1113
|
+
|
|
1114
|
+
return self._with_retry(operation, f"get_all_draft_pages_from_space {space_key}")
|
|
1115
|
+
|
|
1116
|
+
def remove_page_as_draft(self, page_id: str) -> None:
|
|
1117
|
+
"""Remove page as draft (Server-only)."""
|
|
1118
|
+
if self._is_cloud:
|
|
1119
|
+
raise VDSClientError(
|
|
1120
|
+
"remove_page_as_draft is only available in Confluence Server/Data Center mode",
|
|
1121
|
+
context={"page_id": page_id},
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
def operation() -> None:
|
|
1125
|
+
self._client.remove_page_as_draft(page_id) # type: ignore[attr-defined]
|
|
1126
|
+
self._log.info("draft_page_removed", page_id=page_id)
|
|
1127
|
+
|
|
1128
|
+
return self._with_retry(operation, f"remove_page_as_draft {page_id}")
|
|
1129
|
+
|
|
1130
|
+
# --- Cache Management (Server-only) ---
|
|
1131
|
+
|
|
1132
|
+
def get_cache_statistics(self) -> dict[str, Any]:
|
|
1133
|
+
"""Get cache statistics (Server-only)."""
|
|
1134
|
+
if self._is_cloud:
|
|
1135
|
+
raise VDSClientError(
|
|
1136
|
+
"get_cache_statistics is only available in Confluence Server/Data Center mode",
|
|
1137
|
+
context={},
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1140
|
+
def operation():
|
|
1141
|
+
# SDK doesn't have get_cache_statistics, use REST API directly
|
|
1142
|
+
# Cache statistics endpoint: /rest/cache/1.0/stats
|
|
1143
|
+
response = self.request(method="GET", path="/rest/cache/1.0/stats")
|
|
1144
|
+
# SDK request method returns response object, extract JSON
|
|
1145
|
+
if hasattr(response, "json"):
|
|
1146
|
+
result = response.json()
|
|
1147
|
+
else:
|
|
1148
|
+
result = response # Already JSON dict
|
|
1149
|
+
self._log.debug("cache_statistics_retrieved")
|
|
1150
|
+
return self._coerce_dict(result)
|
|
1151
|
+
|
|
1152
|
+
return self._with_retry(operation, "get_cache_statistics")
|
|
1153
|
+
|
|
1154
|
+
def flush_cache(self, cache_name: str | None = None) -> None:
|
|
1155
|
+
"""Flush cache (Server-only).
|
|
1156
|
+
|
|
1157
|
+
Args:
|
|
1158
|
+
cache_name: Optional cache name to flush specific cache. If None, flushes all caches.
|
|
1159
|
+
"""
|
|
1160
|
+
if self._is_cloud:
|
|
1161
|
+
raise VDSClientError(
|
|
1162
|
+
"flush_cache is only available in Confluence Server/Data Center mode",
|
|
1163
|
+
context={"cache_name": cache_name},
|
|
1164
|
+
)
|
|
1165
|
+
|
|
1166
|
+
def operation() -> None:
|
|
1167
|
+
if cache_name:
|
|
1168
|
+
# Flush specific cache package
|
|
1169
|
+
self._client.clean_package_cache(cache_name=cache_name) # type: ignore[attr-defined]
|
|
1170
|
+
self._log.info("cache_flushed", cache_name=cache_name)
|
|
1171
|
+
else:
|
|
1172
|
+
# Flush all caches
|
|
1173
|
+
self._client.clean_all_caches() # type: ignore[attr-defined]
|
|
1174
|
+
self._log.info("all_caches_flushed")
|
|
1175
|
+
|
|
1176
|
+
return self._with_retry(operation, f"flush_cache {cache_name or 'all'}")
|
|
1177
|
+
|
|
1178
|
+
def get_cache_size(self) -> dict[str, Any]:
|
|
1179
|
+
"""Get cache size information (Server-only)."""
|
|
1180
|
+
if self._is_cloud:
|
|
1181
|
+
raise VDSClientError(
|
|
1182
|
+
"get_cache_size is only available in Confluence Server/Data Center mode",
|
|
1183
|
+
context={},
|
|
1184
|
+
)
|
|
1185
|
+
|
|
1186
|
+
def operation():
|
|
1187
|
+
# SDK doesn't have get_cache_size, use REST API directly
|
|
1188
|
+
# Cache size endpoint: /rest/cache/1.0/size
|
|
1189
|
+
response = self.request(method="GET", path="/rest/cache/1.0/size")
|
|
1190
|
+
# SDK request method returns response object, extract JSON
|
|
1191
|
+
if hasattr(response, "json"):
|
|
1192
|
+
result = response.json()
|
|
1193
|
+
else:
|
|
1194
|
+
result = response # Already JSON dict
|
|
1195
|
+
self._log.debug("cache_size_retrieved")
|
|
1196
|
+
return self._coerce_dict(result)
|
|
1197
|
+
|
|
1198
|
+
return self._with_retry(operation, "get_cache_size")
|
|
1199
|
+
|
|
1200
|
+
# --- Advanced Content Operations ---
|
|
1201
|
+
|
|
1202
|
+
def get_page_ancestors(self, page_id: str) -> list[dict[str, Any]]:
|
|
1203
|
+
"""Get page ancestors (parent pages)."""
|
|
1204
|
+
|
|
1205
|
+
def operation() -> list[dict[str, Any]]:
|
|
1206
|
+
result = self._client.get_page_ancestors(page_id) # type: ignore[attr-defined]
|
|
1207
|
+
self._log.debug(
|
|
1208
|
+
"page_ancestors_retrieved", page_id=page_id, count=len(result) if isinstance(result, list) else 0
|
|
1209
|
+
)
|
|
1210
|
+
return result if isinstance(result, list) else []
|
|
1211
|
+
|
|
1212
|
+
return self._with_retry(operation, f"get_page_ancestors {page_id}")
|
|
1213
|
+
|
|
1214
|
+
def move_page(
|
|
1215
|
+
self,
|
|
1216
|
+
page_id: str,
|
|
1217
|
+
target_title: str,
|
|
1218
|
+
position: str = "append",
|
|
1219
|
+
*,
|
|
1220
|
+
space_key: str | None = None,
|
|
1221
|
+
) -> dict[str, Any]:
|
|
1222
|
+
"""Move page to a new location."""
|
|
1223
|
+
|
|
1224
|
+
def operation() -> dict[str, Any]:
|
|
1225
|
+
move_page_method = getattr(self._client, "move_page", None)
|
|
1226
|
+
if not callable(move_page_method):
|
|
1227
|
+
raise VDSClientError("SDK does not support move_page")
|
|
1228
|
+
|
|
1229
|
+
move_params = set(self._safe_signature_params(move_page_method))
|
|
1230
|
+
if "space_key" in move_params and "page_id" in move_params:
|
|
1231
|
+
resolved_space_key = space_key
|
|
1232
|
+
if not resolved_space_key and hasattr(self._client, "get_page_space"):
|
|
1233
|
+
raw_space_key = self._client.get_page_space(page_id) # type: ignore[attr-defined]
|
|
1234
|
+
if isinstance(raw_space_key, str) and raw_space_key.strip():
|
|
1235
|
+
resolved_space_key = raw_space_key.strip()
|
|
1236
|
+
if not resolved_space_key:
|
|
1237
|
+
page = self._client.get_page_by_id(page_id, expand="space")
|
|
1238
|
+
if isinstance(page, dict):
|
|
1239
|
+
page_space = page.get("space")
|
|
1240
|
+
if isinstance(page_space, dict):
|
|
1241
|
+
raw_space_key = page_space.get("key")
|
|
1242
|
+
if raw_space_key:
|
|
1243
|
+
resolved_space_key = str(raw_space_key).strip()
|
|
1244
|
+
if not resolved_space_key:
|
|
1245
|
+
raise VDSClientError(
|
|
1246
|
+
"Unable to resolve space key for move_page",
|
|
1247
|
+
context={"page_id": page_id},
|
|
1248
|
+
)
|
|
1249
|
+
|
|
1250
|
+
legacy_position = {"before": "above", "after": "below"}.get(position, position)
|
|
1251
|
+
result = move_page_method(
|
|
1252
|
+
resolved_space_key,
|
|
1253
|
+
page_id,
|
|
1254
|
+
target_title=target_title,
|
|
1255
|
+
position=legacy_position,
|
|
1256
|
+
)
|
|
1257
|
+
self._log.info(
|
|
1258
|
+
"page_moved",
|
|
1259
|
+
page_id=page_id,
|
|
1260
|
+
target_title=target_title,
|
|
1261
|
+
position=position,
|
|
1262
|
+
resolved_position=legacy_position,
|
|
1263
|
+
space_key=resolved_space_key,
|
|
1264
|
+
move_contract="legacy",
|
|
1265
|
+
)
|
|
1266
|
+
return self._coerce_dict(result)
|
|
1267
|
+
|
|
1268
|
+
result = move_page_method(page_id, target_title, position=position)
|
|
1269
|
+
self._log.info(
|
|
1270
|
+
"page_moved",
|
|
1271
|
+
page_id=page_id,
|
|
1272
|
+
target_title=target_title,
|
|
1273
|
+
position=position,
|
|
1274
|
+
move_contract="modern",
|
|
1275
|
+
)
|
|
1276
|
+
return self._coerce_dict(result)
|
|
1277
|
+
|
|
1278
|
+
return self._with_retry(operation, f"move_page {page_id} {target_title}")
|
|
1279
|
+
|
|
1280
|
+
def get_tables_from_page(self, page_id: str) -> list[dict[str, Any]]:
|
|
1281
|
+
"""Extract tables from page."""
|
|
1282
|
+
|
|
1283
|
+
def operation() -> list[dict[str, Any]]:
|
|
1284
|
+
result = self._client.get_tables_from_page(page_id) # type: ignore[attr-defined]
|
|
1285
|
+
self._log.debug("tables_extracted", page_id=page_id, count=len(result) if isinstance(result, list) else 0)
|
|
1286
|
+
return result if isinstance(result, list) else []
|
|
1287
|
+
|
|
1288
|
+
return self._with_retry(operation, f"get_tables_from_page {page_id}")
|
|
1289
|
+
|
|
1290
|
+
def scrap_regex_from_page(self, page_id: str, regex: str) -> list[dict[str, Any]]:
|
|
1291
|
+
"""Extract regex matches from page."""
|
|
1292
|
+
|
|
1293
|
+
def operation() -> list[dict[str, Any]]:
|
|
1294
|
+
result = self._client.scrap_regex_from_page(page_id, regex) # type: ignore[attr-defined]
|
|
1295
|
+
self._log.debug(
|
|
1296
|
+
"regex_matches_extracted",
|
|
1297
|
+
page_id=page_id,
|
|
1298
|
+
pattern=regex,
|
|
1299
|
+
count=len(result) if isinstance(result, list) else 0,
|
|
1300
|
+
)
|
|
1301
|
+
return result if isinstance(result, list) else []
|
|
1302
|
+
|
|
1303
|
+
return self._with_retry(operation, f"scrap_regex_from_page {page_id} {regex}")
|
|
1304
|
+
|
|
1305
|
+
def get_all_restrictions_for_content(self, content_id: str) -> dict[str, Any]:
|
|
1306
|
+
"""Get all restrictions for content."""
|
|
1307
|
+
|
|
1308
|
+
def operation() -> dict[str, Any]:
|
|
1309
|
+
result = self._client.get_all_restrictions_for_content(content_id) # type: ignore[attr-defined]
|
|
1310
|
+
self._log.debug("content_restrictions_retrieved", content_id=content_id)
|
|
1311
|
+
return self._coerce_dict(result)
|
|
1312
|
+
|
|
1313
|
+
return self._with_retry(operation, f"get_all_restrictions_for_content {content_id}")
|
|
1314
|
+
|
|
1315
|
+
# --- History & Versioning ---
|
|
1316
|
+
|
|
1317
|
+
def history(self, page_id: str) -> dict[str, Any]:
|
|
1318
|
+
"""Get page history."""
|
|
1319
|
+
|
|
1320
|
+
def operation() -> dict[str, Any]:
|
|
1321
|
+
result = self._client.history(page_id) # type: ignore[attr-defined]
|
|
1322
|
+
self._log.debug("page_history_retrieved", page_id=page_id)
|
|
1323
|
+
return self._coerce_dict(result)
|
|
1324
|
+
|
|
1325
|
+
return self._with_retry(operation, f"history {page_id}")
|
|
1326
|
+
|
|
1327
|
+
def get_content_history_by_version_number(self, page_id: str, version: int) -> dict[str, Any]:
|
|
1328
|
+
"""Get specific version of content."""
|
|
1329
|
+
|
|
1330
|
+
def operation() -> dict[str, Any]:
|
|
1331
|
+
result = self._client.get_content_history_by_version_number(page_id, version) # type: ignore[attr-defined]
|
|
1332
|
+
self._log.debug("content_version_retrieved", page_id=page_id, version=version)
|
|
1333
|
+
return self._coerce_dict(result)
|
|
1334
|
+
|
|
1335
|
+
return self._with_retry(operation, f"get_content_history_by_version_number {page_id} {version}")
|
|
1336
|
+
|
|
1337
|
+
def remove_content_history(self, page_id: str, version: int) -> None:
|
|
1338
|
+
"""Remove content history (experimental)."""
|
|
1339
|
+
|
|
1340
|
+
def operation():
|
|
1341
|
+
self._client.remove_content_history(page_id, version) # type: ignore[attr-defined]
|
|
1342
|
+
self._log.info("content_history_removed", page_id=page_id, version=version)
|
|
1343
|
+
|
|
1344
|
+
return self._with_retry(operation, f"remove_content_history {page_id} {version}")
|
|
1345
|
+
|
|
1346
|
+
def archive_space(self, space_key: str) -> dict[str, Any]:
|
|
1347
|
+
"""Archive a Confluence space."""
|
|
1348
|
+
|
|
1349
|
+
def operation() -> dict[str, Any]:
|
|
1350
|
+
result = self._client.archive_space(space_key)
|
|
1351
|
+
self._log.info("space_archived", space_key=space_key)
|
|
1352
|
+
return result or {"status": "archived"}
|
|
1353
|
+
|
|
1354
|
+
return self._with_retry(operation, f"archive_space {space_key}")
|
|
1355
|
+
|
|
1356
|
+
def get_trashed_contents_by_space(
|
|
1357
|
+
self,
|
|
1358
|
+
space_key: str,
|
|
1359
|
+
*,
|
|
1360
|
+
cursor: str | None = None,
|
|
1361
|
+
expand: str | None = None,
|
|
1362
|
+
limit: int = 100,
|
|
1363
|
+
) -> dict[str, Any]:
|
|
1364
|
+
"""List trashed content for a space."""
|
|
1365
|
+
|
|
1366
|
+
def operation() -> dict[str, Any]:
|
|
1367
|
+
result = self._client.get_trashed_contents_by_space(
|
|
1368
|
+
space_key,
|
|
1369
|
+
cursor=cursor,
|
|
1370
|
+
expand=expand,
|
|
1371
|
+
limit=limit,
|
|
1372
|
+
)
|
|
1373
|
+
count = len(result.get("results", [])) if isinstance(result, dict) else 0
|
|
1374
|
+
self._log.debug(
|
|
1375
|
+
"space_trash_listed",
|
|
1376
|
+
space_key=space_key,
|
|
1377
|
+
count=count,
|
|
1378
|
+
cursor=cursor,
|
|
1379
|
+
limit=limit,
|
|
1380
|
+
)
|
|
1381
|
+
return self._coerce_dict(result)
|
|
1382
|
+
|
|
1383
|
+
return self._with_retry(operation, f"get_trashed_contents_by_space {space_key}")
|
|
1384
|
+
|
|
1385
|
+
def remove_trashed_contents_by_space(self, space_key: str) -> dict[str, Any]:
|
|
1386
|
+
"""Remove trashed content for a space."""
|
|
1387
|
+
|
|
1388
|
+
def operation() -> dict[str, Any]:
|
|
1389
|
+
result = self._client.remove_trashed_contents_by_space(space_key)
|
|
1390
|
+
self._log.info("space_trash_cleared", space_key=space_key)
|
|
1391
|
+
return result or {"status": "removed"}
|
|
1392
|
+
|
|
1393
|
+
return self._with_retry(operation, f"remove_trashed_contents_by_space {space_key}")
|
|
1394
|
+
|
|
1395
|
+
def get_child_pages(self, page_id: str, expand: list[str] | None = None) -> list[dict[str, Any]]:
|
|
1396
|
+
"""Get child pages of a page.
|
|
1397
|
+
|
|
1398
|
+
Uses the official SDK `get_page_child_by_type()` when available so callers can:
|
|
1399
|
+
- control pagination reliably
|
|
1400
|
+
- request expanded fields (for example `body.storage`) in one round trip
|
|
1401
|
+
"""
|
|
1402
|
+
|
|
1403
|
+
def operation():
|
|
1404
|
+
expand_arg = ",".join(expand) if expand else None
|
|
1405
|
+
|
|
1406
|
+
# `atlassian.Confluence.get_child_pages()` does not expose pagination controls.
|
|
1407
|
+
# Prefer `get_page_child_by_type()` so crawlers don't silently miss children.
|
|
1408
|
+
if hasattr(self._client, "get_page_child_by_type"):
|
|
1409
|
+
start = 0
|
|
1410
|
+
limit = 100
|
|
1411
|
+
collected: list[dict[str, Any]] = []
|
|
1412
|
+
seen_starts: set[int] = set()
|
|
1413
|
+
seen_tokens: set[str] = set()
|
|
1414
|
+
guard_reason: str | None = None
|
|
1415
|
+
|
|
1416
|
+
while True:
|
|
1417
|
+
if start in seen_starts:
|
|
1418
|
+
guard_reason = "repeated_start"
|
|
1419
|
+
break
|
|
1420
|
+
seen_starts.add(start)
|
|
1421
|
+
|
|
1422
|
+
payload = self._client.get_page_child_by_type(
|
|
1423
|
+
page_id,
|
|
1424
|
+
type="page",
|
|
1425
|
+
start=start,
|
|
1426
|
+
limit=limit,
|
|
1427
|
+
expand=expand_arg,
|
|
1428
|
+
)
|
|
1429
|
+
results: list[dict[str, Any]] = []
|
|
1430
|
+
if isinstance(payload, dict):
|
|
1431
|
+
raw = payload.get("results", [])
|
|
1432
|
+
if isinstance(raw, list):
|
|
1433
|
+
results = [item for item in raw if isinstance(item, dict)]
|
|
1434
|
+
elif isinstance(payload, list):
|
|
1435
|
+
results = [item for item in payload if isinstance(item, dict)]
|
|
1436
|
+
else:
|
|
1437
|
+
results = []
|
|
1438
|
+
|
|
1439
|
+
if not results:
|
|
1440
|
+
break
|
|
1441
|
+
|
|
1442
|
+
collected.extend(results)
|
|
1443
|
+
|
|
1444
|
+
next_start: int | None = None
|
|
1445
|
+
next_token: str | None = None
|
|
1446
|
+
if isinstance(payload, dict):
|
|
1447
|
+
next_start, next_token = self._extract_pagination_marker(payload)
|
|
1448
|
+
|
|
1449
|
+
if next_token:
|
|
1450
|
+
if next_token in seen_tokens:
|
|
1451
|
+
guard_reason = "repeated_token"
|
|
1452
|
+
break
|
|
1453
|
+
seen_tokens.add(next_token)
|
|
1454
|
+
|
|
1455
|
+
if next_start is not None:
|
|
1456
|
+
if next_start <= start:
|
|
1457
|
+
guard_reason = "no_progress_start"
|
|
1458
|
+
break
|
|
1459
|
+
start = next_start
|
|
1460
|
+
continue
|
|
1461
|
+
|
|
1462
|
+
if len(results) < limit:
|
|
1463
|
+
break
|
|
1464
|
+
start += limit
|
|
1465
|
+
|
|
1466
|
+
if guard_reason:
|
|
1467
|
+
log_fn = self._log.info if guard_reason == "no_progress_start" else self._log.warning
|
|
1468
|
+
log_fn(
|
|
1469
|
+
"child_pages_pagination_guard_triggered",
|
|
1470
|
+
page_id=page_id,
|
|
1471
|
+
reason=guard_reason,
|
|
1472
|
+
count=len(collected),
|
|
1473
|
+
last_start=start,
|
|
1474
|
+
)
|
|
1475
|
+
|
|
1476
|
+
self._log.debug(
|
|
1477
|
+
"child_pages_retrieved",
|
|
1478
|
+
page_id=page_id,
|
|
1479
|
+
count=len(collected),
|
|
1480
|
+
expand=expand_arg,
|
|
1481
|
+
)
|
|
1482
|
+
return collected
|
|
1483
|
+
|
|
1484
|
+
result = self._client.get_child_pages(page_id)
|
|
1485
|
+
# Convert generator to list if needed.
|
|
1486
|
+
if result is not None and hasattr(result, "__iter__") and not isinstance(result, (list, tuple, dict)):
|
|
1487
|
+
result = list(result)
|
|
1488
|
+
result_list: list[dict[str, Any]] = []
|
|
1489
|
+
if isinstance(result, list):
|
|
1490
|
+
result_list = [item for item in result if isinstance(item, dict)]
|
|
1491
|
+
self._log.debug(
|
|
1492
|
+
"child_pages_retrieved",
|
|
1493
|
+
page_id=page_id,
|
|
1494
|
+
count=len(result_list),
|
|
1495
|
+
expand=expand_arg,
|
|
1496
|
+
)
|
|
1497
|
+
return result_list
|
|
1498
|
+
|
|
1499
|
+
return self._with_retry(operation, f"get_child_pages {page_id}")
|
|
1500
|
+
|
|
1501
|
+
def get_page_by_title(self, space_key: str, title: str) -> dict[str, Any] | None:
|
|
1502
|
+
"""Get a page by space and title."""
|
|
1503
|
+
try:
|
|
1504
|
+
result = self._client.get_page_by_title(space_key, title)
|
|
1505
|
+
self._log.debug("page_retrieved_by_title", space_key=space_key, title=title)
|
|
1506
|
+
if result is None:
|
|
1507
|
+
return None
|
|
1508
|
+
return self._require_dict(result, "get_page_by_title")
|
|
1509
|
+
|
|
1510
|
+
except Exception as exc:
|
|
1511
|
+
self._handle_api_error(exc, f"get_page_by_title {space_key}/{title}")
|
|
1512
|
+
raise
|
|
1513
|
+
|
|
1514
|
+
def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
|
|
1515
|
+
"""Get attachments for a page."""
|
|
1516
|
+
try:
|
|
1517
|
+
start = 0
|
|
1518
|
+
limit = 50
|
|
1519
|
+
collected: list[dict[str, Any]] = []
|
|
1520
|
+
seen_starts: set[int] = set()
|
|
1521
|
+
seen_tokens: set[str] = set()
|
|
1522
|
+
guard_reason: str | None = None
|
|
1523
|
+
|
|
1524
|
+
while True:
|
|
1525
|
+
if start in seen_starts:
|
|
1526
|
+
guard_reason = "repeated_start"
|
|
1527
|
+
break
|
|
1528
|
+
seen_starts.add(start)
|
|
1529
|
+
|
|
1530
|
+
payload = self._client.get_attachments_from_content(page_id, start=start, limit=limit)
|
|
1531
|
+
results: list[dict[str, Any]] = []
|
|
1532
|
+
if isinstance(payload, dict):
|
|
1533
|
+
raw = payload.get("results", [])
|
|
1534
|
+
if isinstance(raw, list):
|
|
1535
|
+
results = [item for item in raw if isinstance(item, dict)]
|
|
1536
|
+
elif isinstance(payload, list):
|
|
1537
|
+
results = [item for item in payload if isinstance(item, dict)]
|
|
1538
|
+
else:
|
|
1539
|
+
results = []
|
|
1540
|
+
|
|
1541
|
+
if not results:
|
|
1542
|
+
break
|
|
1543
|
+
|
|
1544
|
+
collected.extend(results)
|
|
1545
|
+
|
|
1546
|
+
next_start: int | None = None
|
|
1547
|
+
next_token: str | None = None
|
|
1548
|
+
if isinstance(payload, dict):
|
|
1549
|
+
next_start, next_token = self._extract_pagination_marker(payload)
|
|
1550
|
+
|
|
1551
|
+
if next_token:
|
|
1552
|
+
if next_token in seen_tokens:
|
|
1553
|
+
guard_reason = "repeated_token"
|
|
1554
|
+
break
|
|
1555
|
+
seen_tokens.add(next_token)
|
|
1556
|
+
|
|
1557
|
+
if next_start is not None:
|
|
1558
|
+
if next_start <= start:
|
|
1559
|
+
guard_reason = "no_progress_start"
|
|
1560
|
+
break
|
|
1561
|
+
start = next_start
|
|
1562
|
+
continue
|
|
1563
|
+
|
|
1564
|
+
if len(results) < limit:
|
|
1565
|
+
break
|
|
1566
|
+
start += limit
|
|
1567
|
+
|
|
1568
|
+
if guard_reason:
|
|
1569
|
+
log_fn = self._log.info if guard_reason == "no_progress_start" else self._log.warning
|
|
1570
|
+
log_fn(
|
|
1571
|
+
"attachments_pagination_guard_triggered",
|
|
1572
|
+
page_id=page_id,
|
|
1573
|
+
reason=guard_reason,
|
|
1574
|
+
count=len(collected),
|
|
1575
|
+
last_start=start,
|
|
1576
|
+
)
|
|
1577
|
+
|
|
1578
|
+
self._log.debug("attachments_retrieved", page_id=page_id, count=len(collected))
|
|
1579
|
+
return collected
|
|
1580
|
+
|
|
1581
|
+
except Exception as exc:
|
|
1582
|
+
self._handle_api_error(exc, f"get_attachments {page_id}")
|
|
1583
|
+
raise
|
|
1584
|
+
|
|
1585
|
+
def get_attachment_content(self, attachment_id: str) -> bytes:
|
|
1586
|
+
"""Get attachment content by ID."""
|
|
1587
|
+
try:
|
|
1588
|
+
# Cloud SDKs expose `get_attachment_data`, but Server variants may not.
|
|
1589
|
+
get_attachment_data = getattr(self._client, "get_attachment_data", None)
|
|
1590
|
+
if callable(get_attachment_data):
|
|
1591
|
+
result = get_attachment_data(attachment_id)
|
|
1592
|
+
self._log.debug("attachment_content_retrieved", attachment_id=attachment_id, source="sdk")
|
|
1593
|
+
return self._coerce_bytes(result)
|
|
1594
|
+
|
|
1595
|
+
# Fallback for Confluence Server: resolve the attachment metadata, then download via `_links.download`.
|
|
1596
|
+
from urllib.parse import urlparse
|
|
1597
|
+
|
|
1598
|
+
def _raise_for_status(response: Any) -> None:
|
|
1599
|
+
if hasattr(response, "raise_for_status"):
|
|
1600
|
+
response.raise_for_status()
|
|
1601
|
+
return
|
|
1602
|
+
status_code = getattr(response, "status_code", None)
|
|
1603
|
+
if isinstance(status_code, int) and status_code >= 400:
|
|
1604
|
+
raise ResponseError(
|
|
1605
|
+
f"Unexpected status {status_code}",
|
|
1606
|
+
status_code=status_code,
|
|
1607
|
+
context={"status_code": status_code},
|
|
1608
|
+
)
|
|
1609
|
+
|
|
1610
|
+
def _as_path(url_or_path: str) -> str:
|
|
1611
|
+
if url_or_path.startswith(("http://", "https://")):
|
|
1612
|
+
parsed = urlparse(url_or_path)
|
|
1613
|
+
path = parsed.path or "/"
|
|
1614
|
+
if parsed.query:
|
|
1615
|
+
path = f"{path}?{parsed.query}"
|
|
1616
|
+
return path
|
|
1617
|
+
return url_or_path if url_or_path.startswith("/") else f"/{url_or_path}"
|
|
1618
|
+
|
|
1619
|
+
meta_response = self.request(
|
|
1620
|
+
"GET",
|
|
1621
|
+
f"/rest/api/content/{attachment_id}",
|
|
1622
|
+
params={"expand": "version"},
|
|
1623
|
+
)
|
|
1624
|
+
_raise_for_status(meta_response)
|
|
1625
|
+
payload = meta_response.json()
|
|
1626
|
+
download_link = None
|
|
1627
|
+
if isinstance(payload, dict):
|
|
1628
|
+
links = payload.get("_links")
|
|
1629
|
+
if isinstance(links, dict):
|
|
1630
|
+
download_link = links.get("download")
|
|
1631
|
+
if not download_link or not isinstance(download_link, str):
|
|
1632
|
+
raise VDSClientError(
|
|
1633
|
+
"Attachment download link not found in response",
|
|
1634
|
+
context={"attachment_id": attachment_id},
|
|
1635
|
+
)
|
|
1636
|
+
|
|
1637
|
+
download_response = self.request(
|
|
1638
|
+
"GET",
|
|
1639
|
+
_as_path(download_link),
|
|
1640
|
+
headers={"Accept": "*/*"},
|
|
1641
|
+
)
|
|
1642
|
+
_raise_for_status(download_response)
|
|
1643
|
+
content = getattr(download_response, "content", None)
|
|
1644
|
+
if isinstance(content, bytes):
|
|
1645
|
+
self._log.debug("attachment_content_retrieved", attachment_id=attachment_id, source="download_link")
|
|
1646
|
+
return content
|
|
1647
|
+
# Fallback: some clients expose bytes via `read()` or `text`.
|
|
1648
|
+
if hasattr(download_response, "read"):
|
|
1649
|
+
data = download_response.read()
|
|
1650
|
+
if isinstance(data, bytes):
|
|
1651
|
+
self._log.debug(
|
|
1652
|
+
"attachment_content_retrieved", attachment_id=attachment_id, source="download_link_read"
|
|
1653
|
+
)
|
|
1654
|
+
return data
|
|
1655
|
+
text = getattr(download_response, "text", "")
|
|
1656
|
+
self._log.debug("attachment_content_retrieved", attachment_id=attachment_id, source="download_link_text")
|
|
1657
|
+
return (text or "").encode("utf-8")
|
|
1658
|
+
|
|
1659
|
+
except Exception as exc:
|
|
1660
|
+
self._handle_api_error(exc, f"get_attachment_content {attachment_id}")
|
|
1661
|
+
raise
|
|
1662
|
+
|
|
1663
|
+
def update_attachment_data(
|
|
1664
|
+
self,
|
|
1665
|
+
page_id: str,
|
|
1666
|
+
attachment_id: str,
|
|
1667
|
+
filename: str,
|
|
1668
|
+
content: bytes,
|
|
1669
|
+
content_type: str | None = None,
|
|
1670
|
+
*,
|
|
1671
|
+
comment: str | None = None,
|
|
1672
|
+
) -> dict[str, Any]:
|
|
1673
|
+
"""Update an existing attachment.
|
|
1674
|
+
|
|
1675
|
+
According to atlassian-python-api docs, attach_content automatically updates
|
|
1676
|
+
if the file exists, versioning the new file and keeping the old one.
|
|
1677
|
+
"""
|
|
1678
|
+
|
|
1679
|
+
def operation() -> dict[str, Any]:
|
|
1680
|
+
# Use attach_content - it automatically updates if file exists
|
|
1681
|
+
# See: https://atlassian-python-api.readthedocs.io/confluence.html#
|
|
1682
|
+
result = self._client.attach_content(
|
|
1683
|
+
content=content,
|
|
1684
|
+
name=filename,
|
|
1685
|
+
content_type=content_type or "application/octet-stream",
|
|
1686
|
+
page_id=page_id,
|
|
1687
|
+
comment=comment,
|
|
1688
|
+
)
|
|
1689
|
+
self._log.info("attachment_updated", page_id=page_id, attachment_id=attachment_id, filename=filename)
|
|
1690
|
+
return self._coerce_dict(result)
|
|
1691
|
+
|
|
1692
|
+
return self._with_retry(operation, f"update_attachment {attachment_id}")
|
|
1693
|
+
|
|
1694
|
+
def delete_attachment_by_id(self, page_id: str, attachment_id: str, version: int | None = None) -> None:
|
|
1695
|
+
"""Delete an attachment by ID (historic versions only).
|
|
1696
|
+
|
|
1697
|
+
The Atlassian SDK's ``delete_attachment_by_id`` endpoint only removes versions
|
|
1698
|
+
strictly lower than the current one, so callers should provide ``version`` when
|
|
1699
|
+
targeting a specific revision. Cloud docs:
|
|
1700
|
+
https://atlassian-python-api.readthedocs.io/confluence.html#confluence.Confluence.delete_attachment_by_id
|
|
1701
|
+
"""
|
|
1702
|
+
try:
|
|
1703
|
+
versions_payload = self._client.get_attachment_history(attachment_id) or []
|
|
1704
|
+
except Exception: # pragma: no cover - defensive
|
|
1705
|
+
versions_payload = []
|
|
1706
|
+
|
|
1707
|
+
versions = []
|
|
1708
|
+
for item in versions_payload:
|
|
1709
|
+
if isinstance(item, dict):
|
|
1710
|
+
number = item.get("number")
|
|
1711
|
+
if isinstance(number, int):
|
|
1712
|
+
versions.append(number)
|
|
1713
|
+
|
|
1714
|
+
if version is not None:
|
|
1715
|
+
versions = [ver for ver in versions if ver == version]
|
|
1716
|
+
|
|
1717
|
+
if not versions:
|
|
1718
|
+
if version is not None:
|
|
1719
|
+
versions = [version]
|
|
1720
|
+
else:
|
|
1721
|
+
raise VDSClientError(
|
|
1722
|
+
f"No attachment versions found for {attachment_id}. Specify --version explicitly.",
|
|
1723
|
+
context={"attachment_id": attachment_id, "page_id": page_id},
|
|
1724
|
+
)
|
|
1725
|
+
|
|
1726
|
+
for ver in sorted(set(versions), reverse=True):
|
|
1727
|
+
try:
|
|
1728
|
+
self._client.delete_attachment_by_id(attachment_id, ver)
|
|
1729
|
+
self._log.info(
|
|
1730
|
+
"attachment_version_deleted",
|
|
1731
|
+
page_id=page_id,
|
|
1732
|
+
attachment_id=attachment_id,
|
|
1733
|
+
version=ver,
|
|
1734
|
+
)
|
|
1735
|
+
except Exception as exc:
|
|
1736
|
+
self._handle_api_error(exc, f"delete_attachment {attachment_id} (version {ver})")
|
|
1737
|
+
|
|
1738
|
+
def delete_attachment(self, page_id: str, filename: str, version: int | None = None) -> None:
|
|
1739
|
+
"""Delete the latest version of an attachment by filename.
|
|
1740
|
+
|
|
1741
|
+
Mirrors :py:meth:`Confluence.delete_attachment` from the Atlassian SDK.
|
|
1742
|
+
"""
|
|
1743
|
+
|
|
1744
|
+
try:
|
|
1745
|
+
self._client.delete_attachment(page_id, filename, version=version)
|
|
1746
|
+
self._log.info(
|
|
1747
|
+
"attachment_deleted",
|
|
1748
|
+
page_id=page_id,
|
|
1749
|
+
filename=filename,
|
|
1750
|
+
version=version,
|
|
1751
|
+
)
|
|
1752
|
+
except Exception as exc:
|
|
1753
|
+
self._handle_api_error(exc, f"delete_attachment {filename}")
|
|
1754
|
+
|
|
1755
|
+
def add_comment(self, page_id: str, body: str, parent_id: str | None = None) -> dict[str, Any]:
|
|
1756
|
+
"""Add a comment to a page or reply to an existing comment.
|
|
1757
|
+
|
|
1758
|
+
When ``parent_id`` is provided, the comment will be created as a reply
|
|
1759
|
+
to the specified parent comment (threaded reply), mirroring
|
|
1760
|
+
:py:meth:`Confluence.add_comment` from the Atlassian SDK.
|
|
1761
|
+
"""
|
|
1762
|
+
|
|
1763
|
+
comment_params = set(self._safe_signature_params(self._client.add_comment))
|
|
1764
|
+
supports_parent = "parent_id" in comment_params or len(comment_params) >= 4
|
|
1765
|
+
if parent_id is not None and not supports_parent:
|
|
1766
|
+
raise VDSClientError(
|
|
1767
|
+
"Installed atlassian-python-api build does not support parent comment replies",
|
|
1768
|
+
context={"page_id": page_id},
|
|
1769
|
+
)
|
|
1770
|
+
|
|
1771
|
+
def _op() -> dict[str, Any]:
|
|
1772
|
+
if parent_id is not None:
|
|
1773
|
+
result = self._client.add_comment(page_id, body, parent_id) # type: ignore[call-arg]
|
|
1774
|
+
else:
|
|
1775
|
+
result = self._client.add_comment(page_id, body)
|
|
1776
|
+
self._log.info("comment_added", page_id=page_id, parent_id=parent_id)
|
|
1777
|
+
return self._coerce_dict(result)
|
|
1778
|
+
|
|
1779
|
+
return self._with_retry(_op, "add_comment")
|
|
1780
|
+
|
|
1781
|
+
def get_page_history(self, page_id: str) -> dict[str, Any]:
|
|
1782
|
+
"""Get page version history."""
|
|
1783
|
+
try:
|
|
1784
|
+
history_method = getattr(self._client, "get_history", None)
|
|
1785
|
+
if not callable(history_method):
|
|
1786
|
+
history_method = getattr(self._client, "history", None)
|
|
1787
|
+
if not callable(history_method):
|
|
1788
|
+
raise VDSClientError("SDK does not support page history retrieval")
|
|
1789
|
+
result = history_method(page_id)
|
|
1790
|
+
self._log.debug("page_history_retrieved", page_id=page_id)
|
|
1791
|
+
return self._coerce_dict(result)
|
|
1792
|
+
|
|
1793
|
+
except Exception as exc:
|
|
1794
|
+
self._handle_api_error(exc, f"get_page_history {page_id}")
|
|
1795
|
+
raise
|
|
1796
|
+
|
|
1797
|
+
@property
|
|
1798
|
+
def base_url(self) -> str:
|
|
1799
|
+
"""Base URL of the Confluence server (no trailing slash)."""
|
|
1800
|
+
return str(self._settings.url_for(self._server)).rstrip("/")
|
|
1801
|
+
|
|
1802
|
+
# --- Label Operations ---
|
|
1803
|
+
|
|
1804
|
+
def get_page_labels(self, page_id: str) -> list[dict[str, Any]]:
|
|
1805
|
+
"""Return raw label result list for a page."""
|
|
1806
|
+
|
|
1807
|
+
def operation() -> list[dict[str, Any]]:
|
|
1808
|
+
method = getattr(self._client, "get_page_labels", None)
|
|
1809
|
+
if not callable(method):
|
|
1810
|
+
# Fallback: REST endpoint (older SDK versions)
|
|
1811
|
+
resp = self._client.request("GET", f"rest/api/content/{page_id}/label")
|
|
1812
|
+
payload = resp.json() if hasattr(resp, "json") else {}
|
|
1813
|
+
return payload.get("results", []) if isinstance(payload, dict) else []
|
|
1814
|
+
result = method(page_id)
|
|
1815
|
+
if isinstance(result, dict):
|
|
1816
|
+
return result.get("results", [])
|
|
1817
|
+
return list(result) if result else []
|
|
1818
|
+
|
|
1819
|
+
return self._with_retry(operation, f"get_page_labels {page_id}")
|
|
1820
|
+
|
|
1821
|
+
def set_page_label(self, page_id: str, label: str) -> None:
|
|
1822
|
+
"""Add a label to a page."""
|
|
1823
|
+
|
|
1824
|
+
def operation() -> None:
|
|
1825
|
+
method = getattr(self._client, "set_page_label", None)
|
|
1826
|
+
if not callable(method):
|
|
1827
|
+
# Fallback: REST endpoint (older SDK versions)
|
|
1828
|
+
self._client.request(
|
|
1829
|
+
"POST",
|
|
1830
|
+
f"rest/api/content/{page_id}/label",
|
|
1831
|
+
data=[{"prefix": "global", "name": label}],
|
|
1832
|
+
)
|
|
1833
|
+
return
|
|
1834
|
+
method(page_id, label)
|
|
1835
|
+
|
|
1836
|
+
return self._with_retry(operation, f"set_page_label {page_id}")
|
|
1837
|
+
|
|
1838
|
+
def close(self) -> None:
|
|
1839
|
+
"""Close the client (cleanup method for compatibility)."""
|
|
1840
|
+
# atlassian-python-api doesn't require explicit cleanup
|
|
1841
|
+
pass
|
|
1842
|
+
|
|
1843
|
+
def __enter__(self) -> ConfluenceClient:
|
|
1844
|
+
return self
|
|
1845
|
+
|
|
1846
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
1847
|
+
self.close()
|
|
1848
|
+
|
|
1849
|
+
|
|
1850
|
+
__all__ = ["ConfluenceClient"]
|