@ngocsangairvds/vsaf 4.0.5 → 4.0.7

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.
Files changed (1044) hide show
  1. package/package.json +1 -1
  2. package/packages/cli/dist/commands/install.js +1 -1
  3. package/packages/cli/dist/commands/install.js.map +1 -1
  4. package/skills/vds-skill/create-bitbucket-pr/SKILL.md +7 -1
  5. package/skills/vds-skill/create-jira-epic/SKILL.md +7 -1
  6. package/skills/vds-skill/install-deps.mjs +71 -4
  7. package/skills/vds-skill/push-prd/SKILL.md +8 -1
  8. package/skills/vds-skill/push-srs/SKILL.md +7 -1
  9. package/skills/vds-skill/search-confluence/SKILL.md +7 -1
  10. package/skills/vds-skill/vds-scripts/.claude/phase7-CLOSURE.md +100 -0
  11. package/skills/vds-skill/vds-scripts/.dockerignore +62 -0
  12. package/skills/vds-skill/vds-scripts/.github/ISSUE_TEMPLATE/cli-change.md +92 -0
  13. package/skills/vds-skill/vds-scripts/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +48 -0
  14. package/skills/vds-skill/vds-scripts/.github/workflows/chaos-smoke.yml +266 -0
  15. package/skills/vds-skill/vds-scripts/.github/workflows/confluence-sync.yml +44 -0
  16. package/skills/vds-skill/vds-scripts/.github/workflows/docs-confluence-evidence.yml +170 -0
  17. package/skills/vds-skill/vds-scripts/.github/workflows/docs-quality.yml +59 -0
  18. package/skills/vds-skill/vds-scripts/.github/workflows/lint-and-test.yml +90 -0
  19. package/skills/vds-skill/vds-scripts/.github/workflows/scheduler-load-smoke.yml +104 -0
  20. package/skills/vds-skill/vds-scripts/.github/workflows/telegram-bridge-ci.yml +131 -0
  21. package/skills/vds-skill/vds-scripts/.graphifyignore +29 -0
  22. package/skills/vds-skill/vds-scripts/.importlinter +86 -0
  23. package/skills/vds-skill/vds-scripts/.mcp.json +11 -0
  24. package/skills/vds-skill/vds-scripts/.pre-commit-config.yaml +62 -0
  25. package/skills/vds-skill/vds-scripts/.ruffignore +3 -0
  26. package/skills/vds-skill/vds-scripts/.secrets.baseline +133 -0
  27. package/skills/vds-skill/vds-scripts/AGENTS.md +250 -0
  28. package/skills/vds-skill/vds-scripts/AGENTS.vi.md +92 -0
  29. package/skills/vds-skill/vds-scripts/ECOSYSTEM-CHANGELOG.md +52 -0
  30. package/skills/vds-skill/vds-scripts/ECOSYSTEM-DOCS.md +602 -0
  31. package/skills/vds-skill/vds-scripts/ECOSYSTEM_ALIGNMENT.md +133 -0
  32. package/skills/vds-skill/vds-scripts/Makefile +119 -0
  33. package/skills/vds-skill/vds-scripts/README.md +103 -0
  34. package/skills/vds-skill/vds-scripts/bitbucket_manifest_mapping.toml +34 -0
  35. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/ARCHITECTURE_ANALYSIS.md +258 -0
  36. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/BITBUCKET_API_PRACTICES.md +393 -0
  37. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/EVALUATION_REPORT.md +61 -0
  38. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/FEATURES.md +908 -0
  39. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/README.md +817 -0
  40. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/pyproject.toml +49 -0
  41. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/__init__.py +50 -0
  42. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/async_client.py +641 -0
  43. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/cli.py +2271 -0
  44. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/client.py +2693 -0
  45. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/config.py +186 -0
  46. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/errors.py +34 -0
  47. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/factory.py +185 -0
  48. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/parsers.py +113 -0
  49. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/protocols.py +244 -0
  50. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/repo_ops.py +325 -0
  51. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/__init__.py +8 -0
  52. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/conftest.py +65 -0
  53. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_advanced_search.py +155 -0
  54. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_async_client.py +505 -0
  55. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_branch_permissions.py +172 -0
  56. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_cli.py +113 -0
  57. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_cli_archive.py +122 -0
  58. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_cli_clone.py +131 -0
  59. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client.py +207 -0
  60. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_archive.py +73 -0
  61. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_branch_conditions.py +101 -0
  62. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_code_advanced.py +180 -0
  63. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_code_file.py +33 -0
  64. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_deployment_environments.py +193 -0
  65. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_issues.py +163 -0
  66. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_pipelines_advanced.py +171 -0
  67. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_pr_blockers.py +118 -0
  68. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_repository_variables.py +155 -0
  69. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_code.py +98 -0
  70. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_code_advanced.py +279 -0
  71. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_code_insights.py +334 -0
  72. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_conditions.py +149 -0
  73. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_config.py +297 -0
  74. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_deployment_env.py +352 -0
  75. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_errors.py +67 -0
  76. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_factory.py +352 -0
  77. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_fork_operations.py +203 -0
  78. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_issue_cli.py +262 -0
  79. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_pipeline_advanced.py +265 -0
  80. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_pr_blocker.py +206 -0
  81. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_protocols.py +336 -0
  82. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_repo_ops_archive.py +169 -0
  83. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_repo_ops_clone.py +115 -0
  84. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_repo_ops_parsing.py +149 -0
  85. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_repo_settings.py +336 -0
  86. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_repo_variables.py +266 -0
  87. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_webhooks.py +188 -0
  88. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_workspace.py +234 -0
  89. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/unit/__init__.py +0 -0
  90. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/unit/test_parsers.py +254 -0
  91. package/skills/vds-skill/vds-scripts/brd_orchestrator/README.md +29 -0
  92. package/skills/vds-skill/vds-scripts/brd_orchestrator/pyproject.toml +63 -0
  93. package/skills/vds-skill/vds-scripts/brd_orchestrator/src/vds_brd_orchestrator/__init__.py +17 -0
  94. package/skills/vds-skill/vds-scripts/brd_orchestrator/src/vds_brd_orchestrator/cli.py +187 -0
  95. package/skills/vds-skill/vds-scripts/brd_orchestrator/src/vds_brd_orchestrator/validator.py +121 -0
  96. package/skills/vds-skill/vds-scripts/brd_orchestrator/tests/__init__.py +0 -0
  97. package/skills/vds-skill/vds-scripts/brd_orchestrator/tests/test_cli.py +62 -0
  98. package/skills/vds-skill/vds-scripts/brd_orchestrator/tests/test_validator.py +33 -0
  99. package/skills/vds-skill/vds-scripts/code/code_evidence_pack.json +435 -0
  100. package/skills/vds-skill/vds-scripts/confluence_orchestrator/Dockerfile +19 -0
  101. package/skills/vds-skill/vds-scripts/confluence_orchestrator/README.md +479 -0
  102. package/skills/vds-skill/vds-scripts/confluence_orchestrator/SYNC_SCRIPTS.md +127 -0
  103. package/skills/vds-skill/vds-scripts/confluence_orchestrator/SYNC_STANDARDIZATION.md +108 -0
  104. package/skills/vds-skill/vds-scripts/confluence_orchestrator/pyproject.toml +50 -0
  105. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/__init__.py +56 -0
  106. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/async_client.py +100 -0
  107. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/cli.py +3160 -0
  108. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/config.py +215 -0
  109. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/content.py +368 -0
  110. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/content_v2.py +144 -0
  111. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/crawl_tree.py +1833 -0
  112. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/errors.py +44 -0
  113. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/eventing.py +111 -0
  114. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/http.py +1850 -0
  115. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/orchestration.py +166 -0
  116. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/protocols.py +61 -0
  117. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/reporting.py +78 -0
  118. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/tree.py +122 -0
  119. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/tree_copier.py +431 -0
  120. package/skills/vds-skill/vds-scripts/confluence_orchestrator/sync_pdfs_from_markdown.py +203 -0
  121. package/skills/vds-skill/vds-scripts/confluence_orchestrator/sync_pdfs_to_confluence.py +299 -0
  122. package/skills/vds-skill/vds-scripts/confluence_orchestrator/sync_png_attachments.py +299 -0
  123. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/conftest.py +46 -0
  124. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_advanced_content.py +252 -0
  125. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_advanced_search.py +193 -0
  126. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_async_client.py +104 -0
  127. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_cache_management.py +246 -0
  128. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_cli.py +716 -0
  129. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_config.py +130 -0
  130. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_content.py +192 -0
  131. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_content_flags.py +27 -0
  132. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_content_labels.py +94 -0
  133. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_crawl_tree.py +2252 -0
  134. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_degraded_write_safety.py +176 -0
  135. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_draft_management.py +225 -0
  136. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_errors.py +75 -0
  137. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_eventing.py +73 -0
  138. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_eventing_chaos.py +37 -0
  139. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_eventing_rate_limit.py +44 -0
  140. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_eventing_timeout.py +49 -0
  141. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_export.py +231 -0
  142. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_history.py +217 -0
  143. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_http.py +375 -0
  144. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_orchestration.py +93 -0
  145. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_reporting.py +24 -0
  146. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_search_cql.py +36 -0
  147. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_space_management.py +236 -0
  148. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_space_permissions.py +384 -0
  149. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_tree_copier.py +644 -0
  150. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_tree_copier_remap.py +289 -0
  151. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_user_group_management.py +387 -0
  152. package/skills/vds-skill/vds-scripts/diagram_generator/README.md +663 -0
  153. package/skills/vds-skill/vds-scripts/diagram_generator/ci_validate.sh +16 -0
  154. package/skills/vds-skill/vds-scripts/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-component.png +0 -0
  155. package/skills/vds-skill/vds-scripts/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-component.puml +23 -0
  156. package/skills/vds-skill/vds-scripts/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-sequence.png +0 -0
  157. package/skills/vds-skill/vds-scripts/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-sequence.puml +21 -0
  158. package/skills/vds-skill/vds-scripts/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-usecase.png +0 -0
  159. package/skills/vds-skill/vds-scripts/diagram_generator/docs-nttc/projects/INSURANCE/analysis/current-state/insurance-claim-business/insurance-claim-business-usecase.puml +14 -0
  160. package/skills/vds-skill/vds-scripts/diagram_generator/examples/github-actions-validate.yml +39 -0
  161. package/skills/vds-skill/vds-scripts/diagram_generator/generate_all_diagrams.py +827 -0
  162. package/skills/vds-skill/vds-scripts/diagram_generator/generate_insurance_c4_diagrams.py +261 -0
  163. package/skills/vds-skill/vds-scripts/diagram_generator/generate_insurance_c4_quick.py +486 -0
  164. package/skills/vds-skill/vds-scripts/diagram_generator/pyproject.toml +28 -0
  165. package/skills/vds-skill/vds-scripts/diagram_generator/render_png.py +59 -0
  166. package/skills/vds-skill/vds-scripts/diagram_generator/src/vds_diagram_generator/__init__.py +3 -0
  167. package/skills/vds-skill/vds-scripts/diagram_generator/src/vds_diagram_generator/cli.py +50 -0
  168. package/skills/vds-skill/vds-scripts/diagram_generator/test_c4_hierarchical.py +142 -0
  169. package/skills/vds-skill/vds-scripts/diagram_generator/test_c4_quick.py +131 -0
  170. package/skills/vds-skill/vds-scripts/diagram_generator/tests/__init__.py +0 -0
  171. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_analyzer_completeness.py +260 -0
  172. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_c4_syntax_correctness.py +138 -0
  173. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_component_coverage.py +182 -0
  174. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_mermaid_output.py +80 -0
  175. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_png_generation.py +112 -0
  176. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_scenario_templates.py +15 -0
  177. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_sequence_accuracy.py +93 -0
  178. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_structurizr_export.py +177 -0
  179. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_style_consistency.py +174 -0
  180. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_usecase_generator.py +201 -0
  181. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_usecase_integration.py +124 -0
  182. package/skills/vds-skill/vds-scripts/docker/.dockerignore +38 -0
  183. package/skills/vds-skill/vds-scripts/docker/ADR.md +392 -0
  184. package/skills/vds-skill/vds-scripts/docker/Dockerfile +68 -0
  185. package/skills/vds-skill/vds-scripts/docker/MIGRATION.md +453 -0
  186. package/skills/vds-skill/vds-scripts/docker/README.md +347 -0
  187. package/skills/vds-skill/vds-scripts/docker/ROLLBACK.md +596 -0
  188. package/skills/vds-skill/vds-scripts/docker/compose.phase2-verification.yml +31 -0
  189. package/skills/vds-skill/vds-scripts/docker/docker-compose.cli.yml +206 -0
  190. package/skills/vds-skill/vds-scripts/docker/docker-compose.infra.yml +276 -0
  191. package/skills/vds-skill/vds-scripts/docker/docker-compose.services.yml +425 -0
  192. package/skills/vds-skill/vds-scripts/docker/infrastructure/init-schemas.sql +177 -0
  193. package/skills/vds-skill/vds-scripts/docker/infrastructure/pgbouncer/pgbouncer.ini +75 -0
  194. package/skills/vds-skill/vds-scripts/docker/infrastructure/pgbouncer/userlist.txt +50 -0
  195. package/skills/vds-skill/vds-scripts/docker/infrastructure/pgbouncer/userlist.txt.template +36 -0
  196. package/skills/vds-skill/vds-scripts/docs/.confluence-evidence/.gitkeep +0 -0
  197. package/skills/vds-skill/vds-scripts/docs/.confluence-evidence/PREFLIGHT.md +132 -0
  198. package/skills/vds-skill/vds-scripts/docs/.confluence-evidence/README.md +84 -0
  199. package/skills/vds-skill/vds-scripts/docs/.confluence.yaml +157 -0
  200. package/skills/vds-skill/vds-scripts/docs/.freshness.yaml +54 -0
  201. package/skills/vds-skill/vds-scripts/docs/README.md +235 -0
  202. package/skills/vds-skill/vds-scripts/docs/agents/README.md +33 -0
  203. package/skills/vds-skill/vds-scripts/docs/agents/explanation/data-flow.md +132 -0
  204. package/skills/vds-skill/vds-scripts/docs/agents/explanation/development-roadmap.md +49 -0
  205. package/skills/vds-skill/vds-scripts/docs/agents/explanation/features-overview.md +62 -0
  206. package/skills/vds-skill/vds-scripts/docs/agents/explanation/index.md +36 -0
  207. package/skills/vds-skill/vds-scripts/docs/agents/explanation/runtime-verification-and-gap-reporting.md +127 -0
  208. package/skills/vds-skill/vds-scripts/docs/agents/explanation/system-architecture.md +139 -0
  209. package/skills/vds-skill/vds-scripts/docs/agents/explanation/whats-new.md +75 -0
  210. package/skills/vds-skill/vds-scripts/docs/agents/explanation/who-ecosystem-introduction.md +65 -0
  211. package/skills/vds-skill/vds-scripts/docs/agents/explanation/who-ecosystem-model.md +41 -0
  212. package/skills/vds-skill/vds-scripts/docs/agents/how-to/02-using-vds-ai-memory.md +98 -0
  213. package/skills/vds-skill/vds-scripts/docs/agents/how-to/03-memory-cross-agent.md +241 -0
  214. package/skills/vds-skill/vds-scripts/docs/agents/how-to/04-using-progress-reports.md +240 -0
  215. package/skills/vds-skill/vds-scripts/docs/agents/how-to/08-semantic-search.md +46 -0
  216. package/skills/vds-skill/vds-scripts/docs/agents/how-to/apply-phase3-migration.md +148 -0
  217. package/skills/vds-skill/vds-scripts/docs/agents/how-to/choose-the-right-command-or-skill.md +34 -0
  218. package/skills/vds-skill/vds-scripts/docs/agents/how-to/contribute-new-orchestrator.md +149 -0
  219. package/skills/vds-skill/vds-scripts/docs/agents/how-to/decision-tree.md +63 -0
  220. package/skills/vds-skill/vds-scripts/docs/agents/how-to/first-audit-run.md +83 -0
  221. package/skills/vds-skill/vds-scripts/docs/agents/how-to/index.md +49 -0
  222. package/skills/vds-skill/vds-scripts/docs/agents/how-to/install-and-bootstrap-who-scripts-and-skills.md +314 -0
  223. package/skills/vds-skill/vds-scripts/docs/agents/how-to/orchestrator-workflows/analytics-pipeline-workflow.md +165 -0
  224. package/skills/vds-skill/vds-scripts/docs/agents/how-to/orchestrator-workflows/code-quality-gate-workflow.md +138 -0
  225. package/skills/vds-skill/vds-scripts/docs/agents/how-to/orchestrator-workflows/confluence-bitbucket-sync-workflow.md +130 -0
  226. package/skills/vds-skill/vds-scripts/docs/agents/how-to/orchestrator-workflows/document-delivery-workflow.md +142 -0
  227. package/skills/vds-skill/vds-scripts/docs/agents/how-to/orchestrator-workflows/memory-progress-workflow.md +140 -0
  228. package/skills/vds-skill/vds-scripts/docs/agents/how-to/orchestrator-workflows/research-spec-audit-workflow.md +135 -0
  229. package/skills/vds-skill/vds-scripts/docs/agents/how-to/phase131-all-project-preparation.md +211 -0
  230. package/skills/vds-skill/vds-scripts/docs/agents/how-to/phase131-bounded-parallel-analysis.md +123 -0
  231. package/skills/vds-skill/vds-scripts/docs/agents/how-to/phase131-confluence-upload-recovery.md +204 -0
  232. package/skills/vds-skill/vds-scripts/docs/agents/how-to/phase132-department-preparation.md +144 -0
  233. package/skills/vds-skill/vds-scripts/docs/agents/how-to/run-ecosystem-daily-report.md +213 -0
  234. package/skills/vds-skill/vds-scripts/docs/agents/how-to/tips-and-tricks.md +138 -0
  235. package/skills/vds-skill/vds-scripts/docs/agents/how-to/troubleshooting-guide.md +221 -0
  236. package/skills/vds-skill/vds-scripts/docs/agents/reference/agent-operational-contract.md +162 -0
  237. package/skills/vds-skill/vds-scripts/docs/agents/reference/alignment-phase179-report.md +144 -0
  238. package/skills/vds-skill/vds-scripts/docs/agents/reference/audit-triage-playbook.md +256 -0
  239. package/skills/vds-skill/vds-scripts/docs/agents/reference/backup-restore.md +132 -0
  240. package/skills/vds-skill/vds-scripts/docs/agents/reference/bitbucket-orchestrator.md +56 -0
  241. package/skills/vds-skill/vds-scripts/docs/agents/reference/brd-orchestrator.md +52 -0
  242. package/skills/vds-skill/vds-scripts/docs/agents/reference/capability-coverage-review.md +51 -0
  243. package/skills/vds-skill/vds-scripts/docs/agents/reference/ci-workflows.md +98 -0
  244. package/skills/vds-skill/vds-scripts/docs/agents/reference/circular-dependency-orchestrator.md +55 -0
  245. package/skills/vds-skill/vds-scripts/docs/agents/reference/cli-commands.md +583 -0
  246. package/skills/vds-skill/vds-scripts/docs/agents/reference/cli-development-standards.md +41 -0
  247. package/skills/vds-skill/vds-scripts/docs/agents/reference/cli-help-matrix.md +84 -0
  248. package/skills/vds-skill/vds-scripts/docs/agents/reference/common-errors.md +126 -0
  249. package/skills/vds-skill/vds-scripts/docs/agents/reference/configuration-reference.md +128 -0
  250. package/skills/vds-skill/vds-scripts/docs/agents/reference/confluence-orchestrator.md +56 -0
  251. package/skills/vds-skill/vds-scripts/docs/agents/reference/confluence-sync-target.md +111 -0
  252. package/skills/vds-skill/vds-scripts/docs/agents/reference/confluence-sync.md +46 -0
  253. package/skills/vds-skill/vds-scripts/docs/agents/reference/db-query-orchestrator.md +93 -0
  254. package/skills/vds-skill/vds-scripts/docs/agents/reference/diagrams-orchestrator.md +52 -0
  255. package/skills/vds-skill/vds-scripts/docs/agents/reference/ecosystem-daily-report.md +229 -0
  256. package/skills/vds-skill/vds-scripts/docs/agents/reference/elastic-orchestrator.md +57 -0
  257. package/skills/vds-skill/vds-scripts/docs/agents/reference/env-git-helper.md +216 -0
  258. package/skills/vds-skill/vds-scripts/docs/agents/reference/evolution-orchestrator.md +113 -0
  259. package/skills/vds-skill/vds-scripts/docs/agents/reference/excel-orchestrator.md +51 -0
  260. package/skills/vds-skill/vds-scripts/docs/agents/reference/git-orchestrator.md +62 -0
  261. package/skills/vds-skill/vds-scripts/docs/agents/reference/google-sheets-orchestrator.md +51 -0
  262. package/skills/vds-skill/vds-scripts/docs/agents/reference/grafana-orchestrator.md +52 -0
  263. package/skills/vds-skill/vds-scripts/docs/agents/reference/hexagonal-orchestrator.md +64 -0
  264. package/skills/vds-skill/vds-scripts/docs/agents/reference/index.md +36 -0
  265. package/skills/vds-skill/vds-scripts/docs/agents/reference/infrastructure-v2.15.md +67 -0
  266. package/skills/vds-skill/vds-scripts/docs/agents/reference/intellij-orchestrator.md +50 -0
  267. package/skills/vds-skill/vds-scripts/docs/agents/reference/jira-orchestrator.md +60 -0
  268. package/skills/vds-skill/vds-scripts/docs/agents/reference/links-orchestrator.md +57 -0
  269. package/skills/vds-skill/vds-scripts/docs/agents/reference/lint-cli.md +99 -0
  270. package/skills/vds-skill/vds-scripts/docs/agents/reference/lsp-orchestrator.md +51 -0
  271. package/skills/vds-skill/vds-scripts/docs/agents/reference/markdown-orchestrator.md +53 -0
  272. package/skills/vds-skill/vds-scripts/docs/agents/reference/mcp-orchestrator.md +88 -0
  273. package/skills/vds-skill/vds-scripts/docs/agents/reference/memory-orchestrator.md +53 -0
  274. package/skills/vds-skill/vds-scripts/docs/agents/reference/metabase-orchestrator.md +51 -0
  275. package/skills/vds-skill/vds-scripts/docs/agents/reference/migration-playbook.md +71 -0
  276. package/skills/vds-skill/vds-scripts/docs/agents/reference/multi-agent-orchestrator.md +52 -0
  277. package/skills/vds-skill/vds-scripts/docs/agents/reference/openapi-orchestrator.md +57 -0
  278. package/skills/vds-skill/vds-scripts/docs/agents/reference/orchestrator-architecture.md +194 -0
  279. package/skills/vds-skill/vds-scripts/docs/agents/reference/orchestrator-comparison-matrix.md +79 -0
  280. package/skills/vds-skill/vds-scripts/docs/agents/reference/orchestrator-index.md +73 -0
  281. package/skills/vds-skill/vds-scripts/docs/agents/reference/pdf-orchestrator.md +57 -0
  282. package/skills/vds-skill/vds-scripts/docs/agents/reference/portable-paths-and-config.md +0 -0
  283. package/skills/vds-skill/vds-scripts/docs/agents/reference/portable-paths-validation-matrix.md +129 -0
  284. package/skills/vds-skill/vds-scripts/docs/agents/reference/progress-orchestrator.md +51 -0
  285. package/skills/vds-skill/vds-scripts/docs/agents/reference/progress-report-cli.md +215 -0
  286. package/skills/vds-skill/vds-scripts/docs/agents/reference/public-interface-orchestrator.md +73 -0
  287. package/skills/vds-skill/vds-scripts/docs/agents/reference/research-orchestrator.md +53 -0
  288. package/skills/vds-skill/vds-scripts/docs/agents/reference/schema-orchestrator.md +57 -0
  289. package/skills/vds-skill/vds-scripts/docs/agents/reference/search-tools.md +34 -0
  290. package/skills/vds-skill/vds-scripts/docs/agents/reference/skills-commands.md +256 -0
  291. package/skills/vds-skill/vds-scripts/docs/agents/reference/skills-reference.md +32 -0
  292. package/skills/vds-skill/vds-scripts/docs/agents/reference/sonarqube-orchestrator.md +62 -0
  293. package/skills/vds-skill/vds-scripts/docs/agents/reference/spec-orchestrator.md +56 -0
  294. package/skills/vds-skill/vds-scripts/docs/agents/reference/structure-orchestrator.md +69 -0
  295. package/skills/vds-skill/vds-scripts/docs/agents/reference/system-requirements.md +76 -0
  296. package/skills/vds-skill/vds-scripts/docs/agents/reference/tasks-orchestrator.md +53 -0
  297. package/skills/vds-skill/vds-scripts/docs/agents/reference/validation-and-sync-notes.md +54 -0
  298. package/skills/vds-skill/vds-scripts/docs/agents/reference/vds-ai-memory-api.md +51 -0
  299. package/skills/vds-skill/vds-scripts/docs/agents/reference/vds-cli-reference.md +34 -0
  300. package/skills/vds-skill/vds-scripts/docs/agents/reference/who-capability-inventory.md +96 -0
  301. package/skills/vds-skill/vds-scripts/docs/agents/reference/who-capability-routing-matrix.md +14 -0
  302. package/skills/vds-skill/vds-scripts/docs/agents/tutorials/feature-progression-guide.md +112 -0
  303. package/skills/vds-skill/vds-scripts/docs/agents/tutorials/index.md +36 -0
  304. package/skills/vds-skill/vds-scripts/docs/agents/tutorials/quick-start.md +50 -0
  305. package/skills/vds-skill/vds-scripts/docs/agents/tutorials/who-skills-and-scripts-onboarding.md +47 -0
  306. package/skills/vds-skill/vds-scripts/docs/agents/tutorials/zero-to-productive-developer.md +339 -0
  307. package/skills/vds-skill/vds-scripts/docs/confluence/IMPLEMENTATION-SUMMARY.md +78 -0
  308. package/skills/vds-skill/vds-scripts/docs/confluence/SYNC-GUIDE.md +47 -0
  309. package/skills/vds-skill/vds-scripts/docs/deployment/offline-docker-image-load.md +59 -0
  310. package/skills/vds-skill/vds-scripts/docs/evolution-auto-run-rollout.md +325 -0
  311. package/skills/vds-skill/vds-scripts/docs/evolution-loop-deep-integration.md +496 -0
  312. package/skills/vds-skill/vds-scripts/docs/evolution-loop-integration-guide.md +359 -0
  313. package/skills/vds-skill/vds-scripts/docs/openspace-schema-snapshot.md +73 -0
  314. package/skills/vds-skill/vds-scripts/docs/operations/sla-mttr-policy.md +44 -0
  315. package/skills/vds-skill/vds-scripts/docs/p0-closure-evidence/SUMMARY.md +58 -0
  316. package/skills/vds-skill/vds-scripts/docs/p4-closure-evidence/.gitkeep +0 -0
  317. package/skills/vds-skill/vds-scripts/docs/p4-closure-evidence/smoke-20260427T024137Z-b95b586b.json +15 -0
  318. package/skills/vds-skill/vds-scripts/docs/p8-preflight-evidence/alembic-and-runtime-advisory-locks.md +45 -0
  319. package/skills/vds-skill/vds-scripts/docs/p8-preflight-evidence/dbos-listen-notify.md +54 -0
  320. package/skills/vds-skill/vds-scripts/docs/p8-preflight-evidence/pgbouncer-search-path-empirical.md +110 -0
  321. package/skills/vds-skill/vds-scripts/docs/p8-preflight-evidence/pgvector-set-local-audit.md +51 -0
  322. package/skills/vds-skill/vds-scripts/docs/p8-preflight-evidence/topology-decision-session-mode.md +57 -0
  323. package/skills/vds-skill/vds-scripts/docs/phases/CHANGELOG.md +103 -0
  324. package/skills/vds-skill/vds-scripts/docs/phases/PHASE_125_COMPLETION_AND_MERGE.md +212 -0
  325. package/skills/vds-skill/vds-scripts/docs/phases/phase125/IMPLEMENTATION_REPORT.md +227 -0
  326. package/skills/vds-skill/vds-scripts/docs/phases/phase125/TSK-125.10-11-implementation-summary.md +196 -0
  327. package/skills/vds-skill/vds-scripts/docs/phases/phase125/profile-patch-ollama-local-anthropic.md +122 -0
  328. package/skills/vds-skill/vds-scripts/docs/phases/phase125_completion_summary.md +369 -0
  329. package/skills/vds-skill/vds-scripts/docs/phases/phase125_llm_analysis_skill.md +164 -0
  330. package/skills/vds-skill/vds-scripts/docs/phases/phase125_merge_complete.md +147 -0
  331. package/skills/vds-skill/vds-scripts/docs/phases/phase125_skill_runtime_closure_20260321.md +91 -0
  332. package/skills/vds-skill/vds-scripts/docs/phases/phase2-portable-paths/closure-handoff-summary-2026-03-23.md +290 -0
  333. package/skills/vds-skill/vds-scripts/docs/phases/phase2-portable-paths/remaining-risk-register-2026-03-25.md +143 -0
  334. package/skills/vds-skill/vds-scripts/docs/phases/phase2-portable-paths/verification-evidence-2026-03-23.md +135 -0
  335. package/skills/vds-skill/vds-scripts/docs/v0-sunset-known-issues.md +88 -0
  336. package/skills/vds-skill/vds-scripts/docs/vi/TRANSLATION-BACKLOG.md +72 -0
  337. package/skills/vds-skill/vds-scripts/docs/vi/agents/README.md +41 -0
  338. package/skills/vds-skill/vds-scripts/docs/vi/agents/explanation/features-overview.md +29 -0
  339. package/skills/vds-skill/vds-scripts/docs/vi/agents/explanation/index.md +14 -0
  340. package/skills/vds-skill/vds-scripts/docs/vi/agents/explanation/runtime-verification-and-gap-reporting.md +129 -0
  341. package/skills/vds-skill/vds-scripts/docs/vi/agents/explanation/whats-new.md +37 -0
  342. package/skills/vds-skill/vds-scripts/docs/vi/agents/explanation/who-ecosystem-introduction.md +21 -0
  343. package/skills/vds-skill/vds-scripts/docs/vi/agents/explanation/who-ecosystem-model.md +36 -0
  344. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/02-using-vds-ai-memory.md +100 -0
  345. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/03-memory-cross-agent.md +243 -0
  346. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/04-using-progress-reports.md +242 -0
  347. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/08-semantic-search.md +16 -0
  348. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/choose-the-right-command-or-skill.md +36 -0
  349. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/decision-tree.md +77 -0
  350. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/first-audit-run.md +85 -0
  351. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/index.md +21 -0
  352. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/install-and-bootstrap-who-scripts-and-skills.md +156 -0
  353. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/orchestrator-workflows/analytics-pipeline-workflow.md +174 -0
  354. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/orchestrator-workflows/code-quality-gate-workflow.md +147 -0
  355. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/orchestrator-workflows/confluence-bitbucket-sync-workflow.md +139 -0
  356. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/orchestrator-workflows/document-delivery-workflow.md +151 -0
  357. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/orchestrator-workflows/memory-progress-workflow.md +149 -0
  358. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/orchestrator-workflows/research-spec-audit-workflow.md +144 -0
  359. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/phase131-all-project-preparation.md +213 -0
  360. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/phase131-bounded-parallel-analysis.md +125 -0
  361. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/phase131-confluence-upload-recovery.md +206 -0
  362. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/phase132-department-preparation.md +146 -0
  363. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/tips-and-tricks.md +34 -0
  364. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/troubleshooting-guide.md +36 -0
  365. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/agent-operational-contract.md +98 -0
  366. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/audit-triage-playbook.md +258 -0
  367. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/bitbucket-orchestrator.md +30 -0
  368. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/brd-orchestrator.md +29 -0
  369. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/capability-coverage-review.md +46 -0
  370. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/circular-dependency-orchestrator.md +29 -0
  371. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/cli-commands.md +409 -0
  372. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/cli-development-standards.md +19 -0
  373. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/cli-help-matrix.md +71 -0
  374. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/common-errors.md +133 -0
  375. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/configuration-reference.md +25 -0
  376. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/confluence-orchestrator.md +30 -0
  377. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/db-query-orchestrator.md +34 -0
  378. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/diagrams-orchestrator.md +31 -0
  379. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/elastic-orchestrator.md +30 -0
  380. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/evolution-orchestrator.md +31 -0
  381. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/excel-orchestrator.md +60 -0
  382. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/git-orchestrator.md +31 -0
  383. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/google-sheets-orchestrator.md +60 -0
  384. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/grafana-orchestrator.md +30 -0
  385. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/hexagonal-orchestrator.md +73 -0
  386. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/index.md +25 -0
  387. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/intellij-orchestrator.md +59 -0
  388. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/jira-orchestrator.md +32 -0
  389. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/links-orchestrator.md +66 -0
  390. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/lsp-orchestrator.md +60 -0
  391. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/markdown-orchestrator.md +62 -0
  392. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/mcp-orchestrator.md +34 -0
  393. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/memory-orchestrator.md +45 -0
  394. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/metabase-orchestrator.md +30 -0
  395. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/multi-agent-orchestrator.md +61 -0
  396. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/openapi-orchestrator.md +66 -0
  397. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/orchestrator-architecture.md +24 -0
  398. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/orchestrator-index.md +73 -0
  399. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/pdf-orchestrator.md +30 -0
  400. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/portable-paths-and-config.md +123 -0
  401. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/portable-paths-validation-matrix.md +131 -0
  402. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/progress-orchestrator.md +43 -0
  403. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/progress-report-cli.md +217 -0
  404. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/public-interface-orchestrator.md +82 -0
  405. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/research-orchestrator.md +45 -0
  406. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/schema-orchestrator.md +66 -0
  407. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/search-tools.md +19 -0
  408. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/skills-reference.md +27 -0
  409. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/sonarqube-orchestrator.md +71 -0
  410. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/spec-orchestrator.md +56 -0
  411. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/structure-orchestrator.md +78 -0
  412. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/system-requirements.md +30 -0
  413. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/tasks-orchestrator.md +45 -0
  414. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/validation-and-sync-notes.md +26 -0
  415. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/vds-ai-memory-api.md +53 -0
  416. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/vds-cli-reference.md +34 -0
  417. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/who-capability-inventory.md +98 -0
  418. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/who-capability-routing-matrix.md +16 -0
  419. package/skills/vds-skill/vds-scripts/docs/vi/agents/tutorials/feature-progression-guide.md +124 -0
  420. package/skills/vds-skill/vds-scripts/docs/vi/agents/tutorials/index.md +13 -0
  421. package/skills/vds-skill/vds-scripts/docs/vi/agents/tutorials/quick-start.md +30 -0
  422. package/skills/vds-skill/vds-scripts/docs/vi/agents/tutorials/who-skills-and-scripts-onboarding.md +42 -0
  423. package/skills/vds-skill/vds-scripts/docs/vi/agents/tutorials/zero-to-productive-developer.md +137 -0
  424. package/skills/vds-skill/vds-scripts/elastic_orchestrator/README.md +450 -0
  425. package/skills/vds-skill/vds-scripts/elastic_orchestrator/pyproject.toml +97 -0
  426. package/skills/vds-skill/vds-scripts/elastic_orchestrator/src/vds_elastic_orchestrator/__init__.py +81 -0
  427. package/skills/vds-skill/vds-scripts/elastic_orchestrator/src/vds_elastic_orchestrator/cli.py +652 -0
  428. package/skills/vds-skill/vds-scripts/elastic_orchestrator/src/vds_elastic_orchestrator/client.py +743 -0
  429. package/skills/vds-skill/vds-scripts/elastic_orchestrator/src/vds_elastic_orchestrator/config.py +208 -0
  430. package/skills/vds-skill/vds-scripts/elastic_orchestrator/src/vds_elastic_orchestrator/errors.py +34 -0
  431. package/skills/vds-skill/vds-scripts/elastic_orchestrator/src/vds_elastic_orchestrator/py.typed +0 -0
  432. package/skills/vds-skill/vds-scripts/elastic_orchestrator/tests/__init__.py +0 -0
  433. package/skills/vds-skill/vds-scripts/elastic_orchestrator/tests/conftest.py +227 -0
  434. package/skills/vds-skill/vds-scripts/elastic_orchestrator/tests/test_client.py +990 -0
  435. package/skills/vds-skill/vds-scripts/elastic_orchestrator/tests/test_config.py +268 -0
  436. package/skills/vds-skill/vds-scripts/elastic_orchestrator/tests/test_e2e_verification.py +272 -0
  437. package/skills/vds-skill/vds-scripts/elastic_orchestrator/tests/test_errors.py +78 -0
  438. package/skills/vds-skill/vds-scripts/excel_orchestrator/README.md +288 -0
  439. package/skills/vds-skill/vds-scripts/excel_orchestrator/RESEARCH_BASED_UPDATES_REPORT.md +261 -0
  440. package/skills/vds-skill/vds-scripts/excel_orchestrator/add_essential_missing_effort.py +255 -0
  441. package/skills/vds-skill/vds-scripts/excel_orchestrator/adjust_effort_complexity.py +184 -0
  442. package/skills/vds-skill/vds-scripts/excel_orchestrator/brd_analysis_and_task_breakdown.py +632 -0
  443. package/skills/vds-skill/vds-scripts/excel_orchestrator/brd_analysis_comprehensive.py +1029 -0
  444. package/skills/vds-skill/vds-scripts/excel_orchestrator/check_overlaps_and_brd_coverage.py +570 -0
  445. package/skills/vds-skill/vds-scripts/excel_orchestrator/clean_remarks_column.py +127 -0
  446. package/skills/vds-skill/vds-scripts/excel_orchestrator/comprehensive_brd_check.py +322 -0
  447. package/skills/vds-skill/vds-scripts/excel_orchestrator/create_buffered_summary.py +119 -0
  448. package/skills/vds-skill/vds-scripts/excel_orchestrator/create_service_totals_sheet.py +118 -0
  449. package/skills/vds-skill/vds-scripts/excel_orchestrator/examples/basic_operations.py +85 -0
  450. package/skills/vds-skill/vds-scripts/excel_orchestrator/expand_all_tasks.py +341 -0
  451. package/skills/vds-skill/vds-scripts/excel_orchestrator/expand_tasks.py +304 -0
  452. package/skills/vds-skill/vds-scripts/excel_orchestrator/fill_brd_references.py +347 -0
  453. package/skills/vds-skill/vds-scripts/excel_orchestrator/fill_remarks_and_colors.py +132 -0
  454. package/skills/vds-skill/vds-scripts/excel_orchestrator/finalize_brd_and_cleanup.py +295 -0
  455. package/skills/vds-skill/vds-scripts/excel_orchestrator/finalize_brd_coverage.py +327 -0
  456. package/skills/vds-skill/vds-scripts/excel_orchestrator/fix_all_formulas.py +99 -0
  457. package/skills/vds-skill/vds-scripts/excel_orchestrator/fix_detail_presentation.py +113 -0
  458. package/skills/vds-skill/vds-scripts/excel_orchestrator/fix_presentation_and_effort.py +116 -0
  459. package/skills/vds-skill/vds-scripts/excel_orchestrator/fix_presentation_consistency.py +231 -0
  460. package/skills/vds-skill/vds-scripts/excel_orchestrator/fix_remarks_matching.py +179 -0
  461. package/skills/vds-skill/vds-scripts/excel_orchestrator/group_tasks_by_service_id.py +210 -0
  462. package/skills/vds-skill/vds-scripts/excel_orchestrator/increase_brd_coverage.py +497 -0
  463. package/skills/vds-skill/vds-scripts/excel_orchestrator/increase_effort_complexity.py +155 -0
  464. package/skills/vds-skill/vds-scripts/excel_orchestrator/organize_and_deduplicate.py +273 -0
  465. package/skills/vds-skill/vds-scripts/excel_orchestrator/pyproject.toml +64 -0
  466. package/skills/vds-skill/vds-scripts/excel_orchestrator/rebuild_all_formulas.py +146 -0
  467. package/skills/vds-skill/vds-scripts/excel_orchestrator/remove_base_multiplier_and_check_duplicates.py +310 -0
  468. package/skills/vds-skill/vds-scripts/excel_orchestrator/remove_duplicate_brd_tasks.py +137 -0
  469. package/skills/vds-skill/vds-scripts/excel_orchestrator/research_based_updates.py +457 -0
  470. package/skills/vds-skill/vds-scripts/excel_orchestrator/restore_e_values.py +172 -0
  471. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/__init__.py +5 -0
  472. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/cli.py +746 -0
  473. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/config.py +74 -0
  474. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/converters.py +226 -0
  475. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/errors.py +88 -0
  476. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/excel_client.py +443 -0
  477. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/formatters.py +211 -0
  478. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/logging.py +57 -0
  479. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/source_contract.py +29 -0
  480. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/target_state_status.py +837 -0
  481. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/ulnc_alignment.py +1291 -0
  482. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/validators.py +164 -0
  483. package/skills/vds-skill/vds-scripts/excel_orchestrator/sync_detail_and_total_sheets.py +211 -0
  484. package/skills/vds-skill/vds-scripts/excel_orchestrator/tests/__init__.py +1 -0
  485. package/skills/vds-skill/vds-scripts/excel_orchestrator/tests/conftest.py +36 -0
  486. package/skills/vds-skill/vds-scripts/excel_orchestrator/tests/test_cli.py +383 -0
  487. package/skills/vds-skill/vds-scripts/excel_orchestrator/tests/test_excel_client.py +129 -0
  488. package/skills/vds-skill/vds-scripts/excel_orchestrator/tests/test_ulnc_alignment.py +373 -0
  489. package/skills/vds-skill/vds-scripts/excel_orchestrator/tests/test_validators.py +64 -0
  490. package/skills/vds-skill/vds-scripts/excel_orchestrator/update_api_database_effort.py +261 -0
  491. package/skills/vds-skill/vds-scripts/excel_orchestrator/update_buffers_inline.py +115 -0
  492. package/skills/vds-skill/vds-scripts/excel_orchestrator/update_complex_services_and_add_new.py +336 -0
  493. package/skills/vds-skill/vds-scripts/excel_orchestrator/update_responsibility_and_fix_rows.py +208 -0
  494. package/skills/vds-skill/vds-scripts/excel_orchestrator/update_task_breakdown_vietnamese.py +309 -0
  495. package/skills/vds-skill/vds-scripts/excel_orchestrator/update_vietnamese_and_responsibility.py +415 -0
  496. package/skills/vds-skill/vds-scripts/excel_orchestrator/verify_brd_coverage_comprehensive.py +401 -0
  497. package/skills/vds-skill/vds-scripts/git_orchestrator/ENHANCEMENT_SUMMARY.md +119 -0
  498. package/skills/vds-skill/vds-scripts/git_orchestrator/README.md +286 -0
  499. package/skills/vds-skill/vds-scripts/git_orchestrator/VERIFICATION_REPORT.md +152 -0
  500. package/skills/vds-skill/vds-scripts/git_orchestrator/pyproject.toml +37 -0
  501. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/__init__.py +30 -0
  502. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/__main__.py +4 -0
  503. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/branch_probe.py +271 -0
  504. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/cli.py +892 -0
  505. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/logging_config.py +63 -0
  506. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/manifest.py +249 -0
  507. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/orchestrator.py +1647 -0
  508. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/protocols.py +35 -0
  509. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/reporting.py +55 -0
  510. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/__init__.py +0 -0
  511. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/test_cli_settings.py +19 -0
  512. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/test_integration.py +79 -0
  513. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/test_manifest.py +79 -0
  514. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/test_orchestrator.py +207 -0
  515. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/test_public_api.py +235 -0
  516. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/test_resilience.py +343 -0
  517. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/unit/__init__.py +0 -0
  518. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/unit/test_branch_probe.py +327 -0
  519. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/unit/test_protocols.py +132 -0
  520. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/README.md +241 -0
  521. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/pyproject.toml +45 -0
  522. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/__init__.py +69 -0
  523. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/cli.py +568 -0
  524. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/client.py +186 -0
  525. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/config.py +46 -0
  526. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/errors.py +41 -0
  527. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/tests/__init__.py +1 -0
  528. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/tests/conftest.py +1 -0
  529. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/tests/unit/__init__.py +1 -0
  530. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/tests/unit/test_cli.py +212 -0
  531. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/tests/unit/test_client.py +24 -0
  532. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/tests/unit/test_config.py +16 -0
  533. package/skills/vds-skill/vds-scripts/grafana_orchestrator/README.md +572 -0
  534. package/skills/vds-skill/vds-scripts/grafana_orchestrator/pyproject.toml +102 -0
  535. package/skills/vds-skill/vds-scripts/grafana_orchestrator/src/vds_grafana_orchestrator/__init__.py +78 -0
  536. package/skills/vds-skill/vds-scripts/grafana_orchestrator/src/vds_grafana_orchestrator/cli.py +455 -0
  537. package/skills/vds-skill/vds-scripts/grafana_orchestrator/src/vds_grafana_orchestrator/client.py +700 -0
  538. package/skills/vds-skill/vds-scripts/grafana_orchestrator/src/vds_grafana_orchestrator/config.py +243 -0
  539. package/skills/vds-skill/vds-scripts/grafana_orchestrator/src/vds_grafana_orchestrator/errors.py +34 -0
  540. package/skills/vds-skill/vds-scripts/grafana_orchestrator/src/vds_grafana_orchestrator/py.typed +1 -0
  541. package/skills/vds-skill/vds-scripts/grafana_orchestrator/tests/__init__.py +1 -0
  542. package/skills/vds-skill/vds-scripts/grafana_orchestrator/tests/conftest.py +308 -0
  543. package/skills/vds-skill/vds-scripts/grafana_orchestrator/tests/test_client.py +458 -0
  544. package/skills/vds-skill/vds-scripts/grafana_orchestrator/tests/test_config.py +203 -0
  545. package/skills/vds-skill/vds-scripts/grafana_orchestrator/tests/test_errors.py +78 -0
  546. package/skills/vds-skill/vds-scripts/jira_orchestrator/README.md +864 -0
  547. package/skills/vds-skill/vds-scripts/jira_orchestrator/pyproject.toml +43 -0
  548. package/skills/vds-skill/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/__init__.py +65 -0
  549. package/skills/vds-skill/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/adapter.py +1685 -0
  550. package/skills/vds-skill/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/cli.py +2806 -0
  551. package/skills/vds-skill/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/config.py +168 -0
  552. package/skills/vds-skill/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/errors.py +34 -0
  553. package/skills/vds-skill/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/reporting.py +66 -0
  554. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/__init__.py +1 -0
  555. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/conftest.py +86 -0
  556. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_agile_list_payloads.py +54 -0
  557. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_bulk_operations.py +91 -0
  558. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_components.py +56 -0
  559. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_createmeta.py +45 -0
  560. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_dashboard.py +119 -0
  561. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_issue_properties.py +53 -0
  562. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_permissions_compat.py +41 -0
  563. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_reindex.py +42 -0
  564. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_remote_links.py +75 -0
  565. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_transitions.py +90 -0
  566. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_user_management.py +116 -0
  567. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_version_management.py +181 -0
  568. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_watchers.py +43 -0
  569. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_advanced_search.py +179 -0
  570. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_agile.py +304 -0
  571. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_application_properties.py +243 -0
  572. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_backlog.py +91 -0
  573. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_bulk_operations.py +403 -0
  574. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_cli.py +108 -0
  575. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_components.py +119 -0
  576. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_config.py +166 -0
  577. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_dashboard.py +122 -0
  578. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_discover_fields.py +207 -0
  579. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_errors.py +72 -0
  580. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_filter_management.py +411 -0
  581. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_issue_archiving.py +179 -0
  582. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_issue_links.py +257 -0
  583. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_issue_properties.py +189 -0
  584. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_link_types.py +407 -0
  585. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_parse_set.py +37 -0
  586. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_permissions.py +343 -0
  587. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_reindex.py +81 -0
  588. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_remote_links.py +269 -0
  589. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_security_schemes.py +202 -0
  590. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_transitions_changelog.py +109 -0
  591. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_user_management.py +246 -0
  592. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_version_management.py +503 -0
  593. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_watchers.py +116 -0
  594. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_worklog.py +243 -0
  595. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/README.md +864 -0
  596. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/pyproject.toml +43 -0
  597. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/__init__.py +65 -0
  598. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/adapter.py +1689 -0
  599. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/cli.py +2799 -0
  600. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/config.py +137 -0
  601. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/errors.py +34 -0
  602. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/reporting.py +65 -0
  603. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/__init__.py +1 -0
  604. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/conftest.py +86 -0
  605. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_agile_list_payloads.py +54 -0
  606. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_bulk_operations.py +101 -0
  607. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_components.py +64 -0
  608. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_createmeta.py +45 -0
  609. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_dashboard.py +135 -0
  610. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_issue_properties.py +63 -0
  611. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_permissions_compat.py +42 -0
  612. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_reindex.py +42 -0
  613. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_remote_links.py +89 -0
  614. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_transitions.py +91 -0
  615. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_user_management.py +130 -0
  616. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_version_management.py +189 -0
  617. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_watchers.py +49 -0
  618. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_advanced_search.py +213 -0
  619. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_agile.py +334 -0
  620. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_application_properties.py +261 -0
  621. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_backlog.py +91 -0
  622. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_bulk_operations.py +443 -0
  623. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_cli.py +106 -0
  624. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_components.py +133 -0
  625. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_config.py +166 -0
  626. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_dashboard.py +130 -0
  627. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_discover_fields.py +207 -0
  628. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_errors.py +61 -0
  629. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_filter_management.py +478 -0
  630. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_issue_archiving.py +181 -0
  631. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_issue_links.py +257 -0
  632. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_issue_properties.py +203 -0
  633. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_link_types.py +426 -0
  634. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_parse_set.py +37 -0
  635. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_permissions.py +358 -0
  636. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_reindex.py +81 -0
  637. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_remote_links.py +292 -0
  638. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_security_schemes.py +218 -0
  639. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_transitions_changelog.py +121 -0
  640. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_user_management.py +283 -0
  641. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_version_management.py +561 -0
  642. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_watchers.py +128 -0
  643. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_worklog.py +265 -0
  644. package/skills/vds-skill/vds-scripts/llms.txt +159 -0
  645. package/skills/vds-skill/vds-scripts/markdown_orchestrator/README.md +72 -0
  646. package/skills/vds-skill/vds-scripts/markdown_orchestrator/pyproject.toml +39 -0
  647. package/skills/vds-skill/vds-scripts/markdown_orchestrator/src/vds_markdown_orchestrator/__init__.py +5 -0
  648. package/skills/vds-skill/vds-scripts/markdown_orchestrator/src/vds_markdown_orchestrator/cli.py +102 -0
  649. package/skills/vds-skill/vds-scripts/mcp_server/Dockerfile +63 -0
  650. package/skills/vds-skill/vds-scripts/mcp_server/README.md +140 -0
  651. package/skills/vds-skill/vds-scripts/mcp_server/pyproject.toml +41 -0
  652. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/__init__.py +3 -0
  653. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/config.py +36 -0
  654. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/server.py +111 -0
  655. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/tools/__init__.py +15 -0
  656. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/tools/bitbucket_tools.py +47 -0
  657. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/tools/confluence_tools.py +53 -0
  658. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/tools/git_tools.py +71 -0
  659. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/tools/jira_tools.py +63 -0
  660. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/tools/vidp_tools.py +64 -0
  661. package/skills/vds-skill/vds-scripts/mcp_server/tests/__init__.py +1 -0
  662. package/skills/vds-skill/vds-scripts/mcp_server/tests/conftest.py +31 -0
  663. package/skills/vds-skill/vds-scripts/mcp_server/tests/unit/__init__.py +1 -0
  664. package/skills/vds-skill/vds-scripts/mcp_server/tests/unit/test_bitbucket_tools.py +28 -0
  665. package/skills/vds-skill/vds-scripts/mcp_server/tests/unit/test_confluence_tools.py +28 -0
  666. package/skills/vds-skill/vds-scripts/mcp_server/tests/unit/test_git_tools.py +35 -0
  667. package/skills/vds-skill/vds-scripts/mcp_server/tests/unit/test_jira_tools.py +35 -0
  668. package/skills/vds-skill/vds-scripts/mcp_server/tests/verification/__init__.py +6 -0
  669. package/skills/vds-skill/vds-scripts/mcp_server/tests/verification/conftest.py +51 -0
  670. package/skills/vds-skill/vds-scripts/mcp_server/tests/verification/test_mcp_confluence_tools.py +40 -0
  671. package/skills/vds-skill/vds-scripts/mcp_server/tests/verification/test_mcp_jira_tools.py +39 -0
  672. package/skills/vds-skill/vds-scripts/mcp_server/tests/verification/test_mcp_tool_registration.py +50 -0
  673. package/skills/vds-skill/vds-scripts/pdf_orchestrator/.dockerignore +93 -0
  674. package/skills/vds-skill/vds-scripts/pdf_orchestrator/.env.example +40 -0
  675. package/skills/vds-skill/vds-scripts/pdf_orchestrator/.ruff_rules.py +350 -0
  676. package/skills/vds-skill/vds-scripts/pdf_orchestrator/.yamllint.yml +43 -0
  677. package/skills/vds-skill/vds-scripts/pdf_orchestrator/DEVELOPMENT_PLAN.md +80 -0
  678. package/skills/vds-skill/vds-scripts/pdf_orchestrator/Dockerfile +87 -0
  679. package/skills/vds-skill/vds-scripts/pdf_orchestrator/README.md +608 -0
  680. package/skills/vds-skill/vds-scripts/pdf_orchestrator/cli_verification_test/test.md +6 -0
  681. package/skills/vds-skill/vds-scripts/pdf_orchestrator/cli_verification_test/test.pdf +0 -0
  682. package/skills/vds-skill/vds-scripts/pdf_orchestrator/config/alertmanager.yml +83 -0
  683. package/skills/vds-skill/vds-scripts/pdf_orchestrator/config/prometheus.prod.yml +98 -0
  684. package/skills/vds-skill/vds-scripts/pdf_orchestrator/config/prometheus.yml +40 -0
  685. package/skills/vds-skill/vds-scripts/pdf_orchestrator/config/redis.conf +78 -0
  686. package/skills/vds-skill/vds-scripts/pdf_orchestrator/docs/COMPETITIVE_ANALYSIS_REPORT.md +309 -0
  687. package/skills/vds-skill/vds-scripts/pdf_orchestrator/docs/FEATURES_GUIDE.md +518 -0
  688. package/skills/vds-skill/vds-scripts/pdf_orchestrator/docs/MULTI_USER_DEPLOYMENT_GUIDE.md +615 -0
  689. package/skills/vds-skill/vds-scripts/pdf_orchestrator/docs/USER_GUIDE.md +829 -0
  690. package/skills/vds-skill/vds-scripts/pdf_orchestrator/pyproject.toml +87 -0
  691. package/skills/vds-skill/vds-scripts/pdf_orchestrator/pytest.ini +71 -0
  692. package/skills/vds-skill/vds-scripts/pdf_orchestrator/ruff.toml +6 -0
  693. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/debug_security_report.py +59 -0
  694. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/demo_library_selector.py +109 -0
  695. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/generate_project_stats.py +52 -0
  696. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/generate_styled_pdf.py +95 -0
  697. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/migrate_render_pdfs.py +285 -0
  698. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/setup_team.bat +283 -0
  699. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/setup_team.sh +324 -0
  700. package/skills/vds-skill/vds-scripts/pdf_orchestrator/src/vds_pdf_orchestrator/__init__.py +5 -0
  701. package/skills/vds-skill/vds-scripts/pdf_orchestrator/src/vds_pdf_orchestrator/cli.py +542 -0
  702. package/skills/vds-skill/vds-scripts/pdf_orchestrator/src/vds_pdf_orchestrator/config.py +33 -0
  703. package/skills/vds-skill/vds-scripts/pdf_orchestrator/tests/README.md +650 -0
  704. package/skills/vds-skill/vds-scripts/pdf_orchestrator/tests/__init__.py +0 -0
  705. package/skills/vds-skill/vds-scripts/pdf_orchestrator/tests/conftest.py +520 -0
  706. package/skills/vds-skill/vds-scripts/pdf_orchestrator/tests/requirements.txt +51 -0
  707. package/skills/vds-skill/vds-scripts/pdf_orchestrator/tests/run_tests.py +659 -0
  708. package/skills/vds-skill/vds-scripts/pdf_orchestrator/tests/test_config.py +36 -0
  709. package/skills/vds-skill/vds-scripts/platform_core/pyproject.toml +49 -0
  710. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/__init__.py +16 -0
  711. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/alembic/__init__.py +18 -0
  712. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/alembic/runtime.py +139 -0
  713. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/config.py +88 -0
  714. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/credentials.py +40 -0
  715. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/env.py +24 -0
  716. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/errors.py +127 -0
  717. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/http/__init__.py +18 -0
  718. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/http/auth.py +32 -0
  719. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/http/errors.py +47 -0
  720. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/http/pagination.py +65 -0
  721. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/http/retry.py +62 -0
  722. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/http/stack.py +61 -0
  723. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/logging.py +132 -0
  724. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/protocols.py +77 -0
  725. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/serialization.py +80 -0
  726. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/severity.py +175 -0
  727. package/skills/vds-skill/vds-scripts/platform_core/tests/__init__.py +0 -0
  728. package/skills/vds-skill/vds-scripts/platform_core/tests/conftest.py +1 -0
  729. package/skills/vds-skill/vds-scripts/platform_core/tests/test_alembic_runtime.py +300 -0
  730. package/skills/vds-skill/vds-scripts/platform_core/tests/test_auth.py +84 -0
  731. package/skills/vds-skill/vds-scripts/platform_core/tests/test_config.py +83 -0
  732. package/skills/vds-skill/vds-scripts/platform_core/tests/test_credentials.py +73 -0
  733. package/skills/vds-skill/vds-scripts/platform_core/tests/test_env.py +56 -0
  734. package/skills/vds-skill/vds-scripts/platform_core/tests/test_errors.py +201 -0
  735. package/skills/vds-skill/vds-scripts/platform_core/tests/test_errors_http.py +74 -0
  736. package/skills/vds-skill/vds-scripts/platform_core/tests/test_http_settings.py +116 -0
  737. package/skills/vds-skill/vds-scripts/platform_core/tests/test_logging.py +148 -0
  738. package/skills/vds-skill/vds-scripts/platform_core/tests/test_pagination.py +153 -0
  739. package/skills/vds-skill/vds-scripts/platform_core/tests/test_protocols.py +132 -0
  740. package/skills/vds-skill/vds-scripts/platform_core/tests/test_retry.py +151 -0
  741. package/skills/vds-skill/vds-scripts/platform_core/tests/test_serialization.py +92 -0
  742. package/skills/vds-skill/vds-scripts/platform_core/tests/test_severity.py +178 -0
  743. package/skills/vds-skill/vds-scripts/platform_core/tests/test_stack.py +130 -0
  744. package/skills/vds-skill/vds-scripts/platform_core/uv.lock +341 -0
  745. package/skills/vds-skill/vds-scripts/pyproject.toml +145 -0
  746. package/skills/vds-skill/vds-scripts/pyrightconfig.json +82 -0
  747. package/skills/vds-skill/vds-scripts/repo-manifest.yaml +380 -0
  748. package/skills/vds-skill/vds-scripts/repo-manifest.yaml.example +25 -0
  749. package/skills/vds-skill/vds-scripts/ruff.toml +100 -0
  750. package/skills/vds-skill/vds-scripts/scripts/BRD-Validation-API.postman_collection.json +706 -0
  751. package/skills/vds-skill/vds-scripts/scripts/BRD-Validation-README.md +308 -0
  752. package/skills/vds-skill/vds-scripts/scripts/README.md +271 -0
  753. package/skills/vds-skill/vds-scripts/scripts/_validate_alias_phase2.py +137 -0
  754. package/skills/vds-skill/vds-scripts/scripts/audit-cli-patterns.sh +135 -0
  755. package/skills/vds-skill/vds-scripts/scripts/audit-dashboard.sh +525 -0
  756. package/skills/vds-skill/vds-scripts/scripts/backup.sh +123 -0
  757. package/skills/vds-skill/vds-scripts/scripts/bootstrap_uv.sh +69 -0
  758. package/skills/vds-skill/vds-scripts/scripts/brd-validation-environment.json +51 -0
  759. package/skills/vds-skill/vds-scripts/scripts/brd-validation-test-results.json +13023 -0
  760. package/skills/vds-skill/vds-scripts/scripts/brd_coverage_report.json +276 -0
  761. package/skills/vds-skill/vds-scripts/scripts/check-future-annotations.py +22 -0
  762. package/skills/vds-skill/vds-scripts/scripts/check-invalid-symlinks.py +183 -0
  763. package/skills/vds-skill/vds-scripts/scripts/check-no-debug-markers.py +21 -0
  764. package/skills/vds-skill/vds-scripts/scripts/check-no-unittest.py +21 -0
  765. package/skills/vds-skill/vds-scripts/scripts/ci/assert_no_openspace_commits.sh +37 -0
  766. package/skills/vds-skill/vds-scripts/scripts/ci/verify_branch_protection.sh +64 -0
  767. package/skills/vds-skill/vds-scripts/scripts/closure/phase1_check.sh +483 -0
  768. package/skills/vds-skill/vds-scripts/scripts/closure/phase2_check.sh +500 -0
  769. package/skills/vds-skill/vds-scripts/scripts/create_memory_session.py +36 -0
  770. package/skills/vds-skill/vds-scripts/scripts/deploy-bootstrap.sh +201 -0
  771. package/skills/vds-skill/vds-scripts/scripts/deployment/load_docker_images_offline.sh +90 -0
  772. package/skills/vds-skill/vds-scripts/scripts/dev/cli_smoke.sh +259 -0
  773. package/skills/vds-skill/vds-scripts/scripts/final_completion_report.md +139 -0
  774. package/skills/vds-skill/vds-scripts/scripts/folder_structure_report.json +321 -0
  775. package/skills/vds-skill/vds-scripts/scripts/generate_completion_report.py +132 -0
  776. package/skills/vds-skill/vds-scripts/scripts/generate_intellij_modules.py +154 -0
  777. package/skills/vds-skill/vds-scripts/scripts/init-pgbouncer-userlist.sh +154 -0
  778. package/skills/vds-skill/vds-scripts/scripts/link_integrity_report.json +807 -0
  779. package/skills/vds-skill/vds-scripts/scripts/move_audit_artifact_pages.py +252 -0
  780. package/skills/vds-skill/vds-scripts/scripts/move_audit_artifact_pages_rest.py +165 -0
  781. package/skills/vds-skill/vds-scripts/scripts/move_wrong_dept_pages.py +235 -0
  782. package/skills/vds-skill/vds-scripts/scripts/openspace_bootstrap.sh +56 -0
  783. package/skills/vds-skill/vds-scripts/scripts/openspace_common.sh +75 -0
  784. package/skills/vds-skill/vds-scripts/scripts/openspace_doctor.sh +61 -0
  785. package/skills/vds-skill/vds-scripts/scripts/openspace_sync_shadow.sh +65 -0
  786. package/skills/vds-skill/vds-scripts/scripts/phase7-baseline.sh +77 -0
  787. package/skills/vds-skill/vds-scripts/scripts/preflight/env_check.sh +102 -0
  788. package/skills/vds-skill/vds-scripts/scripts/repair_autopay_reports.sh +173 -0
  789. package/skills/vds-skill/vds-scripts/scripts/rollback_drill.sh +659 -0
  790. package/skills/vds-skill/vds-scripts/scripts/run-audit-in-tmux.sh +286 -0
  791. package/skills/vds-skill/vds-scripts/scripts/run-department-audit.sh +495 -0
  792. package/skills/vds-skill/vds-scripts/scripts/run-project-audit.sh +267 -0
  793. package/skills/vds-skill/vds-scripts/scripts/save_intellij_memories.py +112 -0
  794. package/skills/vds-skill/vds-scripts/scripts/save_memories_to_vds_ai.py +81 -0
  795. package/skills/vds-skill/vds-scripts/scripts/save_memories_vds_style.py +133 -0
  796. package/skills/vds-skill/vds-scripts/scripts/search_intellij_memories.py +48 -0
  797. package/skills/vds-skill/vds-scripts/scripts/setup_intellij_workspace.py +71 -0
  798. package/skills/vds-skill/vds-scripts/scripts/smoke-test-deploy.sh +137 -0
  799. package/skills/vds-skill/vds-scripts/scripts/smoke_deploy_lib.py +205 -0
  800. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/README.md +89 -0
  801. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/confluence_sync_coordinator.sh +27 -0
  802. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/coordination.sh +114 -0
  803. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/diagram_coordinator.sh +25 -0
  804. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/docs_root.sh +22 -0
  805. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/generate_diagrams.sh +22 -0
  806. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/markdown_coordinator.sh +25 -0
  807. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/progress_dashboard.sh +17 -0
  808. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/schema_coordinator.sh +25 -0
  809. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/sync_confluence.sh +30 -0
  810. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/update_dependencies.sh +19 -0
  811. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/validate_links.sh +86 -0
  812. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/validate_markdown.sh +52 -0
  813. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/validate_schemas.sh +26 -0
  814. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/validate_structure.sh +98 -0
  815. package/skills/vds-skill/vds-scripts/scripts/tests/__init__.py +1 -0
  816. package/skills/vds-skill/vds-scripts/scripts/tests/test_dockerfile_correctness.py +815 -0
  817. package/skills/vds-skill/vds-scripts/scripts/tests/test_makefile_loadouts.py +560 -0
  818. package/skills/vds-skill/vds-scripts/scripts/tests/test_smoke_deploy.py +313 -0
  819. package/skills/vds-skill/vds-scripts/scripts/tests/test_verify_alembic.py +581 -0
  820. package/skills/vds-skill/vds-scripts/scripts/tests/test_verify_infra_topology.py +254 -0
  821. package/skills/vds-skill/vds-scripts/scripts/update_modules_xml.py +194 -0
  822. package/skills/vds-skill/vds-scripts/scripts/uv-workspace-alignment-verification-2026-03-25.md +128 -0
  823. package/skills/vds-skill/vds-scripts/scripts/uv-workspace-alignment-verification-2026-04-18.md +100 -0
  824. package/skills/vds-skill/vds-scripts/scripts/validate-cli-standardization.sh +188 -0
  825. package/skills/vds-skill/vds-scripts/scripts/validate_brd_coverage.py +197 -0
  826. package/skills/vds-skill/vds-scripts/scripts/validate_folder_structure.py +234 -0
  827. package/skills/vds-skill/vds-scripts/scripts/validate_link_integrity.py +274 -0
  828. package/skills/vds-skill/vds-scripts/scripts/vami017-caller-compat-report.md +62 -0
  829. package/skills/vds-skill/vds-scripts/scripts/vami017-phase-b-scaffold-notes.md +79 -0
  830. package/skills/vds-skill/vds-scripts/scripts/vds_sh_helpers.sh +180 -0
  831. package/skills/vds-skill/vds-scripts/scripts/verification/phase2_portable_paths_ubuntu_docker.sh +26 -0
  832. package/skills/vds-skill/vds-scripts/scripts/verify-infra-topology.py +868 -0
  833. package/skills/vds-skill/vds-scripts/scripts/verify-memory-cli-e2e.sh +598 -0
  834. package/skills/vds-skill/vds-scripts/scripts/verify-worktree-features.sh +306 -0
  835. package/skills/vds-skill/vds-scripts/scripts/worktree-add.sh +128 -0
  836. package/skills/vds-skill/vds-scripts/scripts/worktree-remove.sh +112 -0
  837. package/skills/vds-skill/vds-scripts/scripts/worktree_compose.sh +269 -0
  838. package/skills/vds-skill/vds-scripts/scripts/worktree_uv.sh +77 -0
  839. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/IMPLEMENTATION_AUDIT.md +376 -0
  840. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/README.md +507 -0
  841. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/pyproject.toml +106 -0
  842. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/scripts/ensure_symlink.sh +38 -0
  843. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/__init__.py +164 -0
  844. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/batch.py +212 -0
  845. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/cli.py +1407 -0
  846. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/client.py +608 -0
  847. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/config.py +260 -0
  848. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/diff.py +220 -0
  849. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/errors.py +34 -0
  850. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/external_sca.py +932 -0
  851. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/portfolio.py +225 -0
  852. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/pr.py +505 -0
  853. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/reports.py +342 -0
  854. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/scanner.py +351 -0
  855. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/webhooks.py +269 -0
  856. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/__init__.py +0 -0
  857. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/conftest.py +134 -0
  858. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_batch.py +419 -0
  859. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_config.py +145 -0
  860. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_errors.py +78 -0
  861. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_external_sca.py +466 -0
  862. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_pr.py +471 -0
  863. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_reports.py +511 -0
  864. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_webhooks.py +660 -0
  865. package/skills/vds-skill/vds-scripts/uv.lock +5046 -0
  866. package/skills/vds-skill/vds-scripts/vds_agent_core/CHANGELOG.md +36 -0
  867. package/skills/vds-skill/vds-scripts/vds_agent_core/README.md +453 -0
  868. package/skills/vds-skill/vds-scripts/vds_agent_core/docs/PHASE9A_ASSESSMENT.md +50 -0
  869. package/skills/vds-skill/vds-scripts/vds_agent_core/docs/embedding.md +468 -0
  870. package/skills/vds-skill/vds-scripts/vds_agent_core/pyproject.toml +51 -0
  871. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/__init__.py +29 -0
  872. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/agents/__init__.py +26 -0
  873. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/agents/hooks.py +119 -0
  874. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/agents/loop.py +864 -0
  875. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/agents/tools.py +41 -0
  876. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/config.py +252 -0
  877. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/__init__.py +55 -0
  878. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/_cascade.py +143 -0
  879. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/budget.py +353 -0
  880. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/cache.py +373 -0
  881. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/embedding.py +815 -0
  882. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/provider.py +173 -0
  883. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/schemas.py +45 -0
  884. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/observability/__init__.py +77 -0
  885. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/observability/decorators.py +258 -0
  886. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/observability/jsonl_exporter.py +236 -0
  887. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/observability/tracer.py +497 -0
  888. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/profiles.py +2015 -0
  889. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/runtime/__init__.py +0 -0
  890. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/runtime/agent_id.py +60 -0
  891. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/security/__init__.py +13 -0
  892. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/security/credentials.py +106 -0
  893. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/skills/__init__.py +1 -0
  894. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/skills/executor.py +238 -0
  895. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/skills/manager.py +381 -0
  896. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/skills/policy.py +568 -0
  897. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/workflows/__init__.py +19 -0
  898. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/workflows/langgraph_runner.py +102 -0
  899. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/workflows/protocols.py +81 -0
  900. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/__init__.py +0 -0
  901. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/conftest.py +62 -0
  902. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/integration/__init__.py +0 -0
  903. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/integration/test_audit_loop_hooks_integration.py +135 -0
  904. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/integration/test_audit_observability_integration.py +246 -0
  905. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/integration/test_public_api_stability.py +91 -0
  906. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/__init__.py +0 -0
  907. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/__init__.py +0 -0
  908. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_call_site_parallelism.py +30 -0
  909. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_dimension_guardrail.py +25 -0
  910. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_drop_in_provider_extensibility.py +76 -0
  911. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_embedding.py +393 -0
  912. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_embedding_cache.py +302 -0
  913. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_embedding_extra.py +696 -0
  914. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_embedding_subclass.py +49 -0
  915. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_no_provider_leakage_in_env.py +34 -0
  916. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_provider_auto_route.py +48 -0
  917. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_runtime_log_clean.py +111 -0
  918. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_w7_logic_fixes.py +219 -0
  919. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/profiles/__init__.py +0 -0
  920. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/profiles/test_embedding_block_parser.py +194 -0
  921. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/profiles/test_env_resolver_allowlist.py +141 -0
  922. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/profiles/test_profile_authorization.py +158 -0
  923. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/profiles/test_profiles_w3_extra.py +547 -0
  924. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/profiles/test_real_audit_profile_compat.py +129 -0
  925. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/runtime/__init__.py +0 -0
  926. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/runtime/test_for_agent.py +322 -0
  927. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/runtime/test_w9_cascade_edges.py +369 -0
  928. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/security/__init__.py +0 -0
  929. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/security/test_credentials.py +132 -0
  930. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_agent_loop.py +663 -0
  931. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_agent_loop_coverage.py +429 -0
  932. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_agents_hooks_defaults.py +22 -0
  933. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_budget.py +155 -0
  934. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_budget_coverage.py +264 -0
  935. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_budget_tracking_only.py +71 -0
  936. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_cache.py +251 -0
  937. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_cache_context.py +62 -0
  938. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_config.py +155 -0
  939. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_langgraph_runner.py +45 -0
  940. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_langgraph_runner_coverage.py +98 -0
  941. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_llm_cache_deep.py +113 -0
  942. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_observability_decorators.py +697 -0
  943. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_observability_hooks.py +217 -0
  944. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_observability_jsonl_exporter.py +542 -0
  945. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_observability_jsonl_wiring.py +313 -0
  946. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_observability_tracer.py +896 -0
  947. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_profiles.py +1571 -0
  948. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_profiles_coverage.py +444 -0
  949. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_provider.py +316 -0
  950. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_schemas.py +63 -0
  951. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_skill_executor.py +297 -0
  952. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_skill_manager.py +370 -0
  953. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_skill_manager_coverage.py +364 -0
  954. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_skill_policy.py +402 -0
  955. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_skill_rubric.py +47 -0
  956. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_tools.py +51 -0
  957. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_workflow_protocols.py +136 -0
  958. package/skills/vds-skill/vds-scripts/vds_cli/README.md +201 -0
  959. package/skills/vds-skill/vds-scripts/vds_cli/VERIFICATION_REPORT.md +41 -0
  960. package/skills/vds-skill/vds-scripts/vds_cli/pyproject.toml +50 -0
  961. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/__init__.py +3 -0
  962. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/assets/git-credential-helper.py +235 -0
  963. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/cli.py +1126 -0
  964. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/commands/__init__.py +1 -0
  965. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/commands/lint_cli.py +389 -0
  966. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/confluence_sync.py +846 -0
  967. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs/consumption/__init__.py +7 -0
  968. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs/consumption/funnel.py +105 -0
  969. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs/consumption/scanner.py +211 -0
  970. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs/freshness/report.py +90 -0
  971. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs/types.py +27 -0
  972. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs_cmd.py +672 -0
  973. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs_metrics.py +75 -0
  974. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs_sync.py +1171 -0
  975. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/ecosystem/__init__.py +39 -0
  976. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/ecosystem/report.py +439 -0
  977. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/ecosystem_docs.py +164 -0
  978. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/env.py +111 -0
  979. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/env_git_helper.py +281 -0
  980. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/google_sheets_orchestrator/__init__.py +3 -0
  981. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/google_sheets_orchestrator/google_sheets_orchestrator.py +173 -0
  982. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/router.py +232 -0
  983. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/skills_cmd.py +274 -0
  984. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/sync_api.py +613 -0
  985. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/sync_service.py +283 -0
  986. package/skills/vds-skill/vds-scripts/vds_cli/tests/conftest.py +62 -0
  987. package/skills/vds-skill/vds-scripts/vds_cli/tests/test_env_git_helper_lifecycle.py +261 -0
  988. package/skills/vds-skill/vds-scripts/vds_cli/tests/test_git_credential_helper.py +240 -0
  989. package/skills/vds-skill/vds-scripts/vds_cli/tests/test_router_help_proxy.py +43 -0
  990. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_cli.py +241 -0
  991. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_cli_DOC004.py +110 -0
  992. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_confluence_sync.py +315 -0
  993. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_confluence_sync_wave7.py +375 -0
  994. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_consumption_funnel.py +106 -0
  995. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_consumption_scanner.py +144 -0
  996. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_docs_cmd.py +89 -0
  997. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_docs_cmd_wave8.py +161 -0
  998. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_docs_metrics.py +16 -0
  999. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_docs_quality_score.py +61 -0
  1000. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_docs_sync.py +417 -0
  1001. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_ecosystem_cli_dashboard.py +667 -0
  1002. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_ecosystem_cli_dashboard_rendering.py +143 -0
  1003. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_ecosystem_docs.py +63 -0
  1004. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_env.py +85 -0
  1005. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_freshness_report.py +125 -0
  1006. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_lint_cli.py +224 -0
  1007. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_router.py +101 -0
  1008. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_skills_cmd.py +419 -0
  1009. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_sync_api.py +357 -0
  1010. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_sync_service.py +170 -0
  1011. package/skills/vds-skill/vds-scripts/vds_cli/tests/verification/conftest.py +51 -0
  1012. package/skills/vds-skill/vds-scripts/vds_cli/tests/verification/test_bitbucket_real.py +32 -0
  1013. package/skills/vds-skill/vds-scripts/vds_cli/tests/verification/test_confluence_real.py +32 -0
  1014. package/skills/vds-skill/vds-scripts/vds_cli/tests/verification/test_jira_real.py +40 -0
  1015. package/skills/vds-skill/vds-scripts/vds_cli_common/README.md +190 -0
  1016. package/skills/vds-skill/vds-scripts/vds_cli_common/pyproject.toml +96 -0
  1017. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/__init__.py +36 -0
  1018. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/app.py +55 -0
  1019. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/completers.py +139 -0
  1020. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/context.py +201 -0
  1021. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/env.py +163 -0
  1022. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/errors.py +440 -0
  1023. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/output.py +284 -0
  1024. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/paths.py +78 -0
  1025. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/testing.py +211 -0
  1026. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/version.py +85 -0
  1027. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/__init__.py +0 -0
  1028. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_app.py +126 -0
  1029. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_completers.py +148 -0
  1030. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_context.py +192 -0
  1031. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_env.py +235 -0
  1032. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_errors.py +275 -0
  1033. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_output.py +229 -0
  1034. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_paths.py +61 -0
  1035. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_testing.py +138 -0
  1036. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_version.py +64 -0
  1037. package/skills/vds-skill/vds-scripts/vidp_orchestrator/README.md +31 -0
  1038. package/skills/vds-skill/vds-scripts/vidp_orchestrator/pyproject.toml +50 -0
  1039. package/skills/vds-skill/vds-scripts/vidp_orchestrator/src/vds_vidp_orchestrator/__init__.py +26 -0
  1040. package/skills/vds-skill/vds-scripts/vidp_orchestrator/src/vds_vidp_orchestrator/cli.py +246 -0
  1041. package/skills/vds-skill/vds-scripts/vidp_orchestrator/src/vds_vidp_orchestrator/client.py +104 -0
  1042. package/skills/vds-skill/vds-scripts/vidp_orchestrator/src/vds_vidp_orchestrator/config.py +82 -0
  1043. package/skills/vds-skill/vds-scripts/vidp_orchestrator/src/vds_vidp_orchestrator/workflows.json +3 -0
  1044. package/skills/vds-skill/vds-scripts/vidp_orchestrator/src/vds_vidp_orchestrator/workflows.py +130 -0
