@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,165 @@
1
+ """Batch polling and webhook management utilities for Confluence Server/DC."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import builtins
6
+ import time
7
+ from collections.abc import Iterable, Iterator, Sequence
8
+ from dataclasses import dataclass, field
9
+ from typing import Any
10
+
11
+ from .http import ConfluenceClient
12
+
13
+
14
+ @dataclass(slots=True)
15
+ class BatchMetrics:
16
+ """Execution metrics captured during a batch scan."""
17
+
18
+ scanned: int = 0
19
+ pages_processed: int = 0
20
+ requests: int = 0
21
+ started_at: float = field(default_factory=time.time)
22
+ finished_at: float | None = None
23
+
24
+ def mark_request(self) -> None:
25
+ self.requests += 1
26
+
27
+ def mark_processed(self, count: int) -> None:
28
+ self.pages_processed += count
29
+
30
+ def mark_finished(self) -> None:
31
+ self.finished_at = time.time()
32
+
33
+ @property
34
+ def duration_seconds(self) -> float | None:
35
+ if self.finished_at is None:
36
+ return None
37
+ return round(self.finished_at - self.started_at, 3)
38
+
39
+
40
+ class BatchRunner:
41
+ """Polling utilities for change detection using REST v1 CQL queries."""
42
+
43
+ def __init__(
44
+ self,
45
+ http_client: ConfluenceClient,
46
+ *,
47
+ expand: Sequence[str] | None = None,
48
+ ) -> None:
49
+ self._http = http_client
50
+ self._expand = expand
51
+ self._last_metrics: BatchMetrics | None = None
52
+
53
+ def scan(
54
+ self,
55
+ cql: str,
56
+ *,
57
+ limit: int = 25,
58
+ max_results: int | None = None,
59
+ start: int = 0,
60
+ expand: Sequence[str] | None = None,
61
+ ) -> Iterator[dict[str, Any]]:
62
+ """Yield individual content results for the given CQL query."""
63
+
64
+ metrics = BatchMetrics()
65
+ self._last_metrics = metrics
66
+ applied_expand = expand if expand is not None else self._expand
67
+ fetched = 0
68
+ current_start = start
69
+
70
+ while True:
71
+ remaining = None if max_results is None else max_results - fetched
72
+ page_limit = limit if remaining is None else min(limit, remaining)
73
+ params = {
74
+ "cql": cql,
75
+ "limit": page_limit,
76
+ "start": current_start,
77
+ }
78
+ if applied_expand:
79
+ params["expand"] = ",".join(applied_expand)
80
+
81
+ metrics.mark_request()
82
+ # Use SDK's cql method instead of raw request
83
+ payload = self._http.search_cql(cql, limit=page_limit, expand=applied_expand, start=current_start)
84
+ results = payload.get("results", [])
85
+ count = len(results)
86
+ if not results:
87
+ break
88
+ metrics.mark_processed(count)
89
+ metrics.scanned += count
90
+ for item in results:
91
+ fetched += 1
92
+ yield item
93
+
94
+ current_start = payload.get("start", current_start) + count
95
+ if max_results is not None and fetched >= max_results:
96
+ break
97
+
98
+ metrics.mark_finished()
99
+ self._last_metrics = metrics
100
+
101
+ def snapshot(
102
+ self,
103
+ cql: str,
104
+ *,
105
+ limit: int = 25,
106
+ max_results: int | None = None,
107
+ expand: Sequence[str] | None = None,
108
+ ) -> dict[str, Any]:
109
+ """Return an aggregated snapshot for reporting pipelines."""
110
+
111
+ results: list[dict[str, Any]] = []
112
+ for item in self.scan(cql, limit=limit, max_results=max_results, expand=expand):
113
+ results.append(item)
114
+ return {
115
+ "cql": cql,
116
+ "limit": limit,
117
+ "maxResults": max_results,
118
+ "count": len(results),
119
+ "results": results,
120
+ }
121
+
122
+ @property
123
+ def last_metrics(self) -> BatchMetrics | None:
124
+ return self._last_metrics
125
+
126
+
127
+ class WebhookClient:
128
+ """Manage Confluence Server/DC webhooks via REST v1."""
129
+
130
+ _BASE_PATH = "/rest/webhooks/1.0/webhook"
131
+
132
+ def __init__(self, http_client: ConfluenceClient) -> None:
133
+ self._http = http_client
134
+
135
+ def create(
136
+ self,
137
+ *,
138
+ name: str,
139
+ url: str,
140
+ events: Iterable[str],
141
+ active: bool = True,
142
+ filters: dict[str, Any] | None = None,
143
+ ) -> dict[str, Any]:
144
+ payload = {
145
+ "name": name,
146
+ "url": url,
147
+ "events": list(events),
148
+ "excludeBody": False,
149
+ "active": active,
150
+ }
151
+ if filters:
152
+ payload["filters"] = filters
153
+ response = self._http.request("POST", self._BASE_PATH, json=payload)
154
+ return response.json()
155
+
156
+ def list(self) -> builtins.list[dict[str, Any]]:
157
+ response = self._http.request("GET", self._BASE_PATH)
158
+ payload = response.json()
159
+ return payload if isinstance(payload, list) else payload.get("results", [])
160
+
161
+ def delete(self, webhook_id: str) -> None:
162
+ self._http.request("DELETE", f"{self._BASE_PATH}/{webhook_id}")
163
+
164
+
165
+ __all__ = ["BatchRunner", "BatchMetrics", "WebhookClient"]
@@ -0,0 +1,78 @@
1
+ """Run reporting utilities for the Confluence orchestrator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass
7
+ from datetime import UTC, datetime
8
+ from pathlib import Path
9
+
10
+ TIMESTAMP_FMT = "%Y-%m-%dT%H-%M-%SZ"
11
+
12
+
13
+ @dataclass(slots=True)
14
+ class RunSummary:
15
+ command: str
16
+ args: list[str]
17
+ server: str
18
+ exit_code: int
19
+ duration_ms: int
20
+ started_at: datetime
21
+ finished_at: datetime
22
+
23
+ @property
24
+ def success(self) -> bool:
25
+ return self.exit_code == 0
26
+
27
+
28
+ def _ensure_directory(base: Path, timestamp: datetime) -> Path:
29
+ day_dir = base / str(timestamp.year) / f"{timestamp.month:02d}" / f"{timestamp.day:02d}"
30
+ day_dir.mkdir(parents=True, exist_ok=True)
31
+ return day_dir
32
+
33
+
34
+ def write_reports(
35
+ summary: RunSummary,
36
+ *,
37
+ base_dir: Path,
38
+ include_markdown: bool = True,
39
+ ) -> Path:
40
+ """Persist JSON (and optional Markdown) reports and return JSON path."""
41
+
42
+ timestamp = summary.finished_at.astimezone(UTC)
43
+ directory = _ensure_directory(base_dir, timestamp)
44
+ slug = f"{timestamp.strftime(TIMESTAMP_FMT)}_{summary.command}"
45
+
46
+ json_path = directory / f"{slug}.json"
47
+ json_payload = {
48
+ "command": summary.command,
49
+ "args": summary.args,
50
+ "server": summary.server,
51
+ "exit_code": summary.exit_code,
52
+ "duration_ms": summary.duration_ms,
53
+ "started_at": summary.started_at.astimezone(UTC).isoformat().replace("+00:00", "Z"),
54
+ "finished_at": summary.finished_at.astimezone(UTC).isoformat().replace("+00:00", "Z"),
55
+ "success": summary.success,
56
+ }
57
+ json_path.write_text(json.dumps(json_payload, indent=2, sort_keys=True))
58
+
59
+ if include_markdown:
60
+ md_path = directory / f"{slug}.md"
61
+ status = "✅ Success" if summary.success else "❌ Failure"
62
+ md_lines = [
63
+ f"# Confluence Run Report — {summary.command}",
64
+ "",
65
+ f"- **Status**: {status} (exit code {summary.exit_code})",
66
+ f"- **Server**: {summary.server}",
67
+ f"- **Duration**: {summary.duration_ms} ms",
68
+ f"- **Started**: {json_payload['started_at']}",
69
+ f"- **Finished**: {json_payload['finished_at']}",
70
+ ]
71
+ if summary.args:
72
+ md_lines.append(f"- **Arguments**: {' '.join(summary.args)}")
73
+ md_path.write_text("\n".join(md_lines) + "\n")
74
+
75
+ return json_path
76
+
77
+
78
+ __all__ = ["RunSummary", "write_reports"]
@@ -0,0 +1,121 @@
1
+ """Confluence page tree traversal and export utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ import structlog
9
+
10
+ from .http import ConfluenceClient
11
+
12
+
13
+ @dataclass
14
+ class TreeNode:
15
+ """Represents a Confluence page in the tree."""
16
+
17
+ page_id: str
18
+ title: str
19
+ space_key: str
20
+ url: str
21
+ children: list[TreeNode] = field(default_factory=list)
22
+ attachments: list[dict[str, Any]] = field(default_factory=list)
23
+ body: str | None = None
24
+
25
+ def to_dict(self) -> dict[str, Any]:
26
+ """Convert tree node to dictionary."""
27
+ return {
28
+ "id": self.page_id,
29
+ "title": self.title,
30
+ "space": self.space_key,
31
+ "url": self.url,
32
+ "body": self.body,
33
+ "attachments": self.attachments,
34
+ "children": [child.to_dict() for child in self.children],
35
+ }
36
+
37
+
38
+ class TreeExporter:
39
+ """Recursively fetch Confluence page tree."""
40
+
41
+ def __init__(self, client: ConfluenceClient) -> None:
42
+ self._client = client
43
+ self._log = structlog.get_logger(__name__)
44
+
45
+ def build_tree(
46
+ self,
47
+ root_id: str,
48
+ *,
49
+ depth: int = -1,
50
+ include_content: bool = False,
51
+ include_attachments: bool = False,
52
+ ) -> TreeNode:
53
+ """
54
+ Recursively build a tree of pages.
55
+
56
+ Args:
57
+ root_id: The root page ID to start from.
58
+ depth: How deep to recurse. -1 for infinite, 0 for just root.
59
+ include_content: Whether to fetch page body (storage format).
60
+ include_attachments: Whether to fetch attachment metadata.
61
+ """
62
+ self._log.info("fetching_page", page_id=root_id)
63
+
64
+ # Prepare expand fields
65
+ expand = ["space"]
66
+ if include_content:
67
+ expand.append("body.storage")
68
+
69
+ # Fetch page details
70
+ page = self._client.get_page(root_id, expand=expand)
71
+ title = page.get("title", "Unknown")
72
+ space_key = page.get("space", {}).get("key", "Unknown")
73
+ links = page.get("_links", {})
74
+ base_url = links.get("base", "")
75
+ web_ui = links.get("webui", "")
76
+ full_url = f"{base_url}{web_ui}" if base_url and web_ui else web_ui
77
+
78
+ body = None
79
+ if include_content:
80
+ body = page.get("body", {}).get("storage", {}).get("value")
81
+
82
+ attachments = []
83
+ if include_attachments:
84
+ try:
85
+ # This fetches metadata, not content
86
+ attachments = self._client.get_attachments(root_id)
87
+ except Exception as e:
88
+ self._log.warning("failed_fetching_attachments", page_id=root_id, error=str(e))
89
+
90
+ node = TreeNode(
91
+ page_id=root_id,
92
+ title=title,
93
+ space_key=space_key,
94
+ url=full_url,
95
+ children=[],
96
+ attachments=attachments,
97
+ body=body,
98
+ )
99
+
100
+ # Recurse if depth allows
101
+ if depth != 0:
102
+ try:
103
+ # Fetch immediate children summaries
104
+ children_summaries = self._client.get_child_pages(root_id)
105
+
106
+ next_depth = depth - 1 if depth > 0 else -1
107
+
108
+ for child_summary in children_summaries:
109
+ child_id = child_summary.get("id")
110
+ if child_id:
111
+ child_node = self.build_tree(
112
+ child_id,
113
+ depth=next_depth,
114
+ include_content=include_content,
115
+ include_attachments=include_attachments,
116
+ )
117
+ node.children.append(child_node)
118
+ except Exception as e:
119
+ self._log.warning("failed_fetching_children", page_id=root_id, error=str(e))
120
+
121
+ return node
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Sync PDF files from markdown sources.
4
+
5
+ This script:
6
+ 1. Scans directory for markdown files
7
+ 2. Checks if corresponding PDF exists
8
+ 3. Compares modification times
9
+ 4. Regenerates PDFs using vds-cli pdf md2pdf when markdown is newer
10
+ 5. Reports summary of regenerated/skipped files
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import subprocess
16
+ import sys
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ import typer
21
+ from rich.console import Console
22
+ from rich.progress import Progress, SpinnerColumn, TextColumn
23
+ from rich.table import Table
24
+
25
+ app = typer.Typer(help="Sync PDF files from markdown sources")
26
+ console = Console()
27
+
28
+ # Path to vds-scripts for running vds-cli (relative to script location)
29
+ VDS_SCRIPTS = Path(__file__).resolve().parent.parent
30
+
31
+
32
+ def find_markdown_files(directory: Path, pattern: str | None = None, recursive: bool = True) -> list[Path]:
33
+ """Find all markdown files in directory, optionally matching pattern."""
34
+ if recursive:
35
+ md_files = sorted(directory.rglob("*.md"))
36
+ else:
37
+ md_files = sorted(directory.glob("*.md"))
38
+ if pattern:
39
+ md_files = [f for f in md_files if pattern.lower() in f.name.lower()]
40
+ return md_files
41
+
42
+
43
+ def get_corresponding_pdf(md_file: Path) -> Path:
44
+ """Get corresponding PDF path for a markdown file."""
45
+ return md_file.with_suffix(".pdf")
46
+
47
+
48
+ def needs_regeneration(md_file: Path, pdf_file: Path, force: bool = False) -> bool:
49
+ """Check if PDF needs regeneration based on modification times."""
50
+ if force:
51
+ return True
52
+ if not pdf_file.exists():
53
+ return True
54
+ # Regenerate if markdown is newer than PDF
55
+ md_mtime = md_file.stat().st_mtime
56
+ pdf_mtime = pdf_file.stat().st_mtime
57
+ return md_mtime > pdf_mtime
58
+
59
+
60
+ def regenerate_pdf(md_file: Path, pdf_file: Path, dry_run: bool = False) -> tuple[bool, str]:
61
+ """Regenerate PDF from markdown using vds-cli pdf md2pdf."""
62
+ if dry_run:
63
+ return True, "Would regenerate (dry-run)"
64
+
65
+ try:
66
+ # Ensure output directory exists
67
+ pdf_file.parent.mkdir(parents=True, exist_ok=True)
68
+
69
+ # Call vds-cli pdf md2pdf
70
+ cmd = [
71
+ "uv",
72
+ "run",
73
+ "--project",
74
+ "vds_cli",
75
+ "vds-cli",
76
+ "pdf",
77
+ "--",
78
+ "md2pdf",
79
+ str(md_file),
80
+ "--output",
81
+ str(pdf_file),
82
+ ]
83
+
84
+ result = subprocess.run(
85
+ cmd,
86
+ cwd=str(VDS_SCRIPTS),
87
+ capture_output=True,
88
+ text=True,
89
+ timeout=300, # 5 minute timeout
90
+ )
91
+
92
+ if result.returncode == 0:
93
+ return True, "Regenerated successfully"
94
+ else:
95
+ error_msg = result.stderr.strip() or result.stdout.strip()
96
+ return False, f"Error: {error_msg}"
97
+
98
+ except subprocess.TimeoutExpired:
99
+ return False, "Timeout after 5 minutes"
100
+ except Exception as e:
101
+ return False, f"Exception: {str(e)}"
102
+
103
+
104
+ @app.command()
105
+ def sync(
106
+ directory: Path = typer.Argument(
107
+ Path("."), help="Directory to scan for markdown files"
108
+ ),
109
+ pattern: str | None = typer.Option(None, "--pattern", "-p", help="Filter markdown files by pattern"),
110
+ dry_run: bool = typer.Option(False, "--dry-run", help="Preview changes without regenerating PDFs"),
111
+ force: bool = typer.Option(False, "--force", help="Regenerate all PDFs regardless of timestamps"),
112
+ recursive: bool = typer.Option(True, "--recursive/--no-recursive", help="Scan subdirectories recursively"),
113
+ ) -> None:
114
+ """Sync PDFs from markdown sources."""
115
+
116
+ directory = directory.resolve()
117
+ if not directory.exists() or not directory.is_dir():
118
+ console.print(f"[red]Error: Directory not found: {directory}[/red]")
119
+ raise typer.Exit(code=1) from None
120
+
121
+ try:
122
+ # Find markdown files
123
+ console.print(f"[bold]Scanning markdown files in:[/bold] {directory}")
124
+ md_files = find_markdown_files(directory, pattern, recursive)
125
+
126
+ if not md_files:
127
+ console.print("[yellow]No markdown files found[/yellow]")
128
+ raise typer.Exit(code=0) from None
129
+
130
+ console.print(f"Found [green]{len(md_files)}[/green] markdown file(s)")
131
+
132
+ # Check each markdown file
133
+ to_regenerate: list[tuple[Path, Path]] = []
134
+ skipped: list[tuple[Path, Path]] = []
135
+ errors: list[tuple[Path, str]] = []
136
+
137
+ console.print("\n[bold]Checking PDF status...[/bold]")
138
+ for md_file in md_files:
139
+ pdf_file = get_corresponding_pdf(md_file)
140
+ if needs_regeneration(md_file, pdf_file, force):
141
+ to_regenerate.append((md_file, pdf_file))
142
+ else:
143
+ skipped.append((md_file, pdf_file))
144
+
145
+ # Display sync plan
146
+ table = Table(title="Sync Plan", show_header=True, header_style="bold magenta")
147
+ table.add_column("Markdown File", style="cyan")
148
+ table.add_column("PDF File", style="green")
149
+ table.add_column("Action", style="yellow")
150
+
151
+ for md_file, pdf_file in to_regenerate:
152
+ status = "REGENERATE" if pdf_file.exists() else "CREATE"
153
+ table.add_row(
154
+ str(md_file.relative_to(directory)),
155
+ str(pdf_file.relative_to(directory)),
156
+ f"[yellow]{status}[/yellow]",
157
+ )
158
+
159
+ for md_file, pdf_file in skipped:
160
+ table.add_row(
161
+ str(md_file.relative_to(directory)),
162
+ str(pdf_file.relative_to(directory)),
163
+ "[green]SKIP (in sync)[/green]",
164
+ )
165
+
166
+ if to_regenerate:
167
+ console.print(table)
168
+ console.print(f"\n[bold]Summary:[/bold] {len(to_regenerate)} to regenerate, {len(skipped)} skipped")
169
+
170
+ if dry_run:
171
+ console.print("\n[yellow]DRY RUN: No PDFs regenerated[/yellow]")
172
+ raise typer.Exit(code=0) from None
173
+
174
+ # Regenerate PDFs
175
+ console.print("\n[bold]Regenerating PDFs...[/bold]")
176
+
177
+ with Progress(
178
+ SpinnerColumn(),
179
+ TextColumn("[progress.description]{task.description}"),
180
+ console=console,
181
+ ) as progress:
182
+ for md_file, pdf_file in to_regenerate:
183
+ task = progress.add_task(f"Regenerating {md_file.name}...", total=None)
184
+
185
+ success, message = regenerate_pdf(md_file, pdf_file, dry_run=False)
186
+ progress.update(task, completed=True)
187
+
188
+ if success:
189
+ console.print(f" [green]✓[/green] {md_file.name} → {pdf_file.name}")
190
+ else:
191
+ console.print(f" [red]✗[/red] {md_file.name}: {message}")
192
+ errors.append((md_file, message))
193
+
194
+ if errors:
195
+ console.print(f"\n[yellow]Completed with {len(errors)} error(s)[/yellow]")
196
+ for md_file, error_msg in errors:
197
+ console.print(f" • {md_file.name}: {error_msg}")
198
+ raise typer.Exit(code=2) from None
199
+ else:
200
+ console.print(f"\n[bold green]✓ All PDFs regenerated successfully![/bold green]")
201
+ else:
202
+ console.print("\n[green]All PDFs are in sync with markdown sources[/green]")
203
+
204
+ except Exception as exc:
205
+ console.print(f"[red]Error: {exc}[/red]")
206
+ raise typer.Exit(code=1) from None
207
+
208
+
209
+ if __name__ == "__main__":
210
+ app()
211
+
212
+
213
+