@ngocsangairvds/vsaf 3.1.27 → 3.2.1

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 (303) hide show
  1. package/package.json +2 -2
  2. package/src/global.js +70 -10
  3. package/tools/skills/vds-scripts-skill/.openskills.json +6 -0
  4. package/tools/skills/vds-scripts-skill/QUALITY.md +44 -0
  5. package/tools/skills/vds-scripts-skill/SKILL.md +135 -0
  6. package/tools/skills/vds-scripts-skill/references/audit-commands.md +171 -0
  7. package/tools/skills/vds-scripts-skill/references/capability-index.md +34 -0
  8. package/tools/skills/vds-scripts-skill/references/development-commands.md +12 -0
  9. package/tools/skills/vds-scripts-skill/references/google-sheets.md +73 -0
  10. package/tools/skills/vds-scripts-skill/references/integration-commands.md +17 -0
  11. package/tools/skills/vds-scripts-skill/references/platform-bootstrap.md +31 -0
  12. package/tools/skills/vds-scripts-skill/references/specialist-routing.md +14 -0
  13. package/tools/skills/vds-scripts-skill/references/validation-commands.md +15 -0
  14. package/tools/skills/vsaf-build/SKILL.md +32 -2
  15. package/tools/skills/vsaf-ship/SKILL.md +41 -10
  16. package/tools/skills/vsaf-test/SKILL.md +8 -0
  17. package/tools/vds-scripts/.mcp.json +11 -0
  18. package/tools/vds-scripts/.secrets.baseline +133 -0
  19. package/tools/vds-scripts/AGENTS.md +152 -0
  20. package/tools/vds-scripts/CLAUDE.md +101 -0
  21. package/tools/vds-scripts/CLI_COMMAND_OPTIMIZATION.md +156 -0
  22. package/tools/vds-scripts/PACKAGE_P125B_IMPLEMENTATION_SUMMARY.md +131 -0
  23. package/tools/vds-scripts/PROJECT_COMPLETION_SUMMARY.md +45 -0
  24. package/tools/vds-scripts/README.md +97 -0
  25. package/tools/vds-scripts/bitbucket_manifest_mapping.toml +34 -0
  26. package/tools/vds-scripts/bitbucket_orchestrator/ARCHITECTURE_ANALYSIS.md +258 -0
  27. package/tools/vds-scripts/bitbucket_orchestrator/BITBUCKET_API_PRACTICES.md +393 -0
  28. package/tools/vds-scripts/bitbucket_orchestrator/EVALUATION_REPORT.md +61 -0
  29. package/tools/vds-scripts/bitbucket_orchestrator/FEATURES.md +908 -0
  30. package/tools/vds-scripts/bitbucket_orchestrator/README.md +687 -0
  31. package/tools/vds-scripts/bitbucket_orchestrator/pyproject.toml +40 -0
  32. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/__init__.py +20 -0
  33. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/async_client.py +657 -0
  34. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/cli.py +2108 -0
  35. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/client.py +2534 -0
  36. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/config.py +171 -0
  37. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/errors.py +67 -0
  38. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/factory.py +185 -0
  39. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/protocols.py +244 -0
  40. package/tools/vds-scripts/bitbucket_orchestrator/tests/__init__.py +8 -0
  41. package/tools/vds-scripts/bitbucket_orchestrator/tests/conftest.py +65 -0
  42. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_advanced_search.py +151 -0
  43. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_async_client.py +546 -0
  44. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_branch_permissions.py +145 -0
  45. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_cli.py +115 -0
  46. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client.py +157 -0
  47. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_branch_conditions.py +79 -0
  48. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_code_advanced.py +163 -0
  49. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_code_file.py +32 -0
  50. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_deployment_environments.py +194 -0
  51. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_issues.py +164 -0
  52. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_pipelines_advanced.py +179 -0
  53. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_pr_blockers.py +119 -0
  54. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_repository_variables.py +156 -0
  55. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_code.py +98 -0
  56. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_code_advanced.py +282 -0
  57. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_code_insights.py +335 -0
  58. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_conditions.py +147 -0
  59. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_config.py +131 -0
  60. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_deployment_env.py +352 -0
  61. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_factory.py +371 -0
  62. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_fork_operations.py +204 -0
  63. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_issue_cli.py +261 -0
  64. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_pipeline_advanced.py +270 -0
  65. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_pr_blocker.py +204 -0
  66. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_protocols.py +334 -0
  67. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_repo_settings.py +343 -0
  68. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_repo_variables.py +270 -0
  69. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_webhooks.py +189 -0
  70. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_workspace.py +233 -0
  71. package/tools/vds-scripts/bitbucket_orchestrator/uv.lock +742 -0
  72. package/tools/vds-scripts/confluence_orchestrator/Dockerfile +19 -0
  73. package/tools/vds-scripts/confluence_orchestrator/README.md +412 -0
  74. package/tools/vds-scripts/confluence_orchestrator/SYNC_SCRIPTS.md +127 -0
  75. package/tools/vds-scripts/confluence_orchestrator/SYNC_STANDARDIZATION.md +108 -0
  76. package/tools/vds-scripts/confluence_orchestrator/pyproject.toml +48 -0
  77. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/__init__.py +20 -0
  78. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/cli.py +2532 -0
  79. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/config.py +175 -0
  80. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/content.py +290 -0
  81. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/content_v2.py +94 -0
  82. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/crawl_tree.py +1835 -0
  83. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/errors.py +80 -0
  84. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/eventing.py +109 -0
  85. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/http.py +1114 -0
  86. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/orchestration.py +165 -0
  87. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/reporting.py +78 -0
  88. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/tree.py +121 -0
  89. package/tools/vds-scripts/confluence_orchestrator/sync_pdfs_from_markdown.py +213 -0
  90. package/tools/vds-scripts/confluence_orchestrator/sync_pdfs_to_confluence.py +305 -0
  91. package/tools/vds-scripts/confluence_orchestrator/sync_png_attachments.py +305 -0
  92. package/tools/vds-scripts/confluence_orchestrator/tests/__init__.py +0 -0
  93. package/tools/vds-scripts/confluence_orchestrator/tests/conftest.py +8 -0
  94. package/tools/vds-scripts/confluence_orchestrator/tests/test_advanced_content.py +224 -0
  95. package/tools/vds-scripts/confluence_orchestrator/tests/test_advanced_search.py +188 -0
  96. package/tools/vds-scripts/confluence_orchestrator/tests/test_cache_management.py +247 -0
  97. package/tools/vds-scripts/confluence_orchestrator/tests/test_cli.py +499 -0
  98. package/tools/vds-scripts/confluence_orchestrator/tests/test_config.py +83 -0
  99. package/tools/vds-scripts/confluence_orchestrator/tests/test_content.py +186 -0
  100. package/tools/vds-scripts/confluence_orchestrator/tests/test_content_flags.py +27 -0
  101. package/tools/vds-scripts/confluence_orchestrator/tests/test_crawl_tree.py +2250 -0
  102. package/tools/vds-scripts/confluence_orchestrator/tests/test_draft_management.py +223 -0
  103. package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing.py +71 -0
  104. package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing_chaos.py +37 -0
  105. package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing_rate_limit.py +44 -0
  106. package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing_timeout.py +49 -0
  107. package/tools/vds-scripts/confluence_orchestrator/tests/test_export.py +230 -0
  108. package/tools/vds-scripts/confluence_orchestrator/tests/test_history.py +204 -0
  109. package/tools/vds-scripts/confluence_orchestrator/tests/test_http.py +117 -0
  110. package/tools/vds-scripts/confluence_orchestrator/tests/test_orchestration.py +91 -0
  111. package/tools/vds-scripts/confluence_orchestrator/tests/test_reporting.py +24 -0
  112. package/tools/vds-scripts/confluence_orchestrator/tests/test_search_cql.py +34 -0
  113. package/tools/vds-scripts/confluence_orchestrator/tests/test_space_management.py +237 -0
  114. package/tools/vds-scripts/confluence_orchestrator/tests/test_space_permissions.py +332 -0
  115. package/tools/vds-scripts/confluence_orchestrator/tests/test_user_group_management.py +388 -0
  116. package/tools/vds-scripts/confluence_orchestrator/uv.lock +1023 -0
  117. package/tools/vds-scripts/git_orchestrator/ENHANCEMENT_SUMMARY.md +119 -0
  118. package/tools/vds-scripts/git_orchestrator/README.md +280 -0
  119. package/tools/vds-scripts/git_orchestrator/VERIFICATION_REPORT.md +152 -0
  120. package/tools/vds-scripts/git_orchestrator/pyproject.toml +35 -0
  121. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/__init__.py +7 -0
  122. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/__main__.py +4 -0
  123. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/cli.py +847 -0
  124. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/logging_config.py +63 -0
  125. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/manifest.py +129 -0
  126. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/orchestrator.py +819 -0
  127. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/reporting.py +53 -0
  128. package/tools/vds-scripts/git_orchestrator/tests/__init__.py +0 -0
  129. package/tools/vds-scripts/git_orchestrator/tests/test_cli_settings.py +21 -0
  130. package/tools/vds-scripts/git_orchestrator/tests/test_integration.py +74 -0
  131. package/tools/vds-scripts/git_orchestrator/tests/test_manifest.py +79 -0
  132. package/tools/vds-scripts/git_orchestrator/tests/test_orchestrator.py +204 -0
  133. package/tools/vds-scripts/git_orchestrator/tests/test_public_api.py +236 -0
  134. package/tools/vds-scripts/git_orchestrator/tests/test_resilience.py +345 -0
  135. package/tools/vds-scripts/git_orchestrator/uv.lock +271 -0
  136. package/tools/vds-scripts/jira_orchestrator/README.md +770 -0
  137. package/tools/vds-scripts/jira_orchestrator/pyproject.toml +39 -0
  138. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/__init__.py +1 -0
  139. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/adapter.py +1320 -0
  140. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/cli.py +2271 -0
  141. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/config.py +138 -0
  142. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/errors.py +67 -0
  143. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/reporting.py +65 -0
  144. package/tools/vds-scripts/jira_orchestrator/tests/__init__.py +1 -0
  145. package/tools/vds-scripts/jira_orchestrator/tests/conftest.py +86 -0
  146. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_agile_list_payloads.py +54 -0
  147. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_bulk_operations.py +69 -0
  148. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_components.py +57 -0
  149. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_createmeta.py +45 -0
  150. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_dashboard.py +117 -0
  151. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_issue_properties.py +54 -0
  152. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_permissions_compat.py +42 -0
  153. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_reindex.py +42 -0
  154. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_remote_links.py +76 -0
  155. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_transitions.py +91 -0
  156. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_user_management.py +110 -0
  157. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_version_management.py +133 -0
  158. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_watchers.py +41 -0
  159. package/tools/vds-scripts/jira_orchestrator/tests/test_advanced_search.py +164 -0
  160. package/tools/vds-scripts/jira_orchestrator/tests/test_agile.py +256 -0
  161. package/tools/vds-scripts/jira_orchestrator/tests/test_application_properties.py +193 -0
  162. package/tools/vds-scripts/jira_orchestrator/tests/test_backlog.py +91 -0
  163. package/tools/vds-scripts/jira_orchestrator/tests/test_bulk_operations.py +277 -0
  164. package/tools/vds-scripts/jira_orchestrator/tests/test_cli.py +106 -0
  165. package/tools/vds-scripts/jira_orchestrator/tests/test_components.py +106 -0
  166. package/tools/vds-scripts/jira_orchestrator/tests/test_config.py +164 -0
  167. package/tools/vds-scripts/jira_orchestrator/tests/test_dashboard.py +122 -0
  168. package/tools/vds-scripts/jira_orchestrator/tests/test_discover_fields.py +207 -0
  169. package/tools/vds-scripts/jira_orchestrator/tests/test_filter_management.py +333 -0
  170. package/tools/vds-scripts/jira_orchestrator/tests/test_issue_archiving.py +164 -0
  171. package/tools/vds-scripts/jira_orchestrator/tests/test_issue_links.py +257 -0
  172. package/tools/vds-scripts/jira_orchestrator/tests/test_issue_properties.py +171 -0
  173. package/tools/vds-scripts/jira_orchestrator/tests/test_link_types.py +314 -0
  174. package/tools/vds-scripts/jira_orchestrator/tests/test_parse_set.py +37 -0
  175. package/tools/vds-scripts/jira_orchestrator/tests/test_permissions.py +273 -0
  176. package/tools/vds-scripts/jira_orchestrator/tests/test_reindex.py +81 -0
  177. package/tools/vds-scripts/jira_orchestrator/tests/test_remote_links.py +254 -0
  178. package/tools/vds-scripts/jira_orchestrator/tests/test_security_schemes.py +170 -0
  179. package/tools/vds-scripts/jira_orchestrator/tests/test_transitions_changelog.py +114 -0
  180. package/tools/vds-scripts/jira_orchestrator/tests/test_user_management.py +226 -0
  181. package/tools/vds-scripts/jira_orchestrator/tests/test_version_management.py +339 -0
  182. package/tools/vds-scripts/jira_orchestrator/tests/test_watchers.py +101 -0
  183. package/tools/vds-scripts/jira_orchestrator/tests/test_worklog.py +223 -0
  184. package/tools/vds-scripts/jira_orchestrator/uv.lock +738 -0
  185. package/tools/vds-scripts/mcp_server/Dockerfile +34 -0
  186. package/tools/vds-scripts/mcp_server/README.md +140 -0
  187. package/tools/vds-scripts/mcp_server/pyproject.toml +42 -0
  188. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/__init__.py +4 -0
  189. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/config.py +36 -0
  190. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/server.py +66 -0
  191. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/__init__.py +14 -0
  192. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/bitbucket_tools.py +47 -0
  193. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/confluence_tools.py +59 -0
  194. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/git_tools.py +71 -0
  195. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/jira_tools.py +63 -0
  196. package/tools/vds-scripts/mcp_server/tests/__init__.py +2 -0
  197. package/tools/vds-scripts/mcp_server/tests/conftest.py +29 -0
  198. package/tools/vds-scripts/mcp_server/tests/unit/__init__.py +2 -0
  199. package/tools/vds-scripts/mcp_server/tests/unit/test_bitbucket_tools.py +25 -0
  200. package/tools/vds-scripts/mcp_server/tests/unit/test_confluence_tools.py +25 -0
  201. package/tools/vds-scripts/mcp_server/tests/unit/test_git_tools.py +32 -0
  202. package/tools/vds-scripts/mcp_server/tests/unit/test_jira_tools.py +32 -0
  203. package/tools/vds-scripts/mcp_server/tests/verification/__init__.py +2 -0
  204. package/tools/vds-scripts/mcp_server/tests/verification/test_mcp_confluence_tools.py +40 -0
  205. package/tools/vds-scripts/mcp_server/tests/verification/test_mcp_jira_tools.py +37 -0
  206. package/tools/vds-scripts/mcp_server/tests/verification/test_mcp_tool_registration.py +47 -0
  207. package/tools/vds-scripts/mcp_server/uv.lock +1032 -0
  208. package/tools/vds-scripts/mypy.ini +5 -0
  209. package/tools/vds-scripts/pyproject.toml +29 -0
  210. package/tools/vds-scripts/repo-manifest.yaml +273 -0
  211. package/tools/vds-scripts/repo-manifest.yaml.example +25 -0
  212. package/tools/vds-scripts/scripts/BRD-Validation-API.postman_collection.json +706 -0
  213. package/tools/vds-scripts/scripts/BRD-Validation-README.md +308 -0
  214. package/tools/vds-scripts/scripts/README.md +162 -0
  215. package/tools/vds-scripts/scripts/bootstrap_uv.sh +30 -0
  216. package/tools/vds-scripts/scripts/brd-validation-environment.json +51 -0
  217. package/tools/vds-scripts/scripts/brd-validation-test-results.json +13023 -0
  218. package/tools/vds-scripts/scripts/brd_coverage_report.json +276 -0
  219. package/tools/vds-scripts/scripts/create_memory_session.py +35 -0
  220. package/tools/vds-scripts/scripts/deployment/load_docker_images_offline.sh +90 -0
  221. package/tools/vds-scripts/scripts/final_completion_report.md +139 -0
  222. package/tools/vds-scripts/scripts/folder_structure_report.json +321 -0
  223. package/tools/vds-scripts/scripts/generate_completion_report.py +125 -0
  224. package/tools/vds-scripts/scripts/generate_intellij_modules.py +150 -0
  225. package/tools/vds-scripts/scripts/link_integrity_report.json +807 -0
  226. package/tools/vds-scripts/scripts/move_audit_artifact_pages.py +255 -0
  227. package/tools/vds-scripts/scripts/move_audit_artifact_pages_rest.py +165 -0
  228. package/tools/vds-scripts/scripts/move_wrong_dept_pages.py +216 -0
  229. package/tools/vds-scripts/scripts/save_intellij_memories.py +120 -0
  230. package/tools/vds-scripts/scripts/save_memories_to_vds_ai.py +83 -0
  231. package/tools/vds-scripts/scripts/save_memories_vds_style.py +129 -0
  232. package/tools/vds-scripts/scripts/search_intellij_memories.py +50 -0
  233. package/tools/vds-scripts/scripts/setup_intellij_workspace.py +65 -0
  234. package/tools/vds-scripts/scripts/target-state-automation/README.md +89 -0
  235. package/tools/vds-scripts/scripts/target-state-automation/confluence_sync_coordinator.sh +27 -0
  236. package/tools/vds-scripts/scripts/target-state-automation/coordination.sh +114 -0
  237. package/tools/vds-scripts/scripts/target-state-automation/diagram_coordinator.sh +25 -0
  238. package/tools/vds-scripts/scripts/target-state-automation/docs_root.sh +22 -0
  239. package/tools/vds-scripts/scripts/target-state-automation/generate_diagrams.sh +22 -0
  240. package/tools/vds-scripts/scripts/target-state-automation/markdown_coordinator.sh +25 -0
  241. package/tools/vds-scripts/scripts/target-state-automation/progress_dashboard.sh +17 -0
  242. package/tools/vds-scripts/scripts/target-state-automation/schema_coordinator.sh +25 -0
  243. package/tools/vds-scripts/scripts/target-state-automation/sync_confluence.sh +30 -0
  244. package/tools/vds-scripts/scripts/target-state-automation/update_dependencies.sh +19 -0
  245. package/tools/vds-scripts/scripts/target-state-automation/validate_links.sh +86 -0
  246. package/tools/vds-scripts/scripts/target-state-automation/validate_markdown.sh +52 -0
  247. package/tools/vds-scripts/scripts/target-state-automation/validate_schemas.sh +26 -0
  248. package/tools/vds-scripts/scripts/target-state-automation/validate_structure.sh +98 -0
  249. package/tools/vds-scripts/scripts/update_modules_xml.py +190 -0
  250. package/tools/vds-scripts/scripts/uv-workspace-alignment-verification-2026-03-25.md +128 -0
  251. package/tools/vds-scripts/scripts/validate_brd_coverage.py +179 -0
  252. package/tools/vds-scripts/scripts/validate_folder_structure.py +240 -0
  253. package/tools/vds-scripts/scripts/validate_link_integrity.py +272 -0
  254. package/tools/vds-scripts/scripts/vds_sh_helpers.sh +180 -0
  255. package/tools/vds-scripts/scripts/verification/phase2_portable_paths_ubuntu_docker.sh +26 -0
  256. package/tools/vds-scripts/scripts/worktree_uv.sh +48 -0
  257. package/tools/vds-scripts/uv.lock +8 -0
  258. package/tools/vds-scripts/vds_cli/README.md +126 -0
  259. package/tools/vds-scripts/vds_cli/VERIFICATION_REPORT.md +41 -0
  260. package/tools/vds-scripts/vds_cli/pyproject.toml +38 -0
  261. package/tools/vds-scripts/vds_cli/src/vds_cli/__init__.py +3 -0
  262. package/tools/vds-scripts/vds_cli/src/vds_cli/cli.py +173 -0
  263. package/tools/vds-scripts/vds_cli/src/vds_cli/docs_sync.py +1203 -0
  264. package/tools/vds-scripts/vds_cli/src/vds_cli/env.py +41 -0
  265. package/tools/vds-scripts/vds_cli/src/vds_cli/google_sheets_orchestrator/__init__.py +3 -0
  266. package/tools/vds-scripts/vds_cli/src/vds_cli/google_sheets_orchestrator/google_sheets_orchestrator.py +198 -0
  267. package/tools/vds-scripts/vds_cli/src/vds_cli/router.py +93 -0
  268. package/tools/vds-scripts/vds_cli/src/vds_cli/sync_api.py +647 -0
  269. package/tools/vds-scripts/vds_cli/src/vds_cli/sync_service.py +266 -0
  270. package/tools/vds-scripts/vds_cli/tests/__init__.py +2 -0
  271. package/tools/vds-scripts/vds_cli/tests/conftest.py +49 -0
  272. package/tools/vds-scripts/vds_cli/tests/unit/__init__.py +2 -0
  273. package/tools/vds-scripts/vds_cli/tests/unit/test_cli.py +143 -0
  274. package/tools/vds-scripts/vds_cli/tests/unit/test_docs_sync.py +422 -0
  275. package/tools/vds-scripts/vds_cli/tests/unit/test_env.py +51 -0
  276. package/tools/vds-scripts/vds_cli/tests/unit/test_router.py +72 -0
  277. package/tools/vds-scripts/vds_cli/tests/unit/test_sync_api.py +357 -0
  278. package/tools/vds-scripts/vds_cli/tests/unit/test_sync_service.py +160 -0
  279. package/tools/vds-scripts/vds_cli/tests/verification/__init__.py +2 -0
  280. package/tools/vds-scripts/vds_cli/tests/verification/test_bitbucket_real.py +33 -0
  281. package/tools/vds-scripts/vds_cli/tests/verification/test_confluence_real.py +35 -0
  282. package/tools/vds-scripts/vds_cli/tests/verification/test_jira_real.py +41 -0
  283. package/tools/vds-scripts/vds_cli/uv.lock +524 -0
  284. package/tools/vds-scripts/vds_cli_common/README.md +190 -0
  285. package/tools/vds-scripts/vds_cli_common/pyproject.toml +92 -0
  286. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/__init__.py +34 -0
  287. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/completers.py +139 -0
  288. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/context.py +201 -0
  289. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/env.py +119 -0
  290. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/errors.py +318 -0
  291. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/output.py +284 -0
  292. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/paths.py +78 -0
  293. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/testing.py +213 -0
  294. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/version.py +85 -0
  295. package/tools/vds-scripts/vds_cli_common/tests/__init__.py +1 -0
  296. package/tools/vds-scripts/vds_cli_common/tests/test_completers.py +148 -0
  297. package/tools/vds-scripts/vds_cli_common/tests/test_context.py +192 -0
  298. package/tools/vds-scripts/vds_cli_common/tests/test_env.py +102 -0
  299. package/tools/vds-scripts/vds_cli_common/tests/test_errors.py +186 -0
  300. package/tools/vds-scripts/vds_cli_common/tests/test_output.py +229 -0
  301. package/tools/vds-scripts/vds_cli_common/tests/test_paths.py +61 -0
  302. package/tools/vds-scripts/vds_cli_common/tests/test_testing.py +138 -0
  303. package/tools/vds-scripts/vds_cli_common/tests/test_version.py +64 -0