@@ -0,0 +1,2252 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import threading
6
+ import time
7
+ import zipfile
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ from confluence_orchestrator.cli import app
12
+ from confluence_orchestrator.crawl_tree import CrawlOptions, CrawlState, _create_bundle_zip, crawl_tree_to_disk
13
+ from typer.testing import CliRunner
14
+
15
+ if TYPE_CHECKING:
16
+ import pytest
17
+
18
+ runner = CliRunner()
19
+
20
+
21
+ def test_crawl_options_phase80_defaults() -> None:
22
+ options = CrawlOptions()
23
+ assert options.max_depth == 3
24
+ assert options.max_nodes == 500
25
+ assert options.page_concurrency == 5
26
+
27
+
28
+ def test_crawl_tree_downloads_pages_and_attachments(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
29
+ class DummyClient:
30
+ def search_cql(self, cql: str, limit: int = 25, expand: list[str] | None = None, **_: Any) -> dict[str, Any]:
31
+ raise AssertionError(f"search_cql should not be called for numeric root (got {cql})")
32
+
33
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
34
+ assert page_id == "1"
35
+ assert expand is not None
36
+ return {
37
+ "id": page_id,
38
+ "title": "Root",
39
+ "body": {
40
+ "view": {
41
+ "value": (
42
+ "<h1>Intro</h1>"
43
+ "<p>Hello</p>"
44
+ '<a class="confluence-userlink" data-username="john.doe" href="/display/~john.doe">@John Doe</a>'
45
+ "<table><tr><th>H1</th><th>H2</th></tr><tr><td>A</td><td>B</td></tr></table>"
46
+ '<a href="/display/X/Y">Y</a>'
47
+ '<a href="/pages/viewpage.action?pageId=777">P777</a>'
48
+ '<a href="/pages/888/Test">P888</a>'
49
+ )
50
+ },
51
+ "storage": {"value": "<p>fallback</p>"},
52
+ },
53
+ "_links": {"base": "http://example", "webui": "/display/SPACE/Root"},
54
+ "space": {"key": "SPACE"},
55
+ "version": {"number": 1, "when": "2026-01-01T00:00:00.000+00:00"},
56
+ "ancestors": [{"id": "10", "title": "Ancestor"}],
57
+ }
58
+
59
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
60
+ assert page_id == "1"
61
+ return []
62
+
63
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
64
+ assert page_id == "1"
65
+ return [
66
+ {
67
+ "id": "a1",
68
+ "title": "file.txt",
69
+ "version": {"number": 2},
70
+ "extensions": {"fileSize": 3},
71
+ }
72
+ ]
73
+
74
+ def get_attachment_content(self, attachment_id: str) -> bytes:
75
+ assert attachment_id == "a1"
76
+ return b"abc"
77
+
78
+ dummy = DummyClient()
79
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: dummy)
80
+
81
+ out_dir = tmp_path / "out"
82
+ result = runner.invoke(
83
+ app,
84
+ [
85
+ "--report-dir",
86
+ str(tmp_path),
87
+ "crawl-tree",
88
+ "1",
89
+ "--out",
90
+ str(out_dir),
91
+ "--depth",
92
+ "0",
93
+ "--emit-corpus",
94
+ "--emit-link-graph",
95
+ "--emit-tables",
96
+ "--emit-mentions",
97
+ ],
98
+ )
99
+
100
+ assert result.exit_code == 0, result.output
101
+ payload = json.loads(result.stdout)
102
+
103
+ assert payload["root_page_id"] == "1"
104
+ assert (out_dir / "1_Root.html").exists()
105
+ assert (out_dir / "attachments" / "a1_file.txt").exists()
106
+ assert payload["corpus_path"]
107
+ assert payload["link_graph_path"]
108
+ assert payload["tables_path"]
109
+ assert payload["mentions_path"]
110
+
111
+ corpus_records = [
112
+ json.loads(line)
113
+ for line in Path(payload["corpus_path"]).read_text(encoding="utf-8").splitlines()
114
+ if line.strip()
115
+ ]
116
+ assert corpus_records
117
+ assert corpus_records[0]["depth"] == 0
118
+ assert corpus_records[0]["parent_id"] is None
119
+ assert corpus_records[0]["ancestors"] == ["10"]
120
+ assert corpus_records[0]["ancestor_titles"] == ["Ancestor"]
121
+
122
+ tables_path = Path(payload["tables_path"])
123
+ mentions_path = Path(payload["mentions_path"])
124
+ assert tables_path.exists()
125
+ assert mentions_path.exists()
126
+
127
+ table_record = json.loads(tables_path.read_text(encoding="utf-8").splitlines()[0])
128
+ assert table_record["page_id"] == "1"
129
+ assert table_record["table_index"] == 0
130
+ assert table_record["headers"] == ["H1", "H2"]
131
+ assert table_record["rows"] == [["A", "B"]]
132
+
133
+ mention_records = [
134
+ json.loads(line) for line in mentions_path.read_text(encoding="utf-8").splitlines() if line.strip()
135
+ ]
136
+ assert mention_records
137
+ assert all(record["page_id"] == "1" for record in mention_records)
138
+ assert any(record.get("type") == "confluence_page" and record.get("value") == "777" for record in mention_records)
139
+
140
+ link_graph = json.loads(Path(payload["link_graph_path"]).read_text(encoding="utf-8"))
141
+ edges = link_graph["edges"]
142
+ enriched = {edge.get("target_page_id"): edge for edge in edges if edge.get("target_page_id")}
143
+ assert enriched["777"]["target_type"] == "confluence_page"
144
+ assert enriched["888"]["target_type"] == "confluence_page"
145
+
146
+
147
+ def test_crawl_tree_appends_attachment_suffix_when_missing(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
148
+ class DummyClient:
149
+ def search_cql(self, cql: str, limit: int = 25, expand: list[str] | None = None, **_: Any) -> dict[str, Any]:
150
+ raise AssertionError(f"search_cql should not be called for numeric root (got {cql})")
151
+
152
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
153
+ assert page_id == "1"
154
+ return {
155
+ "id": page_id,
156
+ "title": "Root",
157
+ "body": {"view": {"value": "<p>ok</p>"}, "storage": {"value": ""}},
158
+ "_links": {"base": "http://example", "webui": "/display/SPACE/Root"},
159
+ }
160
+
161
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
162
+ return []
163
+
164
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
165
+ return [
166
+ {
167
+ "id": "a2",
168
+ "title": "diagram",
169
+ "extensions": {"fileSize": 3, "mediaType": "application/pdf"},
170
+ }
171
+ ]
172
+
173
+ def get_attachment_content(self, attachment_id: str) -> bytes:
174
+ assert attachment_id == "a2"
175
+ return b"abc"
176
+
177
+ dummy = DummyClient()
178
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: dummy)
179
+
180
+ out_dir = tmp_path / "out"
181
+ result = runner.invoke(
182
+ app,
183
+ [
184
+ "--report-dir",
185
+ str(tmp_path),
186
+ "crawl-tree",
187
+ "1",
188
+ "--out",
189
+ str(out_dir),
190
+ "--depth",
191
+ "0",
192
+ "--emit-corpus",
193
+ ],
194
+ )
195
+
196
+ assert result.exit_code == 0, result.output
197
+ assert (out_dir / "attachments" / "a2_diagram.pdf").exists()
198
+
199
+
200
+ def test_crawl_tree_infers_server_from_root_url(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
201
+ class DummyClient:
202
+ def search_cql(self, cql: str, limit: int = 25, expand: list[str] | None = None, **_: Any) -> dict[str, Any]:
203
+ raise AssertionError("search_cql should not be called for pageId URL root")
204
+
205
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
206
+ assert page_id == "1"
207
+ return {
208
+ "id": page_id,
209
+ "title": "Root",
210
+ "body": {"view": {"value": "<p>ok</p>"}, "storage": {"value": ""}},
211
+ "_links": {"base": "http://example", "webui": "/display/SPACE/Root"},
212
+ }
213
+
214
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
215
+ return []
216
+
217
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
218
+ return []
219
+
220
+ def get_attachment_content(self, attachment_id: str) -> bytes:
221
+ raise AssertionError("no attachments expected")
222
+
223
+ dummy = DummyClient()
224
+ observed: dict[str, str] = {}
225
+
226
+ def _build_http_client(settings, server: str):
227
+ observed["server"] = server
228
+ return dummy
229
+
230
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", _build_http_client)
231
+
232
+ out_dir = tmp_path / "out"
233
+ result = runner.invoke(
234
+ app,
235
+ [
236
+ "--report-dir",
237
+ str(tmp_path),
238
+ "crawl-tree",
239
+ "http://10.254.136.35:8090/pages/viewpage.action?pageId=1",
240
+ "--out",
241
+ str(out_dir),
242
+ "--depth",
243
+ "0",
244
+ "--no-include-attachments",
245
+ ],
246
+ )
247
+
248
+ assert result.exit_code == 0, result.output
249
+ # 10.254.136.35 is one of the two old external Confluence servers — both map to "external"
250
+ assert observed["server"] == "external"
251
+
252
+
253
+ def test_crawl_tree_does_not_emit_tables_or_mentions_by_default(
254
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
255
+ ) -> None:
256
+ class DummyClient:
257
+ def search_cql(self, cql: str, limit: int = 25, expand: list[str] | None = None, **_: Any) -> dict[str, Any]:
258
+ raise AssertionError(f"search_cql should not be called for numeric root (got {cql})")
259
+
260
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
261
+ assert page_id == "1"
262
+ return {
263
+ "id": page_id,
264
+ "title": "Root",
265
+ "body": {
266
+ "view": {
267
+ "value": (
268
+ '<a class="confluence-userlink" data-username="john.doe">@John Doe</a>'
269
+ "<table><tr><td>only</td></tr></table>"
270
+ )
271
+ },
272
+ "storage": {"value": ""},
273
+ },
274
+ "_links": {"base": "http://example", "webui": "/display/SPACE/Root"},
275
+ }
276
+
277
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
278
+ return []
279
+
280
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
281
+ return []
282
+
283
+ def get_attachment_content(self, attachment_id: str) -> bytes:
284
+ raise AssertionError("no attachments expected")
285
+
286
+ dummy = DummyClient()
287
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: dummy)
288
+
289
+ out_dir = tmp_path / "out"
290
+ result = runner.invoke(
291
+ app,
292
+ [
293
+ "--report-dir",
294
+ str(tmp_path),
295
+ "crawl-tree",
296
+ "1",
297
+ "--out",
298
+ str(out_dir),
299
+ "--depth",
300
+ "0",
301
+ "--no-include-attachments",
302
+ ],
303
+ )
304
+
305
+ assert result.exit_code == 0, result.output
306
+ payload = json.loads(result.stdout)
307
+ assert payload["tables_path"] is None
308
+ assert payload["mentions_path"] is None
309
+ assert not (out_dir / "tables.jsonl").exists()
310
+ assert not (out_dir / "mentions.jsonl").exists()
311
+
312
+
313
+ def test_crawl_tree_resolves_display_url(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
314
+ class DummyClient:
315
+ def search_cql(self, cql: str, limit: int = 25, expand: list[str] | None = None, **_: Any) -> dict[str, Any]:
316
+ assert 'space="TDOV"' in cql
317
+ assert 'title="My Page"' in cql
318
+ return {"results": [{"id": "42"}]}
319
+
320
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
321
+ assert page_id == "42"
322
+ return {
323
+ "id": page_id,
324
+ "title": "My Page",
325
+ "body": {"storage": {"value": "<p>ok</p>"}, "view": {"value": ""}},
326
+ "_links": {"base": "http://example", "webui": "/display/TDOV/My+Page"},
327
+ }
328
+
329
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
330
+ return []
331
+
332
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
333
+ return []
334
+
335
+ def get_attachment_content(self, attachment_id: str) -> bytes:
336
+ raise AssertionError("no attachments expected")
337
+
338
+ dummy = DummyClient()
339
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: dummy)
340
+
341
+ out_dir = tmp_path / "out"
342
+ result = runner.invoke(
343
+ app,
344
+ [
345
+ "--report-dir",
346
+ str(tmp_path),
347
+ "crawl-tree",
348
+ "http://example/display/TDOV/My+Page",
349
+ "--out",
350
+ str(out_dir),
351
+ "--depth",
352
+ "0",
353
+ "--no-include-attachments",
354
+ ],
355
+ )
356
+
357
+ assert result.exit_code == 0, result.output
358
+ payload = json.loads(result.stdout)
359
+ assert payload["root_page_id"] == "42"
360
+ assert (out_dir / "42_My Page.html").exists()
361
+
362
+
363
+ def test_crawl_tree_splits_corpus_chunks_by_max_chars(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
364
+ long_text = "word " * 200 # > 50 chars
365
+
366
+ class DummyClient:
367
+ def search_cql(self, cql: str, limit: int = 25, expand: list[str] | None = None, **_: Any) -> dict[str, Any]:
368
+ raise AssertionError(f"search_cql should not be called for numeric root (got {cql})")
369
+
370
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
371
+ assert page_id == "1"
372
+ return {
373
+ "id": page_id,
374
+ "title": "Root",
375
+ "body": {"view": {"value": f"<h1>Intro</h1><p>{long_text}</p>"}, "storage": {"value": ""}},
376
+ "_links": {"base": "http://example", "webui": "/display/SPACE/Root"},
377
+ }
378
+
379
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
380
+ return []
381
+
382
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
383
+ return []
384
+
385
+ def get_attachment_content(self, attachment_id: str) -> bytes:
386
+ raise AssertionError("no attachments expected")
387
+
388
+ dummy = DummyClient()
389
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: dummy)
390
+
391
+ out_dir = tmp_path / "out"
392
+ result = runner.invoke(
393
+ app,
394
+ [
395
+ "--report-dir",
396
+ str(tmp_path),
397
+ "crawl-tree",
398
+ "1",
399
+ "--out",
400
+ str(out_dir),
401
+ "--depth",
402
+ "0",
403
+ "--no-include-attachments",
404
+ "--emit-corpus",
405
+ "--chunk-max-chars",
406
+ "50",
407
+ ],
408
+ )
409
+
410
+ assert result.exit_code == 0, result.output
411
+ payload = json.loads(result.stdout)
412
+ corpus_path = Path(payload["corpus_path"])
413
+ corpus = [json.loads(line) for line in corpus_path.read_text(encoding="utf-8").splitlines() if line.strip()]
414
+ assert len(corpus) > 1
415
+ assert all(len(entry.get("text") or "") <= 50 for entry in corpus)
416
+ assert {entry.get("chunk_ordinal") for entry in corpus} == set(range(len(corpus)))
417
+ assert corpus[0]["source_type"] == "confluence"
418
+ assert corpus[0]["doc_class"] == "canonical_doc"
419
+ assert corpus[0]["generated_flag"] is False
420
+ assert corpus[0]["historical_flag"] is False
421
+ assert corpus[0]["discovery"] == "confluence_crawl_page"
422
+
423
+ base_key = "1|Intro"
424
+ expected_first = hashlib.sha256(base_key.encode("utf-8")).hexdigest()[:16]
425
+ expected_second = hashlib.sha256(f"{base_key}|1".encode()).hexdigest()[:16]
426
+ assert corpus[0]["chunk_id"] == expected_first
427
+ assert corpus[1]["chunk_id"] == expected_second
428
+
429
+
430
+ def test_crawl_tree_uses_export_view_when_requested(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
431
+ class DummyClient:
432
+ def search_cql(self, cql: str, limit: int = 25, expand: list[str] | None = None, **_: Any) -> dict[str, Any]:
433
+ raise AssertionError(f"search_cql should not be called for numeric root (got {cql})")
434
+
435
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
436
+ assert page_id == "1"
437
+ assert expand is not None
438
+ assert "body.export_view" in expand
439
+ return {
440
+ "id": page_id,
441
+ "title": "Root",
442
+ "body": {
443
+ "view": {"value": "<p>view</p>"},
444
+ "export_view": {"value": "<p>export</p>"},
445
+ "storage": {"value": "<p>storage</p>"},
446
+ },
447
+ "_links": {"base": "http://example", "webui": "/display/SPACE/Root"},
448
+ }
449
+
450
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
451
+ return []
452
+
453
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
454
+ return []
455
+
456
+ def get_attachment_content(self, attachment_id: str) -> bytes:
457
+ raise AssertionError("no attachments expected")
458
+
459
+ dummy = DummyClient()
460
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: dummy)
461
+
462
+ out_dir = tmp_path / "out"
463
+ result = runner.invoke(
464
+ app,
465
+ [
466
+ "--report-dir",
467
+ str(tmp_path),
468
+ "crawl-tree",
469
+ "1",
470
+ "--out",
471
+ str(out_dir),
472
+ "--depth",
473
+ "0",
474
+ "--no-include-attachments",
475
+ "--body-format",
476
+ "export_view",
477
+ ],
478
+ )
479
+
480
+ assert result.exit_code == 0, result.output
481
+ payload = json.loads(result.stdout)
482
+ html_path = Path(payload["page"]["html_path"])
483
+ assert "export" in html_path.read_text(encoding="utf-8")
484
+ assert payload["page"]["source_format"] == "html-export-view"
485
+
486
+
487
+ def test_crawl_tree_concurrency_preserves_deterministic_order(tmp_path: Path) -> None:
488
+ class DummyClient:
489
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
490
+ raise AssertionError("search_cql should not be called")
491
+
492
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
493
+ # Force non-deterministic completion order when concurrency > 1.
494
+ if page_id == "2":
495
+ time.sleep(0.04)
496
+ if page_id == "3":
497
+ time.sleep(0.01)
498
+ return {
499
+ "id": page_id,
500
+ "title": f"Page {page_id}",
501
+ "body": {"view": {"value": f"<h1>{page_id}</h1><p>Content {page_id}</p>"}},
502
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
503
+ "version": {"number": int(page_id)},
504
+ }
505
+
506
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
507
+ if page_id == "1":
508
+ return [{"id": "2"}, {"id": "3"}]
509
+ return []
510
+
511
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
512
+ return []
513
+
514
+ def get_attachment_content(self, attachment_id: str) -> bytes:
515
+ return b""
516
+
517
+ client = DummyClient()
518
+ sequential = crawl_tree_to_disk(
519
+ http=client,
520
+ root_ref="1",
521
+ output_dir=tmp_path / "seq",
522
+ options=CrawlOptions(max_depth=1, include_attachments=False, emit_corpus=True, page_concurrency=1),
523
+ )
524
+ concurrent = crawl_tree_to_disk(
525
+ http=client,
526
+ root_ref="1",
527
+ output_dir=tmp_path / "concurrent",
528
+ options=CrawlOptions(max_depth=1, include_attachments=False, emit_corpus=True, page_concurrency=4),
529
+ )
530
+
531
+ assert [p["page_id"] for p in sequential["child_pages"]] == ["2", "3"]
532
+ assert [p["page_id"] for p in concurrent["child_pages"]] == ["2", "3"]
533
+
534
+ seq_corpus = Path(sequential["corpus_path"]).read_text(encoding="utf-8").splitlines()
535
+ conc_corpus = Path(concurrent["corpus_path"]).read_text(encoding="utf-8").splitlines()
536
+ assert [json.loads(line)["page_id"] for line in seq_corpus] == [json.loads(line)["page_id"] for line in conc_corpus]
537
+
538
+
539
+ def test_crawl_tree_parallelizes_child_discovery_with_page_fetch(tmp_path: Path) -> None:
540
+ lock = threading.Lock()
541
+ overlap_counter = 0
542
+ active_page_calls = 0
543
+ active_children_calls = 0
544
+
545
+ class DummyClient:
546
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
547
+ raise AssertionError("search_cql should not be called")
548
+
549
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
550
+ nonlocal overlap_counter, active_page_calls, active_children_calls
551
+ with lock:
552
+ active_page_calls += 1
553
+ if active_children_calls > 0:
554
+ overlap_counter += 1
555
+ time.sleep(0.03)
556
+ with lock:
557
+ active_page_calls -= 1
558
+ return {
559
+ "id": page_id,
560
+ "title": f"Page {page_id}",
561
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
562
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
563
+ "version": {"number": int(page_id)},
564
+ }
565
+
566
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
567
+ nonlocal overlap_counter, active_page_calls, active_children_calls
568
+ with lock:
569
+ active_children_calls += 1
570
+ if active_page_calls > 0:
571
+ overlap_counter += 1
572
+ time.sleep(0.03)
573
+ with lock:
574
+ active_children_calls -= 1
575
+ if page_id == "1":
576
+ return [{"id": "2"}]
577
+ return []
578
+
579
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
580
+ return []
581
+
582
+ def get_attachment_content(self, attachment_id: str) -> bytes:
583
+ return b""
584
+
585
+ result = crawl_tree_to_disk(
586
+ http=DummyClient(),
587
+ root_ref="1",
588
+ output_dir=tmp_path / "out",
589
+ options=CrawlOptions(max_depth=1, include_attachments=False, page_concurrency=1),
590
+ )
591
+
592
+ assert result["child_pages"][0]["page_id"] == "2"
593
+ assert overlap_counter > 0
594
+
595
+
596
+ def test_crawl_tree_concurrent_throughput_exceeds_2x_sequential(tmp_path: Path) -> None:
597
+ class DummyClient:
598
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
599
+ raise AssertionError("search_cql should not be called")
600
+
601
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
602
+ time.sleep(0.08)
603
+ return {
604
+ "id": page_id,
605
+ "title": f"Page {page_id}",
606
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
607
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
608
+ "version": {"number": int(page_id)},
609
+ }
610
+
611
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
612
+ if page_id == "1":
613
+ return [{"id": str(i)} for i in range(2, 8)]
614
+ return []
615
+
616
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
617
+ return []
618
+
619
+ def get_attachment_content(self, attachment_id: str) -> bytes:
620
+ return b""
621
+
622
+ client = DummyClient()
623
+ start = time.perf_counter()
624
+ crawl_tree_to_disk(
625
+ http=client,
626
+ root_ref="1",
627
+ output_dir=tmp_path / "seq",
628
+ options=CrawlOptions(max_depth=1, include_attachments=False, page_concurrency=1),
629
+ )
630
+ sequential_elapsed = time.perf_counter() - start
631
+
632
+ start = time.perf_counter()
633
+ crawl_tree_to_disk(
634
+ http=client,
635
+ root_ref="1",
636
+ output_dir=tmp_path / "concurrent",
637
+ options=CrawlOptions(max_depth=1, include_attachments=False, page_concurrency=5),
638
+ )
639
+ concurrent_elapsed = time.perf_counter() - start
640
+
641
+ assert concurrent_elapsed * 2 <= sequential_elapsed
642
+
643
+
644
+ def test_crawl_tree_structure_first_mode_pre_discovers_before_fetch(tmp_path: Path) -> None:
645
+ call_log: list[tuple[str, str]] = []
646
+
647
+ class DummyClient:
648
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
649
+ raise AssertionError("search_cql should not be called")
650
+
651
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
652
+ call_log.append(("page", page_id))
653
+ return {
654
+ "id": page_id,
655
+ "title": f"Page {page_id}",
656
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
657
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
658
+ "version": {"number": int(page_id)},
659
+ }
660
+
661
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
662
+ call_log.append(("children", page_id))
663
+ if page_id == "1":
664
+ return [{"id": "2"}, {"id": "3"}]
665
+ if page_id == "2":
666
+ return [{"id": "4"}]
667
+ return []
668
+
669
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
670
+ return []
671
+
672
+ def get_attachment_content(self, attachment_id: str) -> bytes:
673
+ return b""
674
+
675
+ result = crawl_tree_to_disk(
676
+ http=DummyClient(),
677
+ root_ref="1",
678
+ output_dir=tmp_path / "out",
679
+ options=CrawlOptions(
680
+ max_depth=2,
681
+ include_attachments=False,
682
+ page_concurrency=2,
683
+ staged_mode="structure_first",
684
+ max_nodes=2,
685
+ ),
686
+ )
687
+
688
+ first_page_call_index = next(index for index, event in enumerate(call_log) if event[0] == "page")
689
+ assert all(event[0] == "children" for event in call_log[:first_page_call_index])
690
+ assert call_log[0] == ("children", "1")
691
+ assert [page["page_id"] for page in result["child_pages"]] == ["2"]
692
+ assert result["resume_state"]["termination_reason"] == "max_nodes_reached"
693
+ assert result["resume_state"]["pending_queue_size"] > 0
694
+ assert result["crawl_mode"]["staged_mode"] == "structure_first"
695
+ assert result["crawl_mode"]["page_concurrency"] == 2
696
+
697
+
698
+ # =============================================================================
699
+ # TSK-188: Resume/Incremental Crawling Tests
700
+ # =============================================================================
701
+
702
+
703
+ def test_crawl_state_serialization() -> None:
704
+ """Test CrawlState can serialize and deserialize correctly."""
705
+ state = CrawlState(
706
+ root_page_id="123",
707
+ body_format="view",
708
+ visited_pages={
709
+ "123": {"version": 5, "html_sha256": "abc123", "visited_at": "2026-01-01T00:00:00Z"},
710
+ "456": {"version": 3, "html_sha256": "def456", "visited_at": "2026-01-01T00:01:00Z"},
711
+ },
712
+ pending_queue=[("789", "123", 1), ("101", "123", 1)],
713
+ started_at="2026-01-01T00:00:00Z",
714
+ last_updated_at="2026-01-01T00:05:00Z",
715
+ completed=False,
716
+ last_sync_timestamp="2026-01-01T00:05:00Z",
717
+ last_full_reconciliation_timestamp="2026-01-01T00:00:00Z",
718
+ )
719
+
720
+ # Serialize
721
+ data = state.to_dict()
722
+ assert data["root_page_id"] == "123"
723
+ assert len(data["visited_pages"]) == 2
724
+ assert len(data["pending_queue"]) == 2
725
+ assert data["completed"] is False
726
+ assert data["last_sync_timestamp"] == "2026-01-01T00:05:00Z"
727
+ assert data["last_full_reconciliation_timestamp"] == "2026-01-01T00:00:00Z"
728
+
729
+ # Deserialize
730
+ restored = CrawlState.from_dict(data)
731
+ assert restored.root_page_id == "123"
732
+ assert restored.body_format == "view"
733
+ assert len(restored.visited_pages) == 2
734
+ assert restored.visited_pages["123"]["version"] == 5
735
+ assert len(restored.pending_queue) == 2
736
+ assert restored.pending_queue[0] == ("789", "123", 1)
737
+ assert restored.last_sync_timestamp == "2026-01-01T00:05:00Z"
738
+ assert restored.last_full_reconciliation_timestamp == "2026-01-01T00:00:00Z"
739
+
740
+
741
+ def test_crawl_state_is_page_unchanged_by_version() -> None:
742
+ """Test page unchanged detection by version number."""
743
+ state = CrawlState(
744
+ root_page_id="1",
745
+ body_format="view",
746
+ visited_pages={"1": {"version": 5, "html_sha256": "abc", "visited_at": "2026-01-01T00:00:00Z"}},
747
+ pending_queue=[],
748
+ started_at="2026-01-01T00:00:00Z",
749
+ last_updated_at="2026-01-01T00:00:00Z",
750
+ )
751
+
752
+ # Same version = unchanged
753
+ assert state.is_page_unchanged("1", 5, None) is True
754
+ # Different version = changed
755
+ assert state.is_page_unchanged("1", 6, None) is False
756
+ # Unknown page = changed
757
+ assert state.is_page_unchanged("2", 5, None) is False
758
+
759
+
760
+ def test_crawl_state_is_page_unchanged_by_hash() -> None:
761
+ """Test page unchanged detection by content hash."""
762
+ state = CrawlState(
763
+ root_page_id="1",
764
+ body_format="view",
765
+ visited_pages={"1": {"version": None, "html_sha256": "abc123", "visited_at": "2026-01-01T00:00:00Z"}},
766
+ pending_queue=[],
767
+ started_at="2026-01-01T00:00:00Z",
768
+ last_updated_at="2026-01-01T00:00:00Z",
769
+ )
770
+
771
+ # Same hash = unchanged
772
+ assert state.is_page_unchanged("1", None, "abc123") is True
773
+ # Different hash = changed
774
+ assert state.is_page_unchanged("1", None, "xyz789") is False
775
+
776
+
777
+ def test_crawl_tree_max_nodes_limits_pages(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
778
+ """Test --max-nodes stops crawl after N pages."""
779
+ pages_fetched: list[str] = []
780
+
781
+ class DummyClient:
782
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
783
+ raise AssertionError("search_cql should not be called")
784
+
785
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
786
+ pages_fetched.append(page_id)
787
+ return {
788
+ "id": page_id,
789
+ "title": f"Page {page_id}",
790
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
791
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
792
+ "version": {"number": 1},
793
+ }
794
+
795
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
796
+ # Root has 5 children
797
+ if page_id == "1":
798
+ return [{"id": str(i)} for i in range(2, 7)]
799
+ return []
800
+
801
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
802
+ return []
803
+
804
+ def get_attachment_content(self, attachment_id: str) -> bytes:
805
+ return b""
806
+
807
+ dummy = DummyClient()
808
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: dummy)
809
+
810
+ out_dir = tmp_path / "out"
811
+ result = runner.invoke(
812
+ app,
813
+ [
814
+ "--report-dir",
815
+ str(tmp_path),
816
+ "crawl-tree",
817
+ "1",
818
+ "--out",
819
+ str(out_dir),
820
+ "--depth",
821
+ "2",
822
+ "--no-include-attachments",
823
+ "--max-nodes",
824
+ "3",
825
+ ],
826
+ )
827
+
828
+ assert result.exit_code == 0, result.output
829
+ payload = json.loads(result.stdout)
830
+
831
+ # Should have processed exactly 3 nodes
832
+ assert payload["resume_state"]["nodes_processed"] == 3
833
+ assert payload["resume_state"]["completed"] is False
834
+ assert payload["resume_state"]["termination_reason"] == "max_nodes_reached"
835
+ assert payload["resume_state"]["pending_queue_size"] > 0
836
+ assert len(pages_fetched) == 3
837
+
838
+
839
+ def test_crawl_tree_cli_accepts_crawl_max_aliases(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
840
+ class DummyClient:
841
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
842
+ raise AssertionError("search_cql should not be called")
843
+
844
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
845
+ return {
846
+ "id": page_id,
847
+ "title": f"Page {page_id}",
848
+ "body": {"view": {"value": "<p>Content</p>"}},
849
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
850
+ "version": {"number": 1},
851
+ }
852
+
853
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
854
+ return [{"id": "2"}] if page_id == "1" else []
855
+
856
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
857
+ return []
858
+
859
+ def get_attachment_content(self, attachment_id: str) -> bytes:
860
+ return b""
861
+
862
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: DummyClient())
863
+
864
+ out_dir = tmp_path / "out"
865
+ result = runner.invoke(
866
+ app,
867
+ [
868
+ "--report-dir",
869
+ str(tmp_path),
870
+ "crawl-tree",
871
+ "1",
872
+ "--out",
873
+ str(out_dir),
874
+ "--crawl-max-depth",
875
+ "0",
876
+ "--crawl-max-nodes",
877
+ "10",
878
+ "--no-include-attachments",
879
+ ],
880
+ )
881
+
882
+ assert result.exit_code == 0, result.output
883
+ payload = json.loads(result.stdout)
884
+ assert payload["resume_state"]["nodes_processed"] == 1
885
+ assert payload["crawl_mode"]["page_concurrency"] == 5
886
+
887
+
888
+ def test_crawl_tree_rejects_depth_out_of_range(tmp_path: Path) -> None:
889
+ out_dir = tmp_path / "out"
890
+ result = runner.invoke(
891
+ app,
892
+ [
893
+ "--report-dir",
894
+ str(tmp_path),
895
+ "crawl-tree",
896
+ "1",
897
+ "--out",
898
+ str(out_dir),
899
+ "--depth",
900
+ "-1",
901
+ ],
902
+ )
903
+
904
+ assert result.exit_code != 0
905
+ assert "Invalid value for '--depth'" in result.output
906
+
907
+
908
+ def test_crawl_tree_resume_saves_state(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
909
+ """Test that crawl saves state file for resume."""
910
+
911
+ class DummyClient:
912
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
913
+ raise AssertionError("search_cql should not be called")
914
+
915
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
916
+ return {
917
+ "id": page_id,
918
+ "title": f"Page {page_id}",
919
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
920
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
921
+ "version": {"number": 1},
922
+ }
923
+
924
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
925
+ return []
926
+
927
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
928
+ return []
929
+
930
+ def get_attachment_content(self, attachment_id: str) -> bytes:
931
+ return b""
932
+
933
+ dummy = DummyClient()
934
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: dummy)
935
+
936
+ out_dir = tmp_path / "out"
937
+ result = runner.invoke(
938
+ app,
939
+ [
940
+ "--report-dir",
941
+ str(tmp_path),
942
+ "crawl-tree",
943
+ "1",
944
+ "--out",
945
+ str(out_dir),
946
+ "--depth",
947
+ "0",
948
+ "--no-include-attachments",
949
+ "--resume",
950
+ ],
951
+ )
952
+
953
+ assert result.exit_code == 0, result.output
954
+
955
+ # State file should exist
956
+ state_path = out_dir / "crawl_state.json"
957
+ assert state_path.exists()
958
+
959
+ state_data = json.loads(state_path.read_text(encoding="utf-8"))
960
+ assert state_data["root_page_id"] == "1"
961
+ assert state_data["completed"] is True
962
+ assert "1" in state_data["visited_pages"]
963
+ assert state_data["run_diagnostics"]["termination_reason"] == "queue_exhausted"
964
+ assert state_data["run_diagnostics"]["pending_queue_size"] == 0
965
+
966
+
967
+ def test_crawl_tree_resume_skips_unchanged_pages(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
968
+ """Test that resume mode skips pages that haven't changed."""
969
+ pages_fetched: list[str] = []
970
+ call_count: dict[str, int] = {"1": 0}
971
+
972
+ class DummyClient:
973
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
974
+ raise AssertionError("search_cql should not be called")
975
+
976
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
977
+ call_count["1"] = call_count.get("1", 0) + 1
978
+ pages_fetched.append(page_id)
979
+ return {
980
+ "id": page_id,
981
+ "title": f"Page {page_id}",
982
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
983
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
984
+ "version": {"number": 1}, # Version stays at 1
985
+ }
986
+
987
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
988
+ return []
989
+
990
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
991
+ return []
992
+
993
+ def get_attachment_content(self, attachment_id: str) -> bytes:
994
+ return b""
995
+
996
+ dummy = DummyClient()
997
+ out_dir = tmp_path / "out"
998
+
999
+ # First crawl
1000
+ result1 = crawl_tree_to_disk(
1001
+ http=dummy,
1002
+ root_ref="1",
1003
+ output_dir=out_dir,
1004
+ options=CrawlOptions(max_depth=0, include_attachments=False, resume=True),
1005
+ )
1006
+
1007
+ assert result1["resume_state"]["nodes_processed"] == 1
1008
+ pages_fetched.clear()
1009
+
1010
+ # Second crawl with resume - should skip unchanged page
1011
+ result2 = crawl_tree_to_disk(
1012
+ http=dummy,
1013
+ root_ref="1",
1014
+ output_dir=out_dir,
1015
+ options=CrawlOptions(max_depth=0, include_attachments=False, resume=True),
1016
+ )
1017
+
1018
+ # Page should be skipped (only version check, not full fetch)
1019
+ assert result2["resume_state"]["skipped_unchanged"] == 1
1020
+ assert result2["progress_event"]["pages_skipped_unchanged"] == 1
1021
+
1022
+
1023
+ def test_crawl_tree_resume_avoids_full_fetch_for_unchanged_pages(tmp_path: Path) -> None:
1024
+ """Phase 27 TSK-275.1: unchanged pages should only use version checks on warm runs."""
1025
+ full_fetch_calls: list[str] = []
1026
+
1027
+ class DummyClient:
1028
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1029
+ raise AssertionError("search_cql should not be called")
1030
+
1031
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1032
+ if expand and "version" in expand and len(expand) == 1:
1033
+ return {
1034
+ "id": page_id,
1035
+ "title": f"Page {page_id}",
1036
+ "body": {"view": {"value": ""}},
1037
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1038
+ "version": {"number": 1},
1039
+ }
1040
+ full_fetch_calls.append(page_id)
1041
+ return {
1042
+ "id": page_id,
1043
+ "title": f"Page {page_id}",
1044
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
1045
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1046
+ "version": {"number": 1},
1047
+ }
1048
+
1049
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1050
+ if page_id == "88716667":
1051
+ return [{"id": "100"}, {"id": "200"}]
1052
+ return []
1053
+
1054
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1055
+ return []
1056
+
1057
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1058
+ return b""
1059
+
1060
+ out_dir = tmp_path / "out"
1061
+ client = DummyClient()
1062
+ crawl_tree_to_disk(
1063
+ http=client,
1064
+ root_ref="88716667",
1065
+ output_dir=out_dir,
1066
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True),
1067
+ )
1068
+ assert len(full_fetch_calls) == 3
1069
+
1070
+ full_fetch_calls.clear()
1071
+ result = crawl_tree_to_disk(
1072
+ http=client,
1073
+ root_ref="88716667",
1074
+ output_dir=out_dir,
1075
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True),
1076
+ )
1077
+ assert result["resume_state"]["skipped_unchanged"] == 3
1078
+ assert full_fetch_calls == []
1079
+
1080
+
1081
+ def test_crawl_tree_resume_runs_version_pass_before_full_fetch(tmp_path: Path) -> None:
1082
+ call_log: list[tuple[str, str]] = []
1083
+ versions = {"1": 1, "2": 1}
1084
+
1085
+ class DummyClient:
1086
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1087
+ raise AssertionError("search_cql should not be called")
1088
+
1089
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1090
+ if expand == ["version"]:
1091
+ call_log.append(("version", page_id))
1092
+ return {
1093
+ "id": page_id,
1094
+ "title": f"Page {page_id}",
1095
+ "body": {"view": {"value": ""}},
1096
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1097
+ "version": {"number": versions[page_id]},
1098
+ }
1099
+
1100
+ call_log.append(("full", page_id))
1101
+ return {
1102
+ "id": page_id,
1103
+ "title": f"Page {page_id}",
1104
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
1105
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1106
+ "space": {"key": "SPACE"},
1107
+ "version": {"number": versions[page_id]},
1108
+ }
1109
+
1110
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1111
+ if page_id == "1":
1112
+ return [{"id": "2"}]
1113
+ return []
1114
+
1115
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1116
+ return []
1117
+
1118
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1119
+ return b""
1120
+
1121
+ out_dir = tmp_path / "out"
1122
+ client = DummyClient()
1123
+ crawl_tree_to_disk(
1124
+ http=client,
1125
+ root_ref="1",
1126
+ output_dir=out_dir,
1127
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True),
1128
+ )
1129
+
1130
+ call_log.clear()
1131
+ versions["2"] = 2
1132
+ result = crawl_tree_to_disk(
1133
+ http=client,
1134
+ root_ref="1",
1135
+ output_dir=out_dir,
1136
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True),
1137
+ )
1138
+
1139
+ assert result["resume_state"]["skipped_unchanged"] == 1
1140
+ assert ("version", "1") in call_log
1141
+ assert ("full", "1") not in call_log
1142
+ assert call_log.index(("version", "2")) < call_log.index(("full", "2"))
1143
+
1144
+
1145
+ def test_crawl_tree_resume_refreshes_only_changed_page(tmp_path: Path) -> None:
1146
+ """Phase 27 TSK-275.2: changed-only refresh should fetch only the mutated page."""
1147
+ full_fetch_calls: list[str] = []
1148
+ versions = {"88716667": 1, "100": 1, "200": 1}
1149
+
1150
+ class DummyClient:
1151
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1152
+ raise AssertionError("search_cql should not be called")
1153
+
1154
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1155
+ if expand and "version" in expand and len(expand) == 1:
1156
+ return {
1157
+ "id": page_id,
1158
+ "title": f"Page {page_id}",
1159
+ "body": {"view": {"value": ""}},
1160
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1161
+ "version": {"number": versions[page_id]},
1162
+ }
1163
+ full_fetch_calls.append(page_id)
1164
+ return {
1165
+ "id": page_id,
1166
+ "title": f"Page {page_id}",
1167
+ "body": {"view": {"value": f"<p>Content {page_id} v{versions[page_id]}</p>"}},
1168
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1169
+ "version": {"number": versions[page_id]},
1170
+ }
1171
+
1172
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1173
+ if page_id == "88716667":
1174
+ return [{"id": "100"}, {"id": "200"}]
1175
+ return []
1176
+
1177
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1178
+ return []
1179
+
1180
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1181
+ return b""
1182
+
1183
+ out_dir = tmp_path / "out"
1184
+ client = DummyClient()
1185
+ crawl_tree_to_disk(
1186
+ http=client,
1187
+ root_ref="88716667",
1188
+ output_dir=out_dir,
1189
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True),
1190
+ )
1191
+
1192
+ full_fetch_calls.clear()
1193
+ versions["200"] = 2
1194
+ result = crawl_tree_to_disk(
1195
+ http=client,
1196
+ root_ref="88716667",
1197
+ output_dir=out_dir,
1198
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True),
1199
+ )
1200
+ assert result["resume_state"]["skipped_unchanged"] == 2
1201
+ assert full_fetch_calls == ["200"]
1202
+
1203
+
1204
+ def test_crawl_tree_resume_emit_corpus_preserves_unchanged_pages_on_partial_refresh(tmp_path: Path) -> None:
1205
+ versions = {"1": 1, "2": 1, "3": 1}
1206
+
1207
+ class DummyClient:
1208
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1209
+ raise AssertionError("search_cql should not be called")
1210
+
1211
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1212
+ if expand and "version" in expand and len(expand) == 1:
1213
+ return {
1214
+ "id": page_id,
1215
+ "title": f"Page {page_id}",
1216
+ "body": {"view": {"value": ""}},
1217
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1218
+ "version": {"number": versions[page_id]},
1219
+ }
1220
+ return {
1221
+ "id": page_id,
1222
+ "title": f"Page {page_id}",
1223
+ "body": {"view": {"value": f"<p>Content {page_id} v{versions[page_id]}</p>"}},
1224
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1225
+ "version": {"number": versions[page_id]},
1226
+ }
1227
+
1228
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1229
+ if page_id == "1":
1230
+ return [{"id": "2"}, {"id": "3"}]
1231
+ return []
1232
+
1233
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1234
+ return []
1235
+
1236
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1237
+ return b""
1238
+
1239
+ out_dir = tmp_path / "out"
1240
+ client = DummyClient()
1241
+ initial = crawl_tree_to_disk(
1242
+ http=client,
1243
+ root_ref="1",
1244
+ output_dir=out_dir,
1245
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True, emit_corpus=True),
1246
+ )
1247
+ assert initial["corpus_path"]
1248
+
1249
+ versions["3"] = 2
1250
+ refreshed = crawl_tree_to_disk(
1251
+ http=client,
1252
+ root_ref="1",
1253
+ output_dir=out_dir,
1254
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True, emit_corpus=True),
1255
+ )
1256
+ assert refreshed["resume_state"]["skipped_unchanged"] == 2
1257
+
1258
+ corpus_path = Path(str(refreshed["corpus_path"]))
1259
+ corpus_rows = [json.loads(line) for line in corpus_path.read_text(encoding="utf-8").splitlines() if line.strip()]
1260
+ page_ids = {str(row.get("page_id") or "") for row in corpus_rows if isinstance(row, dict)}
1261
+ assert page_ids == {"1", "2", "3"}
1262
+
1263
+ page_three_text = [str(row.get("text") or "") for row in corpus_rows if str(row.get("page_id") or "") == "3"]
1264
+ assert any("v2" in text for text in page_three_text)
1265
+
1266
+
1267
+ def test_crawl_tree_cold_vs_warm_call_profile_for_root_88716667(tmp_path: Path) -> None:
1268
+ """Phase 27 TSK-275.3 benchmark proxy: warm run should reduce full fetch calls."""
1269
+ full_fetch_calls: list[str] = []
1270
+
1271
+ class DummyClient:
1272
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1273
+ raise AssertionError("search_cql should not be called")
1274
+
1275
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1276
+ if expand and "version" in expand and len(expand) == 1:
1277
+ return {
1278
+ "id": page_id,
1279
+ "title": f"Page {page_id}",
1280
+ "body": {"view": {"value": ""}},
1281
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1282
+ "version": {"number": 1},
1283
+ }
1284
+ full_fetch_calls.append(page_id)
1285
+ return {
1286
+ "id": page_id,
1287
+ "title": f"Page {page_id}",
1288
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
1289
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1290
+ "version": {"number": 1},
1291
+ }
1292
+
1293
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1294
+ if page_id == "88716667":
1295
+ return [{"id": "100"}, {"id": "200"}, {"id": "300"}]
1296
+ return []
1297
+
1298
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1299
+ return []
1300
+
1301
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1302
+ return b""
1303
+
1304
+ out_dir = tmp_path / "out"
1305
+ client = DummyClient()
1306
+ crawl_tree_to_disk(
1307
+ http=client,
1308
+ root_ref="88716667",
1309
+ output_dir=out_dir,
1310
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True),
1311
+ )
1312
+ cold_full_fetch = len(full_fetch_calls)
1313
+ assert cold_full_fetch == 4
1314
+
1315
+ full_fetch_calls.clear()
1316
+ crawl_tree_to_disk(
1317
+ http=client,
1318
+ root_ref="88716667",
1319
+ output_dir=out_dir,
1320
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True),
1321
+ )
1322
+ warm_full_fetch = len(full_fetch_calls)
1323
+ assert warm_full_fetch < cold_full_fetch
1324
+
1325
+
1326
+ def test_crawl_tree_resume_state_reports_queue_exhausted(tmp_path: Path) -> None:
1327
+ class DummyClient:
1328
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1329
+ raise AssertionError("search_cql should not be called")
1330
+
1331
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1332
+ return {
1333
+ "id": page_id,
1334
+ "title": f"Page {page_id}",
1335
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
1336
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1337
+ "version": {"number": 1},
1338
+ }
1339
+
1340
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1341
+ return []
1342
+
1343
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1344
+ return []
1345
+
1346
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1347
+ return b""
1348
+
1349
+ result = crawl_tree_to_disk(
1350
+ http=DummyClient(),
1351
+ root_ref="1",
1352
+ output_dir=tmp_path / "out",
1353
+ options=CrawlOptions(max_depth=0, include_attachments=False, resume=True),
1354
+ )
1355
+
1356
+ assert result["resume_state"]["completed"] is True
1357
+ assert result["resume_state"]["termination_reason"] == "queue_exhausted"
1358
+ assert result["resume_state"]["pending_queue_size"] == 0
1359
+
1360
+
1361
+ def test_crawl_tree_resume_skip_unchanged_with_concurrency(tmp_path: Path) -> None:
1362
+ full_fetch_calls: list[str] = []
1363
+
1364
+ class DummyClient:
1365
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1366
+ raise AssertionError("search_cql should not be called")
1367
+
1368
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1369
+ if expand and "version" in expand and len(expand) == 1:
1370
+ return {
1371
+ "id": page_id,
1372
+ "title": f"Page {page_id}",
1373
+ "body": {"view": {"value": ""}},
1374
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1375
+ "version": {"number": 1},
1376
+ }
1377
+ full_fetch_calls.append(page_id)
1378
+ return {
1379
+ "id": page_id,
1380
+ "title": f"Page {page_id}",
1381
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
1382
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1383
+ "version": {"number": 1},
1384
+ }
1385
+
1386
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1387
+ if page_id == "1":
1388
+ return [{"id": "2"}, {"id": "3"}, {"id": "4"}]
1389
+ return []
1390
+
1391
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1392
+ return []
1393
+
1394
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1395
+ return b""
1396
+
1397
+ out_dir = tmp_path / "out"
1398
+ client = DummyClient()
1399
+ crawl_tree_to_disk(
1400
+ http=client,
1401
+ root_ref="1",
1402
+ output_dir=out_dir,
1403
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True, page_concurrency=3),
1404
+ )
1405
+ cold_full_fetch = len(full_fetch_calls)
1406
+ assert cold_full_fetch == 4
1407
+
1408
+ full_fetch_calls.clear()
1409
+ crawl_tree_to_disk(
1410
+ http=client,
1411
+ root_ref="1",
1412
+ output_dir=out_dir,
1413
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True, page_concurrency=3),
1414
+ )
1415
+ assert len(full_fetch_calls) < cold_full_fetch
1416
+
1417
+
1418
+ def test_crawl_tree_resume_supports_incremental_high_water_mark_and_reconciliation(tmp_path: Path) -> None:
1419
+ search_calls: list[dict[str, Any]] = []
1420
+ deleted = {"value": False}
1421
+
1422
+ class DummyClient:
1423
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1424
+ raise AssertionError("search_cql should not be called")
1425
+
1426
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1427
+ version_num = 1
1428
+ if expand == ["version"]:
1429
+ return {
1430
+ "id": page_id,
1431
+ "title": f"Page {page_id}",
1432
+ "version": {"number": version_num},
1433
+ }
1434
+ return {
1435
+ "id": page_id,
1436
+ "title": f"Page {page_id}",
1437
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
1438
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1439
+ "space": {"key": "SPACE"},
1440
+ "version": {"number": version_num},
1441
+ }
1442
+
1443
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1444
+ if page_id == "1" and not deleted["value"]:
1445
+ return [{"id": "2"}]
1446
+ return []
1447
+
1448
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1449
+ return []
1450
+
1451
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1452
+ return b""
1453
+
1454
+ def search_by_space_and_type(
1455
+ self,
1456
+ space_key: str | None = None,
1457
+ content_type: str | None = None,
1458
+ *,
1459
+ limit: int = 25,
1460
+ start: int = 0,
1461
+ expand: list[str] | None = None,
1462
+ last_modified_after: Any = None,
1463
+ overlap_minutes: int = 0,
1464
+ **_: Any,
1465
+ ) -> dict[str, Any]:
1466
+ search_calls.append(
1467
+ {
1468
+ "space_key": space_key,
1469
+ "content_type": content_type,
1470
+ "limit": limit,
1471
+ "start": start,
1472
+ "last_modified_after": last_modified_after,
1473
+ "overlap_minutes": overlap_minutes,
1474
+ }
1475
+ )
1476
+ if last_modified_after is not None:
1477
+ return {"results": []}
1478
+ if deleted["value"]:
1479
+ return {"results": [{"id": "1"}]}
1480
+ return {"results": [{"id": "1"}, {"id": "2"}]}
1481
+
1482
+ out_dir = tmp_path / "out"
1483
+ client = DummyClient()
1484
+
1485
+ crawl_tree_to_disk(
1486
+ http=client,
1487
+ root_ref="1",
1488
+ output_dir=out_dir,
1489
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True),
1490
+ )
1491
+
1492
+ deleted["value"] = True
1493
+ search_calls.clear()
1494
+ result = crawl_tree_to_disk(
1495
+ http=client,
1496
+ root_ref="1",
1497
+ output_dir=out_dir,
1498
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True),
1499
+ )
1500
+
1501
+ state_data = json.loads((out_dir / "crawl_state.json").read_text(encoding="utf-8"))
1502
+ assert "2" not in state_data["visited_pages"]
1503
+ assert state_data["last_sync_timestamp"] is not None
1504
+ assert state_data["last_full_reconciliation_timestamp"] is not None
1505
+ assert state_data["run_diagnostics"]["full_reconciliation_ran"] is True
1506
+ assert state_data["run_diagnostics"]["full_reconciliation_deleted_ids"] == ["2"]
1507
+ assert state_data["run_diagnostics"]["full_reconciliation_skipped"] is False
1508
+ assert state_data["run_diagnostics"]["full_reconciliation_skip_reason"] is None
1509
+ assert state_data["run_diagnostics"]["incremental_known_page_count"] == 2
1510
+ assert state_data["run_diagnostics"]["incremental_high_water_mark_ran"] is True
1511
+ assert state_data["run_diagnostics"]["incremental_high_water_mark_skipped"] is False
1512
+ assert state_data["run_diagnostics"]["incremental_high_water_mark_skip_reason"] is None
1513
+
1514
+ incremental_calls = [call for call in search_calls if call["last_modified_after"] is not None]
1515
+ assert incremental_calls
1516
+ assert all(call["overlap_minutes"] == 5 for call in incremental_calls)
1517
+ full_calls = [call for call in search_calls if call["last_modified_after"] is None]
1518
+ assert full_calls
1519
+
1520
+ assert result["resume_state"]["last_sync_timestamp"] is not None
1521
+ assert result["resume_state"]["last_full_reconciliation_timestamp"] is not None
1522
+
1523
+
1524
+ def test_crawl_tree_resume_skips_full_reconciliation_for_root_only_state(tmp_path: Path) -> None:
1525
+ search_calls: list[dict[str, Any]] = []
1526
+
1527
+ class DummyClient:
1528
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1529
+ raise AssertionError("search_cql should not be called")
1530
+
1531
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1532
+ if expand == ["version"]:
1533
+ return {"id": page_id, "title": f"Page {page_id}", "version": {"number": 1}}
1534
+ return {
1535
+ "id": page_id,
1536
+ "title": f"Page {page_id}",
1537
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
1538
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1539
+ "space": {"key": "SPACE"},
1540
+ "version": {"number": 1},
1541
+ }
1542
+
1543
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1544
+ return []
1545
+
1546
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1547
+ return []
1548
+
1549
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1550
+ return b""
1551
+
1552
+ def search_by_space_and_type(
1553
+ self,
1554
+ space_key: str | None = None,
1555
+ content_type: str | None = None,
1556
+ *,
1557
+ limit: int = 25,
1558
+ start: int = 0,
1559
+ expand: list[str] | None = None,
1560
+ last_modified_after: Any = None,
1561
+ overlap_minutes: int = 0,
1562
+ **_: Any,
1563
+ ) -> dict[str, Any]:
1564
+ search_calls.append(
1565
+ {
1566
+ "space_key": space_key,
1567
+ "content_type": content_type,
1568
+ "limit": limit,
1569
+ "start": start,
1570
+ "last_modified_after": last_modified_after,
1571
+ "overlap_minutes": overlap_minutes,
1572
+ }
1573
+ )
1574
+ if last_modified_after is not None:
1575
+ return {"results": []}
1576
+ return {"results": [{"id": "1"}]}
1577
+
1578
+ out_dir = tmp_path / "out"
1579
+ client = DummyClient()
1580
+
1581
+ crawl_tree_to_disk(
1582
+ http=client,
1583
+ root_ref="1",
1584
+ output_dir=out_dir,
1585
+ options=CrawlOptions(max_depth=0, include_attachments=False, resume=True),
1586
+ )
1587
+
1588
+ search_calls.clear()
1589
+ result = crawl_tree_to_disk(
1590
+ http=client,
1591
+ root_ref="1",
1592
+ output_dir=out_dir,
1593
+ options=CrawlOptions(max_depth=0, include_attachments=False, resume=True),
1594
+ )
1595
+
1596
+ state_data = json.loads((out_dir / "crawl_state.json").read_text(encoding="utf-8"))
1597
+ assert state_data["last_full_reconciliation_timestamp"] is not None
1598
+ assert state_data["run_diagnostics"]["full_reconciliation_ran"] is False
1599
+ assert state_data["run_diagnostics"]["full_reconciliation_skipped"] is True
1600
+ assert state_data["run_diagnostics"]["full_reconciliation_skip_reason"] == "known_pages_lte_1"
1601
+ assert state_data["run_diagnostics"]["incremental_known_page_count"] == 1
1602
+ assert state_data["run_diagnostics"]["incremental_high_water_mark_ran"] is False
1603
+ assert state_data["run_diagnostics"]["incremental_high_water_mark_skipped"] is True
1604
+ assert state_data["run_diagnostics"]["incremental_high_water_mark_skip_reason"] == "known_pages_lte_1"
1605
+
1606
+ incremental_calls = [call for call in search_calls if call["last_modified_after"] is not None]
1607
+ assert not incremental_calls
1608
+ full_calls = [call for call in search_calls if call["last_modified_after"] is None]
1609
+ assert not full_calls
1610
+
1611
+ assert result["resume_state"]["last_full_reconciliation_timestamp"] is not None
1612
+
1613
+
1614
+ def test_crawl_tree_exposes_http_telemetry_in_manifest_and_state(tmp_path: Path) -> None:
1615
+ class DummyClient:
1616
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1617
+ raise AssertionError("search_cql should not be called")
1618
+
1619
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1620
+ return {
1621
+ "id": page_id,
1622
+ "title": f"Page {page_id}",
1623
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
1624
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1625
+ "version": {"number": 1},
1626
+ }
1627
+
1628
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1629
+ return []
1630
+
1631
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1632
+ return []
1633
+
1634
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1635
+ return b""
1636
+
1637
+ def get_runtime_telemetry(self) -> dict[str, Any]:
1638
+ return {
1639
+ "retries_total": 2,
1640
+ "rate_limit_total": 1,
1641
+ "timeout_total": 1,
1642
+ "adaptive_throttle": {"effective_concurrency_signal": 0.73, "pressure_score": 0.27},
1643
+ "recent_retry_events": [{"reason": "http_429"}, {"reason": "timeout"}],
1644
+ }
1645
+
1646
+ out_dir = tmp_path / "out"
1647
+ result = crawl_tree_to_disk(
1648
+ http=DummyClient(),
1649
+ root_ref="1",
1650
+ output_dir=out_dir,
1651
+ options=CrawlOptions(max_depth=0, include_attachments=False, resume=True),
1652
+ )
1653
+
1654
+ assert result["http_telemetry"]["retries_total"] == 2
1655
+ assert result["telemetry"]["retries_total"] == 2
1656
+ assert result["http_telemetry"]["adaptive_throttle"]["effective_concurrency_signal"] == 0.73
1657
+ assert result["resume_state"]["completed"] is True
1658
+
1659
+ state_data = json.loads((out_dir / "crawl_state.json").read_text(encoding="utf-8"))
1660
+ assert state_data["run_diagnostics"]["http_telemetry"]["timeout_total"] == 1
1661
+ assert state_data["run_diagnostics"]["telemetry"]["timeout_total"] == 1
1662
+ assert state_data["run_diagnostics"]["progress_event"]["pages_discovered"] == 1
1663
+
1664
+
1665
+ def test_crawl_tree_emits_crawl_progress_runtime_events(tmp_path: Path) -> None:
1666
+ emitted_events: list[tuple[str, dict[str, Any]]] = []
1667
+
1668
+ class DummyClient:
1669
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1670
+ raise AssertionError("search_cql should not be called")
1671
+
1672
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1673
+ return {
1674
+ "id": page_id,
1675
+ "title": f"Page {page_id}",
1676
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
1677
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1678
+ "version": {"number": 1},
1679
+ }
1680
+
1681
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1682
+ if page_id == "1":
1683
+ return [{"id": "2"}, {"id": "3"}]
1684
+ return []
1685
+
1686
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1687
+ return []
1688
+
1689
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1690
+ return b""
1691
+
1692
+ def _capture_runtime_event(event: str, payload: dict[str, Any]) -> None:
1693
+ emitted_events.append((event, payload))
1694
+
1695
+ result = crawl_tree_to_disk(
1696
+ http=DummyClient(),
1697
+ root_ref="1",
1698
+ output_dir=tmp_path / "out",
1699
+ options=CrawlOptions(
1700
+ max_depth=1,
1701
+ include_attachments=False,
1702
+ page_concurrency=2,
1703
+ runtime_event_sink=_capture_runtime_event,
1704
+ ),
1705
+ )
1706
+
1707
+ progress_events = [payload for name, payload in emitted_events if name == "crawl_progress"]
1708
+ assert progress_events
1709
+ assert progress_events[-1]["pages_discovered"] == result["progress_event"]["pages_discovered"]
1710
+ assert progress_events[-1]["pages_fetched"] == result["progress_event"]["pages_fetched"]
1711
+ assert progress_events[-1]["pages_failed"] == result["progress_event"]["pages_failed"]
1712
+ assert progress_events[-1]["pages_skipped_unchanged"] == result["progress_event"]["pages_skipped_unchanged"]
1713
+
1714
+
1715
+ def test_crawl_tree_emits_pages_deleted_event(tmp_path: Path) -> None:
1716
+ emitted_events: list[tuple[str, dict[str, Any]]] = []
1717
+ search_calls: list[dict[str, Any]] = []
1718
+ deleted = {"value": False}
1719
+
1720
+ class DummyClient:
1721
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1722
+ raise AssertionError("search_cql should not be called")
1723
+
1724
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1725
+ if expand == ["version"]:
1726
+ return {"id": page_id, "title": f"Page {page_id}", "version": {"number": 1}}
1727
+ return {
1728
+ "id": page_id,
1729
+ "title": f"Page {page_id}",
1730
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
1731
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1732
+ "space": {"key": "SPACE"},
1733
+ "version": {"number": 1},
1734
+ }
1735
+
1736
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1737
+ if page_id == "1" and not deleted["value"]:
1738
+ return [{"id": "2"}]
1739
+ return []
1740
+
1741
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1742
+ return []
1743
+
1744
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1745
+ return b""
1746
+
1747
+ def search_by_space_and_type(
1748
+ self,
1749
+ space_key: str | None = None,
1750
+ content_type: str | None = None,
1751
+ *,
1752
+ limit: int = 25,
1753
+ start: int = 0,
1754
+ expand: list[str] | None = None,
1755
+ last_modified_after: Any = None,
1756
+ overlap_minutes: int = 0,
1757
+ **_: Any,
1758
+ ) -> dict[str, Any]:
1759
+ search_calls.append(
1760
+ {
1761
+ "space_key": space_key,
1762
+ "content_type": content_type,
1763
+ "limit": limit,
1764
+ "start": start,
1765
+ "last_modified_after": last_modified_after,
1766
+ "overlap_minutes": overlap_minutes,
1767
+ }
1768
+ )
1769
+ if last_modified_after is not None:
1770
+ return {"results": []}
1771
+ if deleted["value"]:
1772
+ return {"results": [{"id": "1"}]}
1773
+ return {"results": [{"id": "1"}, {"id": "2"}]}
1774
+
1775
+ def _capture_runtime_event(event: str, payload: dict[str, Any]) -> None:
1776
+ emitted_events.append((event, payload))
1777
+
1778
+ out_dir = tmp_path / "out"
1779
+ client = DummyClient()
1780
+
1781
+ crawl_tree_to_disk(
1782
+ http=client,
1783
+ root_ref="1",
1784
+ output_dir=out_dir,
1785
+ options=CrawlOptions(
1786
+ max_depth=1,
1787
+ include_attachments=False,
1788
+ resume=True,
1789
+ runtime_event_sink=_capture_runtime_event,
1790
+ ),
1791
+ )
1792
+ deleted["value"] = True
1793
+ emitted_events.clear()
1794
+ search_calls.clear()
1795
+
1796
+ crawl_tree_to_disk(
1797
+ http=client,
1798
+ root_ref="1",
1799
+ output_dir=out_dir,
1800
+ options=CrawlOptions(
1801
+ max_depth=1,
1802
+ include_attachments=False,
1803
+ resume=True,
1804
+ runtime_event_sink=_capture_runtime_event,
1805
+ ),
1806
+ )
1807
+
1808
+ pages_deleted_events = [payload for name, payload in emitted_events if name == "pages_deleted"]
1809
+ assert pages_deleted_events
1810
+ assert pages_deleted_events[-1]["deleted_count"] == 1
1811
+ assert pages_deleted_events[-1]["page_ids"] == ["2"]
1812
+
1813
+
1814
+ def test_crawl_tree_throughput_metrics_and_adaptive_signal(tmp_path: Path) -> None:
1815
+ class DummyClient:
1816
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1817
+ raise AssertionError("search_cql should not be called")
1818
+
1819
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1820
+ return {
1821
+ "id": page_id,
1822
+ "title": f"Page {page_id}",
1823
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
1824
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1825
+ "version": {"number": 1},
1826
+ }
1827
+
1828
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1829
+ if page_id == "1":
1830
+ return [{"id": "2"}, {"id": "3"}]
1831
+ return []
1832
+
1833
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1834
+ return []
1835
+
1836
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1837
+ return b""
1838
+
1839
+ def get_runtime_telemetry(self) -> dict[str, Any]:
1840
+ return {
1841
+ "adaptive_throttle": {"effective_concurrency_signal": 0.2, "pressure_score": 0.8},
1842
+ "latency_ms": {"operation": {"p95": 123.4}},
1843
+ "recent_retry_events": [{"reason": "http_429"}, {"reason": "timeout"}],
1844
+ }
1845
+
1846
+ result = crawl_tree_to_disk(
1847
+ http=DummyClient(),
1848
+ root_ref="1",
1849
+ output_dir=tmp_path / "out",
1850
+ options=CrawlOptions(max_depth=1, include_attachments=False, page_concurrency=4),
1851
+ )
1852
+
1853
+ telemetry = result["http_telemetry"]
1854
+ assert telemetry["inflight_workers"] == 1
1855
+ assert telemetry["queue_depth"] == 0
1856
+ assert telemetry["pages_per_second"] > 0
1857
+ assert telemetry["retry_events"] == 2
1858
+ assert telemetry["p95_page_latency_ms"] == 123.4
1859
+
1860
+ progress_event = result["progress_event"]
1861
+ assert progress_event == {
1862
+ "pages_discovered": 3,
1863
+ "pages_fetched": 3,
1864
+ "pages_failed": 0,
1865
+ "pages_skipped": 0,
1866
+ "pages_skipped_unchanged": 0,
1867
+ "throttle": {
1868
+ "configured_page_concurrency": 4,
1869
+ "effective_page_concurrency": 1,
1870
+ "effective_concurrency_signal": 0.2,
1871
+ "pressure_score": 0.8,
1872
+ },
1873
+ }
1874
+
1875
+
1876
+ def test_crawl_tree_resume_continues_from_pending_queue(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
1877
+ """Test that resume continues from where it left off."""
1878
+ pages_fetched: list[str] = []
1879
+
1880
+ class DummyClient:
1881
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1882
+ raise AssertionError("search_cql should not be called")
1883
+
1884
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1885
+ pages_fetched.append(page_id)
1886
+ return {
1887
+ "id": page_id,
1888
+ "title": f"Page {page_id}",
1889
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
1890
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
1891
+ "version": {"number": int(page_id)}, # Different versions
1892
+ }
1893
+
1894
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1895
+ if page_id == "1":
1896
+ return [{"id": "2"}, {"id": "3"}, {"id": "4"}]
1897
+ return []
1898
+
1899
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1900
+ return []
1901
+
1902
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1903
+ return b""
1904
+
1905
+ dummy = DummyClient()
1906
+ out_dir = tmp_path / "out"
1907
+
1908
+ # First crawl with max_nodes=2 (will process 1 and 2, leave 3,4 pending)
1909
+ result1 = crawl_tree_to_disk(
1910
+ http=dummy,
1911
+ root_ref="1",
1912
+ output_dir=out_dir,
1913
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True, max_nodes=2),
1914
+ )
1915
+
1916
+ assert result1["resume_state"]["nodes_processed"] == 2
1917
+ assert result1["resume_state"]["completed"] is False
1918
+
1919
+ pages_fetched.clear()
1920
+
1921
+ # Resume crawl - should continue with remaining pages
1922
+ result2 = crawl_tree_to_disk(
1923
+ http=dummy,
1924
+ root_ref="1",
1925
+ output_dir=out_dir,
1926
+ options=CrawlOptions(max_depth=1, include_attachments=False, resume=True),
1927
+ )
1928
+
1929
+ # Should have processed pages 3 and 4 (plus version checks for 1, 2)
1930
+ assert result2["resume_state"]["completed"] is True
1931
+
1932
+
1933
+ def test_crawl_tree_cli_resume_flag(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
1934
+ """Test CLI --resume flag works."""
1935
+
1936
+ class DummyClient:
1937
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1938
+ raise AssertionError("search_cql should not be called")
1939
+
1940
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1941
+ return {
1942
+ "id": page_id,
1943
+ "title": f"Page {page_id}",
1944
+ "body": {"view": {"value": "<p>Content</p>"}},
1945
+ "_links": {"base": "http://example", "webui": "/display/SPACE/Page"},
1946
+ "version": {"number": 1},
1947
+ }
1948
+
1949
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
1950
+ return []
1951
+
1952
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
1953
+ return []
1954
+
1955
+ def get_attachment_content(self, attachment_id: str) -> bytes:
1956
+ return b""
1957
+
1958
+ dummy = DummyClient()
1959
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: dummy)
1960
+
1961
+ out_dir = tmp_path / "out"
1962
+
1963
+ # Run with --resume
1964
+ result = runner.invoke(
1965
+ app,
1966
+ [
1967
+ "--report-dir",
1968
+ str(tmp_path),
1969
+ "crawl-tree",
1970
+ "1",
1971
+ "--out",
1972
+ str(out_dir),
1973
+ "--depth",
1974
+ "0",
1975
+ "--no-include-attachments",
1976
+ "--resume",
1977
+ ],
1978
+ )
1979
+
1980
+ assert result.exit_code == 0, result.output
1981
+ payload = json.loads(result.stdout)
1982
+ assert "resume_state" in payload
1983
+ assert payload["resume_state"]["state_path"] == str(out_dir / "crawl_state.json")
1984
+
1985
+
1986
+ def test_crawl_tree_cli_accepts_phase27_compat_flags(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
1987
+ """Phase 27 compatibility: crawl-tree should accept cache/content root flags from audit orchestrator."""
1988
+
1989
+ class DummyClient:
1990
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
1991
+ raise AssertionError("search_cql should not be called")
1992
+
1993
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
1994
+ return {
1995
+ "id": page_id,
1996
+ "title": f"Page {page_id}",
1997
+ "body": {"view": {"value": "<p>Content</p>"}},
1998
+ "_links": {"base": "http://example", "webui": "/display/SPACE/Page"},
1999
+ "version": {"number": 1},
2000
+ }
2001
+
2002
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
2003
+ return []
2004
+
2005
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
2006
+ return []
2007
+
2008
+ def get_attachment_content(self, attachment_id: str) -> bytes:
2009
+ return b""
2010
+
2011
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: DummyClient())
2012
+
2013
+ out_dir = tmp_path / "out"
2014
+ content_root = tmp_path / "content-root"
2015
+ result = runner.invoke(
2016
+ app,
2017
+ [
2018
+ "--report-dir",
2019
+ str(tmp_path),
2020
+ "crawl-tree",
2021
+ "1",
2022
+ "--out",
2023
+ str(out_dir),
2024
+ "--depth",
2025
+ "0",
2026
+ "--no-include-attachments",
2027
+ "--cache-mode",
2028
+ "refresh",
2029
+ "--cache-ttl-hours",
2030
+ "48",
2031
+ "--incremental",
2032
+ "--content-root",
2033
+ str(content_root),
2034
+ "--project-storage-key",
2035
+ "project-x",
2036
+ ],
2037
+ )
2038
+
2039
+ assert result.exit_code == 0, result.output
2040
+ payload = json.loads(result.stdout)
2041
+ assert payload["cache"]["mode"] == "refresh"
2042
+ assert payload["cache"]["ttl_hours"] == 48
2043
+ assert payload["cache"]["incremental"] is True
2044
+ assert payload["cache"]["effective_resume"] is True
2045
+ assert payload["cache"]["content_root"] == str(content_root)
2046
+ assert payload["cache"]["project_storage_key"] == "project-x"
2047
+
2048
+
2049
+ def test_crawl_tree_cli_accepts_concurrency_and_staged_mode_flags(
2050
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
2051
+ ) -> None:
2052
+ class DummyClient:
2053
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
2054
+ raise AssertionError("search_cql should not be called")
2055
+
2056
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
2057
+ return {
2058
+ "id": page_id,
2059
+ "title": f"Page {page_id}",
2060
+ "body": {"view": {"value": "<p>Content</p>"}},
2061
+ "_links": {"base": "http://example", "webui": "/display/SPACE/Page"},
2062
+ "version": {"number": 1},
2063
+ }
2064
+
2065
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
2066
+ return []
2067
+
2068
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
2069
+ return []
2070
+
2071
+ def get_attachment_content(self, attachment_id: str) -> bytes:
2072
+ return b""
2073
+
2074
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: DummyClient())
2075
+
2076
+ out_dir = tmp_path / "out"
2077
+ result = runner.invoke(
2078
+ app,
2079
+ [
2080
+ "--report-dir",
2081
+ str(tmp_path),
2082
+ "crawl-tree",
2083
+ "1",
2084
+ "--out",
2085
+ str(out_dir),
2086
+ "--depth",
2087
+ "0",
2088
+ "--no-include-attachments",
2089
+ "--page-concurrency",
2090
+ "3",
2091
+ "--staged-mode",
2092
+ "structure-first",
2093
+ ],
2094
+ )
2095
+
2096
+ assert result.exit_code == 0, result.output
2097
+ payload = json.loads(result.stdout)
2098
+ assert payload["crawl_mode"]["page_concurrency"] == 3
2099
+ assert payload["crawl_mode"]["staged_mode"] == "structure_first"
2100
+
2101
+
2102
+ # =============================================================================
2103
+ # TSK-189: Bundle Zip Tests
2104
+ # =============================================================================
2105
+
2106
+
2107
+ def test_create_bundle_zip_contains_all_artifacts(tmp_path: Path) -> None:
2108
+ """Test that bundle zip contains all expected files."""
2109
+ out_dir = tmp_path / "out"
2110
+ out_dir.mkdir()
2111
+ attachments_dir = out_dir / "attachments"
2112
+ attachments_dir.mkdir()
2113
+
2114
+ # Create mock files
2115
+ (out_dir / "123_Root.html").write_text("<p>Root page</p>")
2116
+ (out_dir / "456_Child.html").write_text("<p>Child page</p>")
2117
+ (attachments_dir / "a1_file.txt").write_text("attachment content")
2118
+ (out_dir / "corpus.jsonl").write_text('{"chunk_id": "abc"}\n')
2119
+ (out_dir / "link-graph.json").write_text('{"edges": []}')
2120
+ (out_dir / "tables.jsonl").write_text('{"table_id": "t1"}\n')
2121
+ (out_dir / "mentions.jsonl").write_text('{"mention_id": "m1"}\n')
2122
+ (out_dir / "crawl_state.json").write_text('{"completed": true}')
2123
+ manifest_path = out_dir / "crawl_manifest.json"
2124
+ manifest_path.write_text('{"root_page_id": "123"}')
2125
+
2126
+ # Create bundle
2127
+ bundle_path = _create_bundle_zip(out_dir, "123", manifest_path)
2128
+
2129
+ assert bundle_path.exists()
2130
+ assert bundle_path.suffix == ".zip"
2131
+ assert "crawl_bundle_123_" in bundle_path.name
2132
+
2133
+ # Verify contents
2134
+ with zipfile.ZipFile(bundle_path, "r") as zf:
2135
+ names = zf.namelist()
2136
+ assert "123_Root.html" in names
2137
+ assert "456_Child.html" in names
2138
+ assert "attachments/a1_file.txt" in names
2139
+ assert "corpus.jsonl" in names
2140
+ assert "link-graph.json" in names
2141
+ assert "tables.jsonl" in names
2142
+ assert "mentions.jsonl" in names
2143
+ assert "crawl_state.json" in names
2144
+ assert "crawl_manifest.json" in names
2145
+
2146
+
2147
+ def test_crawl_tree_bundle_option(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
2148
+ """Test --bundle flag creates zip archive."""
2149
+
2150
+ class DummyClient:
2151
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
2152
+ raise AssertionError("search_cql should not be called")
2153
+
2154
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
2155
+ return {
2156
+ "id": page_id,
2157
+ "title": f"Page {page_id}",
2158
+ "body": {"view": {"value": f"<p>Content {page_id}</p>"}},
2159
+ "_links": {"base": "http://example", "webui": f"/display/SPACE/Page{page_id}"},
2160
+ "version": {"number": 1},
2161
+ }
2162
+
2163
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
2164
+ return []
2165
+
2166
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
2167
+ return [{"id": "a1", "title": "file.txt", "version": {"number": 1}}]
2168
+
2169
+ def get_attachment_content(self, attachment_id: str) -> bytes:
2170
+ return b"attachment content"
2171
+
2172
+ dummy = DummyClient()
2173
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda settings, server: dummy)
2174
+
2175
+ out_dir = tmp_path / "out"
2176
+ result = runner.invoke(
2177
+ app,
2178
+ [
2179
+ "--report-dir",
2180
+ str(tmp_path),
2181
+ "crawl-tree",
2182
+ "1",
2183
+ "--out",
2184
+ str(out_dir),
2185
+ "--depth",
2186
+ "0",
2187
+ "--emit-corpus",
2188
+ "--emit-link-graph",
2189
+ "--bundle",
2190
+ ],
2191
+ )
2192
+
2193
+ assert result.exit_code == 0, result.output
2194
+ payload = json.loads(result.stdout)
2195
+
2196
+ # Bundle should be created
2197
+ assert "bundle_path" in payload
2198
+ bundle_path = Path(payload["bundle_path"])
2199
+ assert bundle_path.exists()
2200
+
2201
+ # Verify bundle contents
2202
+ with zipfile.ZipFile(bundle_path, "r") as zf:
2203
+ names = zf.namelist()
2204
+ assert any(name.endswith(".html") for name in names)
2205
+ assert "corpus.jsonl" in names
2206
+ assert "link-graph.json" in names
2207
+ assert "crawl_manifest.json" in names
2208
+
2209
+
2210
+ def test_crawl_tree_bundle_with_no_attachments(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
2211
+ """Test bundle works even without attachments."""
2212
+
2213
+ class DummyClient:
2214
+ def search_cql(self, cql: str, **_: Any) -> dict[str, Any]:
2215
+ raise AssertionError("search_cql should not be called")
2216
+
2217
+ def get_page(self, page_id: str, expand: list[str] | None = None) -> dict[str, Any]:
2218
+ return {
2219
+ "id": page_id,
2220
+ "title": "Root",
2221
+ "body": {"view": {"value": "<p>Content</p>"}},
2222
+ "_links": {"base": "http://example", "webui": "/display/SPACE/Root"},
2223
+ "version": {"number": 1},
2224
+ }
2225
+
2226
+ def get_child_pages(self, page_id: str) -> list[dict[str, Any]]:
2227
+ return []
2228
+
2229
+ def get_attachments(self, page_id: str) -> list[dict[str, Any]]:
2230
+ return []
2231
+
2232
+ def get_attachment_content(self, attachment_id: str) -> bytes:
2233
+ return b""
2234
+
2235
+ dummy = DummyClient()
2236
+ out_dir = tmp_path / "out"
2237
+
2238
+ result = crawl_tree_to_disk(
2239
+ http=dummy,
2240
+ root_ref="1",
2241
+ output_dir=out_dir,
2242
+ options=CrawlOptions(max_depth=0, include_attachments=False, bundle=True),
2243
+ )
2244
+
2245
+ assert "bundle_path" in result
2246
+ bundle_path = Path(result["bundle_path"])
2247
+ assert bundle_path.exists()
2248
+
2249
+ with zipfile.ZipFile(bundle_path, "r") as zf:
2250
+ names = zf.namelist()
2251
+ assert "1_Root.html" in names
2252
+ assert "crawl_manifest.json" in names