@@ -0,0 +1,422 @@
1
+ """Unit tests for multi-writer instruction sync engine."""
2
+
3
+ import errno
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+ import vds_cli.docs_sync as docs_sync
9
+ from vds_cli.docs_sync import (
10
+ DocsSyncEngine,
11
+ HttpCentralStateBackend,
12
+ LocalJsonCentralStateBackend,
13
+ SyncConfig,
14
+ SyncError,
15
+ _atomic_write_text,
16
+ _build_backend,
17
+ )
18
+ from vds_cli.sync_api import (
19
+ ConfluencePropertySyncApiClient,
20
+ SyncApiDocument,
21
+ SyncApiTransientError,
22
+ VdsAiMemorySyncApiClient,
23
+ )
24
+
25
+
26
+ def _write_targets(repo_root: Path, *, agents: str, claude: str, copilot: str) -> None:
27
+ (repo_root / ".github").mkdir(parents=True, exist_ok=True)
28
+ (repo_root / "AGENTS.md").write_text(agents, encoding="utf-8")
29
+ (repo_root / "CLAUDE.md").write_text(claude, encoding="utf-8")
30
+ (repo_root / ".github" / "copilot-instructions.md").write_text(copilot, encoding="utf-8")
31
+
32
+
33
+ def _config(repo_root: Path) -> SyncConfig:
34
+ return SyncConfig(
35
+ repo_root=repo_root,
36
+ reconcile_interval_seconds=60,
37
+ debounce_ms=100,
38
+ log_json=False,
39
+ )
40
+
41
+
42
+ @pytest.mark.parametrize(
43
+ ("edited_relpath", "updated"),
44
+ [
45
+ ("AGENTS.md", "# Shared instructions\n\n- updated from AGENTS\n"),
46
+ ("CLAUDE.md", "# Shared instructions\n\n- updated from CLAUDE\n"),
47
+ (
48
+ ".github/copilot-instructions.md",
49
+ "# Shared instructions\n\n- updated from copilot file\n",
50
+ ),
51
+ ],
52
+ )
53
+ def test_any_source_file_edit_converges_all_targets(
54
+ tmp_path: Path,
55
+ edited_relpath: str,
56
+ updated: str,
57
+ ) -> None:
58
+ """Editing any of the 3 source files should converge all targets."""
59
+ base = "# Shared instructions\n\n- baseline\n"
60
+ _write_targets(tmp_path, agents=base, claude=base, copilot=base)
61
+
62
+ engine = DocsSyncEngine(config=_config(tmp_path))
63
+ startup_result = engine.run_once(reason="startup")
64
+ assert startup_result in {"synced", "fanout", "noop"}
65
+
66
+ (tmp_path / edited_relpath).write_text(updated, encoding="utf-8")
67
+
68
+ result = engine.run_once(reason="event")
69
+ assert result == "synced"
70
+
71
+ assert (tmp_path / "AGENTS.md").read_text(encoding="utf-8") == updated
72
+ assert (tmp_path / "CLAUDE.md").read_text(encoding="utf-8") == updated
73
+ assert (tmp_path / ".github" / "copilot-instructions.md").read_text(
74
+ encoding="utf-8"
75
+ ) == updated
76
+
77
+
78
+ def test_divergent_local_edits_emit_conflict_artifact(tmp_path: Path) -> None:
79
+ """Divergent concurrent edits should produce conflict artifact and avoid overwrite."""
80
+ base = "# Shared instructions\n\n- baseline\n"
81
+ _write_targets(tmp_path, agents=base, claude=base, copilot=base)
82
+
83
+ engine = DocsSyncEngine(config=_config(tmp_path))
84
+ engine.run_once(reason="startup")
85
+
86
+ agents_update = "# Shared instructions\n\n- AGENTS edit\n"
87
+ claude_update = "# Shared instructions\n\n- CLAUDE edit\n"
88
+ (tmp_path / "AGENTS.md").write_text(agents_update, encoding="utf-8")
89
+ (tmp_path / "CLAUDE.md").write_text(claude_update, encoding="utf-8")
90
+
91
+ result = engine.run_once(reason="event")
92
+ assert result == "conflict"
93
+
94
+ conflicts = list((tmp_path / ".vds-sync" / "conflicts").glob(".sync-conflict-*.md"))
95
+ assert conflicts, "Expected conflict artifact file"
96
+
97
+ assert (tmp_path / "AGENTS.md").read_text(encoding="utf-8") == agents_update
98
+ assert (tmp_path / "CLAUDE.md").read_text(encoding="utf-8") == claude_update
99
+
100
+
101
+ def test_watch_backend_auto_prefers_watchfiles_when_available(
102
+ tmp_path: Path,
103
+ monkeypatch,
104
+ ) -> None:
105
+ """Auto backend should select watchfiles when module callable is available."""
106
+ base = "# Shared instructions\n\n- baseline\n"
107
+ _write_targets(tmp_path, agents=base, claude=base, copilot=base)
108
+ engine = DocsSyncEngine(config=_config(tmp_path))
109
+
110
+ monkeypatch.setattr("vds_cli.docs_sync._load_watchfiles_watch", lambda: lambda *a, **k: iter(()))
111
+
112
+ assert engine._resolve_watch_backend() == "watchfiles"
113
+
114
+
115
+ def test_revision_mismatch_non_overlapping_changes_merge_and_sync(tmp_path: Path) -> None:
116
+ """Revision mismatch should auto-merge safely when edits do not overlap."""
117
+ base = "A\nB\nC\n"
118
+ _write_targets(tmp_path, agents=base, claude=base, copilot=base)
119
+ engine = DocsSyncEngine(config=_config(tmp_path))
120
+ engine.run_once(reason="startup")
121
+
122
+ backend = engine.backend
123
+ assert isinstance(backend, LocalJsonCentralStateBackend)
124
+ central = backend.pull()
125
+ assert central is not None
126
+ backend.push_if_match(
127
+ content="A\nB_REMOTE\nC\n",
128
+ source_file="AGENTS.md",
129
+ expected_revision=central.revision,
130
+ )
131
+
132
+ (tmp_path / "CLAUDE.md").write_text("A\nB\nC_LOCAL\n", encoding="utf-8")
133
+ result = engine.run_once(reason="event")
134
+
135
+ assert result == "synced"
136
+ expected = "A\nB_REMOTE\nC_LOCAL\n"
137
+ assert (tmp_path / "AGENTS.md").read_text(encoding="utf-8") == expected
138
+ assert (tmp_path / "CLAUDE.md").read_text(encoding="utf-8") == expected
139
+ assert (tmp_path / ".github" / "copilot-instructions.md").read_text(encoding="utf-8") == expected
140
+
141
+
142
+ def test_revision_mismatch_conflicting_changes_emits_conflict_artifact(tmp_path: Path) -> None:
143
+ """Revision mismatch with overlapping edits should not auto-merge."""
144
+ base = "A\nB\nC\n"
145
+ _write_targets(tmp_path, agents=base, claude=base, copilot=base)
146
+ engine = DocsSyncEngine(config=_config(tmp_path))
147
+ engine.run_once(reason="startup")
148
+
149
+ backend = engine.backend
150
+ assert isinstance(backend, LocalJsonCentralStateBackend)
151
+ central = backend.pull()
152
+ assert central is not None
153
+ backend.push_if_match(
154
+ content="A\nB_REMOTE\nC\n",
155
+ source_file="AGENTS.md",
156
+ expected_revision=central.revision,
157
+ )
158
+
159
+ (tmp_path / "CLAUDE.md").write_text("A\nB_LOCAL\nC\n", encoding="utf-8")
160
+ result = engine.run_once(reason="event")
161
+ assert result == "conflict"
162
+
163
+ conflicts = list((tmp_path / ".vds-sync" / "conflicts").glob(".sync-conflict-*.md"))
164
+ assert conflicts, "Expected conflict artifact file for unresolved merge"
165
+
166
+
167
+ def test_periodic_reconcile_recovers_when_central_changes_without_local_event(tmp_path: Path) -> None:
168
+ """Periodic reconcile should fan out central changes even without local file-change event."""
169
+ base = "# Shared instructions\n\n- baseline\n"
170
+ _write_targets(tmp_path, agents=base, claude=base, copilot=base)
171
+
172
+ engine = DocsSyncEngine(config=_config(tmp_path))
173
+ engine.run_once(reason="startup")
174
+
175
+ backend = engine.backend
176
+ assert isinstance(backend, LocalJsonCentralStateBackend)
177
+ central = backend.pull()
178
+ assert central is not None
179
+
180
+ updated = "# Shared instructions\n\n- central update only\n"
181
+ backend.push_if_match(
182
+ content=updated,
183
+ source_file="AGENTS.md",
184
+ expected_revision=central.revision,
185
+ )
186
+
187
+ result = engine.run_once(reason="periodic")
188
+ assert result == "fanout"
189
+ assert (tmp_path / "AGENTS.md").read_text(encoding="utf-8") == updated
190
+ assert (tmp_path / "CLAUDE.md").read_text(encoding="utf-8") == updated
191
+ assert (tmp_path / ".github" / "copilot-instructions.md").read_text(encoding="utf-8") == updated
192
+
193
+
194
+ def test_build_backend_vds_ai_memory_contract_uses_env_defaults(
195
+ tmp_path: Path,
196
+ monkeypatch,
197
+ ) -> None:
198
+ """HTTP backend should resolve concrete VDS AI Memory env contracts."""
199
+ monkeypatch.setenv("VDS_AI_MEMORY_BASE_URL", "https://memory.example")
200
+ monkeypatch.setenv("VDS_AI_MEMORY_API_KEY", "mem-token")
201
+ config = SyncConfig(
202
+ repo_root=tmp_path,
203
+ central_backend="http",
204
+ central_contract="vds-ai-memory",
205
+ central_memory_id="memory-123",
206
+ log_json=False,
207
+ )
208
+
209
+ backend = _build_backend(config)
210
+ assert isinstance(backend, HttpCentralStateBackend)
211
+ assert isinstance(backend.client, VdsAiMemorySyncApiClient)
212
+ assert backend.client.base_url == "https://memory.example"
213
+ assert backend.client.memory_id == "memory-123"
214
+ assert backend.client.auth_token == "mem-token"
215
+
216
+
217
+ def test_build_backend_confluence_contract_uses_standard_env_credentials(
218
+ tmp_path: Path,
219
+ monkeypatch,
220
+ ) -> None:
221
+ """Confluence contract should use VDS credential policy from env."""
222
+ monkeypatch.setenv("CONFLUENCE_INTERNAL_URL", "https://confluence.internal")
223
+ monkeypatch.setenv("VDS_USERNAME", "sync-user")
224
+ monkeypatch.setenv("VDS_PASSWORD", "sync-pass")
225
+ config = SyncConfig(
226
+ repo_root=tmp_path,
227
+ central_backend="http",
228
+ central_contract="confluence-property",
229
+ central_confluence_page_id="12345",
230
+ central_confluence_server="internal",
231
+ log_json=False,
232
+ )
233
+
234
+ backend = _build_backend(config)
235
+ assert isinstance(backend, HttpCentralStateBackend)
236
+ assert isinstance(backend.client, ConfluencePropertySyncApiClient)
237
+ assert backend.client.base_url == "https://confluence.internal"
238
+ assert backend.client.page_id == "12345"
239
+ assert backend.client.username == "sync-user"
240
+
241
+
242
+ def test_build_backend_vds_ai_memory_contract_requires_memory_id(tmp_path: Path, monkeypatch) -> None:
243
+ """Memory contract must reject missing memory identifiers."""
244
+ monkeypatch.setenv("VDS_AI_MEMORY_BASE_URL", "https://memory.example")
245
+ config = SyncConfig(
246
+ repo_root=tmp_path,
247
+ central_backend="http",
248
+ central_contract="vds-ai-memory",
249
+ log_json=False,
250
+ )
251
+
252
+ with pytest.raises(SyncError):
253
+ _build_backend(config)
254
+
255
+
256
+ def test_http_backend_retries_transient_pull_then_succeeds(monkeypatch) -> None:
257
+ """HTTP backend should retry transient pull failures with bounded backoff."""
258
+
259
+ class _TransientPullClient:
260
+ def __init__(self) -> None:
261
+ self.calls = 0
262
+
263
+ def pull_document(self):
264
+ self.calls += 1
265
+ if self.calls < 3:
266
+ raise SyncApiTransientError("temporary network failure")
267
+ return SyncApiDocument(
268
+ content="canonical",
269
+ revision="3",
270
+ content_hash="abc123",
271
+ source_file="AGENTS.md",
272
+ updated_at="2026-03-01T00:00:00Z",
273
+ )
274
+
275
+ delays: list[float] = []
276
+ monkeypatch.setattr(docs_sync.time, "sleep", lambda delay: delays.append(delay))
277
+
278
+ backend = HttpCentralStateBackend(
279
+ client=_TransientPullClient(),
280
+ retry_attempts=3,
281
+ retry_base_delay_seconds=0.05,
282
+ retry_max_delay_seconds=0.1,
283
+ )
284
+ document = backend.pull()
285
+ assert document is not None
286
+ assert document.revision == "3"
287
+ assert delays == [0.05, 0.1]
288
+
289
+
290
+ def test_http_backend_exhausts_retry_budget_on_transient_pull(monkeypatch) -> None:
291
+ """HTTP backend should raise SyncError after retry budget is exhausted."""
292
+
293
+ class _AlwaysTransientClient:
294
+ def pull_document(self):
295
+ raise SyncApiTransientError("still unavailable")
296
+
297
+ monkeypatch.setattr(docs_sync.time, "sleep", lambda _delay: None)
298
+ backend = HttpCentralStateBackend(
299
+ client=_AlwaysTransientClient(),
300
+ retry_attempts=2,
301
+ retry_base_delay_seconds=0.01,
302
+ retry_max_delay_seconds=0.02,
303
+ )
304
+
305
+ with pytest.raises(SyncError, match="Failed pulling central state via HTTP"):
306
+ backend.pull()
307
+
308
+
309
+ def test_sync_logs_include_source_hash_and_write_outcomes(tmp_path: Path) -> None:
310
+ """Sync log events should include source hash and fan-out write outcomes."""
311
+ base = "# Shared instructions\n\n- baseline\n"
312
+ _write_targets(tmp_path, agents=base, claude=base, copilot=base)
313
+ engine = DocsSyncEngine(config=_config(tmp_path))
314
+ engine.run_once(reason="startup")
315
+
316
+ updated = "# Shared instructions\n\n- updated from CLAUDE\n"
317
+ (tmp_path / "CLAUDE.md").write_text(updated, encoding="utf-8")
318
+ result = engine.run_once(reason="event")
319
+ assert result == "synced"
320
+
321
+ log_lines = (tmp_path / ".vds-sync" / "watch-agents.log.jsonl").read_text(
322
+ encoding="utf-8"
323
+ ).splitlines()
324
+ entries = [json.loads(line) for line in log_lines if line.strip()]
325
+ sync_event = next(
326
+ entry
327
+ for entry in reversed(entries)
328
+ if entry.get("event") == "sync" and entry.get("reason") == "event"
329
+ )
330
+
331
+ assert sync_event["source_file"] == "CLAUDE.md"
332
+ assert isinstance(sync_event.get("source_hash"), str)
333
+ assert len(sync_event["source_hash"]) == 64
334
+ assert isinstance(sync_event.get("write_outcomes"), dict)
335
+ assert set(sync_event["write_outcomes"].keys()) == set(docs_sync.TARGET_FILES)
336
+ assert "updated" in sync_event["write_outcomes"].values()
337
+
338
+
339
+ def test_atomic_write_retries_windows_style_permission_error(tmp_path: Path, monkeypatch) -> None:
340
+ """Atomic writer should retry transient PermissionError (common on Windows)."""
341
+ target = tmp_path / "AGENTS.md"
342
+ target.write_text("old\n", encoding="utf-8")
343
+ attempts = {"count": 0}
344
+ real_replace = docs_sync.os.replace
345
+
346
+ def _flaky_replace(src, dst):
347
+ attempts["count"] += 1
348
+ if attempts["count"] < 3:
349
+ raise PermissionError("Access is denied")
350
+ return real_replace(src, dst)
351
+
352
+ monkeypatch.setattr(docs_sync.os, "replace", _flaky_replace)
353
+ _atomic_write_text(target, "new\n")
354
+
355
+ assert target.read_text(encoding="utf-8") == "new\n"
356
+ assert attempts["count"] == 3
357
+ assert list(tmp_path.glob(".AGENTS.md.*.tmp")) == []
358
+
359
+
360
+ def test_atomic_write_retries_posix_eacces_error(tmp_path: Path, monkeypatch) -> None:
361
+ """Atomic writer should retry transient POSIX EACCES replace failures."""
362
+ target = tmp_path / "CLAUDE.md"
363
+ target.write_text("old\n", encoding="utf-8")
364
+ attempts = {"count": 0}
365
+ real_replace = docs_sync.os.replace
366
+
367
+ def _flaky_replace(src, dst):
368
+ attempts["count"] += 1
369
+ if attempts["count"] == 1:
370
+ raise OSError(errno.EACCES, "Permission denied")
371
+ return real_replace(src, dst)
372
+
373
+ monkeypatch.setattr(docs_sync.os, "replace", _flaky_replace)
374
+ _atomic_write_text(target, "line1\r\nline2\r\n")
375
+
376
+ assert target.read_bytes() == b"line1\r\nline2\r\n"
377
+ assert attempts["count"] == 2
378
+ assert list(tmp_path.glob(".CLAUDE.md.*.tmp")) == []
379
+
380
+
381
+ def test_atomic_write_cleans_temp_file_after_permanent_failure(tmp_path: Path, monkeypatch) -> None:
382
+ """Atomic writer should not leak temp files when replace fails repeatedly."""
383
+ target = tmp_path / "copilot-instructions.md"
384
+ target.write_text("old\n", encoding="utf-8")
385
+
386
+ def _always_fail(src, dst): # noqa: ARG001
387
+ raise PermissionError("still locked")
388
+
389
+ monkeypatch.setattr(docs_sync.os, "replace", _always_fail)
390
+ with pytest.raises(PermissionError):
391
+ _atomic_write_text(target, "new\n")
392
+
393
+ assert target.read_text(encoding="utf-8") == "old\n"
394
+ assert list(tmp_path.glob(".copilot-instructions.md.*.tmp")) == []
395
+
396
+
397
+ def test_log_write_deadlock_does_not_crash_sync_engine(
398
+ tmp_path: Path,
399
+ monkeypatch,
400
+ capsys,
401
+ ) -> None:
402
+ """Sync should keep running when local JSON log sink raises EDEADLK."""
403
+ base = "# Shared instructions\n\n- baseline\n"
404
+ _write_targets(tmp_path, agents=base, claude=base, copilot=base)
405
+ engine = DocsSyncEngine(config=_config(tmp_path))
406
+
407
+ original_open = Path.open
408
+
409
+ def _patched_open(self: Path, *args, **kwargs): # type: ignore[no-untyped-def]
410
+ mode = kwargs.get("mode")
411
+ if mode is None and args:
412
+ mode = args[0]
413
+ if self == engine.config.log_path and mode == "a":
414
+ raise OSError(errno.EDEADLK, "Resource deadlock avoided")
415
+ return original_open(self, *args, **kwargs)
416
+
417
+ monkeypatch.setattr(Path, "open", _patched_open)
418
+
419
+ engine._log("noop", reason="test", conflict_state="none", write_outcomes={})
420
+
421
+ captured = capsys.readouterr()
422
+ assert '"event": "log-write-failed"' in captured.err
@@ -0,0 +1,51 @@
1
+ """Unit tests for environment management.
2
+
3
+ Based on pytest v8.3.3+ API
4
+ Context7: /pytest-dev/pytest
5
+ Key Features: Fixtures, parametrization
6
+ """
7
+
8
+ from pathlib import Path
9
+
10
+ from vds_cli.env import VDSSettings, load_environment
11
+
12
+
13
+ def test_vds_settings_instantiation() -> None:
14
+ """Test VDSSettings can be instantiated."""
15
+ # Note: This test verifies the class can be instantiated
16
+ # Actual values depend on environment and ~/.vds/.env file
17
+ settings = VDSSettings()
18
+ assert isinstance(settings, VDSSettings)
19
+ # Verify all expected attributes exist
20
+ assert hasattr(settings, "vds_username")
21
+ assert hasattr(settings, "vds_password")
22
+ assert hasattr(settings, "bitbucket_access_token")
23
+ assert hasattr(settings, "internal_confluence_token")
24
+ assert hasattr(settings, "external_confluence_token")
25
+
26
+
27
+ def test_vds_settings_with_env_file(mock_env_file: Path, mock_home_dir: Path) -> None:
28
+ """Test VDSSettings can load from environment file."""
29
+ # Note: pydantic-settings reads from both env file and environment variables
30
+ # This test verifies the class can be instantiated with a file present
31
+ settings = VDSSettings()
32
+ assert isinstance(settings, VDSSettings)
33
+ # Values may come from file or environment - just verify structure
34
+ assert hasattr(settings, "vds_username")
35
+
36
+
37
+ def test_load_environment() -> None:
38
+ """Test load_environment function."""
39
+ settings = load_environment()
40
+ assert isinstance(settings, VDSSettings)
41
+ assert hasattr(settings, "vds_username")
42
+
43
+
44
+ def test_load_environment_returns_settings() -> None:
45
+ """Test load_environment returns VDSSettings instance."""
46
+ settings = load_environment()
47
+ assert isinstance(settings, VDSSettings)
48
+ # Verify it has all expected attributes
49
+ assert hasattr(settings, "vds_username")
50
+ assert hasattr(settings, "bitbucket_access_token")
51
+
@@ -0,0 +1,72 @@
1
+ """Unit tests for command routing.
2
+
3
+ Based on pytest v8.3.3+ API
4
+ Context7: /pytest-dev/pytest
5
+ Key Features: Fixtures, mocking
6
+ """
7
+
8
+ from pathlib import Path
9
+ from unittest.mock import Mock, patch
10
+
11
+ import pytest
12
+ from vds_cli.router import ORCHESTRATORS, run_orchestrator
13
+
14
+
15
+ def test_orchestrators_dict() -> None:
16
+ """Test ORCHESTRATORS dictionary structure."""
17
+ assert "jira" in ORCHESTRATORS
18
+ assert "confluence" in ORCHESTRATORS
19
+ assert "bitbucket" in ORCHESTRATORS
20
+ assert "git" in ORCHESTRATORS
21
+
22
+ for _name, config in ORCHESTRATORS.items():
23
+ assert "module" in config
24
+ assert "path" in config
25
+ assert isinstance(config["module"], str)
26
+ assert isinstance(config["path"], str)
27
+
28
+
29
+ def test_run_orchestrator_invalid_service(mock_script_dir: Path) -> None:
30
+ """Test run_orchestrator with invalid service name."""
31
+ with pytest.raises(ValueError, match="Unknown service"):
32
+ run_orchestrator("invalid", [], mock_script_dir)
33
+
34
+
35
+ def test_run_orchestrator_missing_directory(mock_script_dir: Path) -> None:
36
+ """Test run_orchestrator when orchestrator directory doesn't exist."""
37
+ # Remove one orchestrator directory
38
+ (mock_script_dir / "jira_orchestrator").rmdir()
39
+
40
+ with pytest.raises(FileNotFoundError, match="not found"):
41
+ run_orchestrator("jira", [], mock_script_dir)
42
+
43
+
44
+ @patch("vds_cli.router.subprocess.run")
45
+ def test_run_orchestrator_success(mock_subprocess: Mock, mock_script_dir: Path) -> None:
46
+ """Test successful orchestrator execution."""
47
+ mock_subprocess.return_value = Mock(returncode=0)
48
+
49
+ exit_code = run_orchestrator("jira", ["search", "project = NTTC"], mock_script_dir)
50
+
51
+ assert exit_code == 0
52
+ mock_subprocess.assert_called_once()
53
+ call_args = mock_subprocess.call_args[0][0]
54
+ assert "uv" in call_args
55
+ assert "--project" in call_args
56
+ assert "jira_orchestrator" in str(call_args)
57
+ assert "python" in call_args
58
+ assert "-m" in call_args
59
+ assert "vds_jira_orchestrator.cli" in call_args
60
+ assert "search" in call_args
61
+ assert "project = NTTC" in call_args
62
+
63
+
64
+ @patch("vds_cli.router.subprocess.run")
65
+ def test_run_orchestrator_failure(mock_subprocess: Mock, mock_script_dir: Path) -> None:
66
+ """Test orchestrator execution with non-zero exit code."""
67
+ mock_subprocess.return_value = Mock(returncode=1)
68
+
69
+ exit_code = run_orchestrator("confluence", ["content", "search"], mock_script_dir)
70
+
71
+ assert exit_code == 1
72
+