@ngocsangairvds/vsaf 4.0.6 → 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 (1039) 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/install-deps.mjs +20 -4
  5. package/skills/vds-skill/vds-scripts/.claude/phase7-CLOSURE.md +100 -0
  6. package/skills/vds-skill/vds-scripts/.dockerignore +62 -0
  7. package/skills/vds-skill/vds-scripts/.github/ISSUE_TEMPLATE/cli-change.md +92 -0
  8. package/skills/vds-skill/vds-scripts/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +48 -0
  9. package/skills/vds-skill/vds-scripts/.github/workflows/chaos-smoke.yml +266 -0
  10. package/skills/vds-skill/vds-scripts/.github/workflows/confluence-sync.yml +44 -0
  11. package/skills/vds-skill/vds-scripts/.github/workflows/docs-confluence-evidence.yml +170 -0
  12. package/skills/vds-skill/vds-scripts/.github/workflows/docs-quality.yml +59 -0
  13. package/skills/vds-skill/vds-scripts/.github/workflows/lint-and-test.yml +90 -0
  14. package/skills/vds-skill/vds-scripts/.github/workflows/scheduler-load-smoke.yml +104 -0
  15. package/skills/vds-skill/vds-scripts/.github/workflows/telegram-bridge-ci.yml +131 -0
  16. package/skills/vds-skill/vds-scripts/.graphifyignore +29 -0
  17. package/skills/vds-skill/vds-scripts/.importlinter +86 -0
  18. package/skills/vds-skill/vds-scripts/.mcp.json +11 -0
  19. package/skills/vds-skill/vds-scripts/.pre-commit-config.yaml +62 -0
  20. package/skills/vds-skill/vds-scripts/.ruffignore +3 -0
  21. package/skills/vds-skill/vds-scripts/.secrets.baseline +133 -0
  22. package/skills/vds-skill/vds-scripts/AGENTS.md +250 -0
  23. package/skills/vds-skill/vds-scripts/AGENTS.vi.md +92 -0
  24. package/skills/vds-skill/vds-scripts/ECOSYSTEM-CHANGELOG.md +52 -0
  25. package/skills/vds-skill/vds-scripts/ECOSYSTEM-DOCS.md +602 -0
  26. package/skills/vds-skill/vds-scripts/ECOSYSTEM_ALIGNMENT.md +133 -0
  27. package/skills/vds-skill/vds-scripts/Makefile +119 -0
  28. package/skills/vds-skill/vds-scripts/README.md +103 -0
  29. package/skills/vds-skill/vds-scripts/bitbucket_manifest_mapping.toml +34 -0
  30. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/ARCHITECTURE_ANALYSIS.md +258 -0
  31. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/BITBUCKET_API_PRACTICES.md +393 -0
  32. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/EVALUATION_REPORT.md +61 -0
  33. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/FEATURES.md +908 -0
  34. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/README.md +817 -0
  35. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/pyproject.toml +49 -0
  36. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/__init__.py +50 -0
  37. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/async_client.py +641 -0
  38. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/cli.py +2271 -0
  39. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/client.py +2693 -0
  40. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/config.py +186 -0
  41. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/errors.py +34 -0
  42. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/factory.py +185 -0
  43. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/parsers.py +113 -0
  44. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/protocols.py +244 -0
  45. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/repo_ops.py +325 -0
  46. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/__init__.py +8 -0
  47. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/conftest.py +65 -0
  48. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_advanced_search.py +155 -0
  49. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_async_client.py +505 -0
  50. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_branch_permissions.py +172 -0
  51. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_cli.py +113 -0
  52. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_cli_archive.py +122 -0
  53. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_cli_clone.py +131 -0
  54. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client.py +207 -0
  55. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_archive.py +73 -0
  56. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_branch_conditions.py +101 -0
  57. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_code_advanced.py +180 -0
  58. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_code_file.py +33 -0
  59. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_deployment_environments.py +193 -0
  60. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_issues.py +163 -0
  61. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_pipelines_advanced.py +171 -0
  62. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_pr_blockers.py +118 -0
  63. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_client_repository_variables.py +155 -0
  64. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_code.py +98 -0
  65. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_code_advanced.py +279 -0
  66. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_code_insights.py +334 -0
  67. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_conditions.py +149 -0
  68. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_config.py +297 -0
  69. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_deployment_env.py +352 -0
  70. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_errors.py +67 -0
  71. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_factory.py +352 -0
  72. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_fork_operations.py +203 -0
  73. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_issue_cli.py +262 -0
  74. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_pipeline_advanced.py +265 -0
  75. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_pr_blocker.py +206 -0
  76. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_protocols.py +336 -0
  77. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_repo_ops_archive.py +169 -0
  78. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_repo_ops_clone.py +115 -0
  79. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_repo_ops_parsing.py +149 -0
  80. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_repo_settings.py +336 -0
  81. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_repo_variables.py +266 -0
  82. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_webhooks.py +188 -0
  83. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/test_workspace.py +234 -0
  84. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/unit/__init__.py +0 -0
  85. package/skills/vds-skill/vds-scripts/bitbucket_orchestrator/tests/unit/test_parsers.py +254 -0
  86. package/skills/vds-skill/vds-scripts/brd_orchestrator/README.md +29 -0
  87. package/skills/vds-skill/vds-scripts/brd_orchestrator/pyproject.toml +63 -0
  88. package/skills/vds-skill/vds-scripts/brd_orchestrator/src/vds_brd_orchestrator/__init__.py +17 -0
  89. package/skills/vds-skill/vds-scripts/brd_orchestrator/src/vds_brd_orchestrator/cli.py +187 -0
  90. package/skills/vds-skill/vds-scripts/brd_orchestrator/src/vds_brd_orchestrator/validator.py +121 -0
  91. package/skills/vds-skill/vds-scripts/brd_orchestrator/tests/__init__.py +0 -0
  92. package/skills/vds-skill/vds-scripts/brd_orchestrator/tests/test_cli.py +62 -0
  93. package/skills/vds-skill/vds-scripts/brd_orchestrator/tests/test_validator.py +33 -0
  94. package/skills/vds-skill/vds-scripts/code/code_evidence_pack.json +435 -0
  95. package/skills/vds-skill/vds-scripts/confluence_orchestrator/Dockerfile +19 -0
  96. package/skills/vds-skill/vds-scripts/confluence_orchestrator/README.md +479 -0
  97. package/skills/vds-skill/vds-scripts/confluence_orchestrator/SYNC_SCRIPTS.md +127 -0
  98. package/skills/vds-skill/vds-scripts/confluence_orchestrator/SYNC_STANDARDIZATION.md +108 -0
  99. package/skills/vds-skill/vds-scripts/confluence_orchestrator/pyproject.toml +50 -0
  100. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/__init__.py +56 -0
  101. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/async_client.py +100 -0
  102. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/cli.py +3160 -0
  103. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/config.py +215 -0
  104. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/content.py +368 -0
  105. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/content_v2.py +144 -0
  106. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/crawl_tree.py +1833 -0
  107. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/errors.py +44 -0
  108. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/eventing.py +111 -0
  109. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/http.py +1850 -0
  110. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/orchestration.py +166 -0
  111. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/protocols.py +61 -0
  112. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/reporting.py +78 -0
  113. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/tree.py +122 -0
  114. package/skills/vds-skill/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/tree_copier.py +431 -0
  115. package/skills/vds-skill/vds-scripts/confluence_orchestrator/sync_pdfs_from_markdown.py +203 -0
  116. package/skills/vds-skill/vds-scripts/confluence_orchestrator/sync_pdfs_to_confluence.py +299 -0
  117. package/skills/vds-skill/vds-scripts/confluence_orchestrator/sync_png_attachments.py +299 -0
  118. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/conftest.py +46 -0
  119. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_advanced_content.py +252 -0
  120. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_advanced_search.py +193 -0
  121. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_async_client.py +104 -0
  122. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_cache_management.py +246 -0
  123. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_cli.py +716 -0
  124. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_config.py +130 -0
  125. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_content.py +192 -0
  126. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_content_flags.py +27 -0
  127. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_content_labels.py +94 -0
  128. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_crawl_tree.py +2252 -0
  129. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_degraded_write_safety.py +176 -0
  130. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_draft_management.py +225 -0
  131. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_errors.py +75 -0
  132. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_eventing.py +73 -0
  133. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_eventing_chaos.py +37 -0
  134. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_eventing_rate_limit.py +44 -0
  135. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_eventing_timeout.py +49 -0
  136. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_export.py +231 -0
  137. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_history.py +217 -0
  138. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_http.py +375 -0
  139. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_orchestration.py +93 -0
  140. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_reporting.py +24 -0
  141. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_search_cql.py +36 -0
  142. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_space_management.py +236 -0
  143. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_space_permissions.py +384 -0
  144. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_tree_copier.py +644 -0
  145. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_tree_copier_remap.py +289 -0
  146. package/skills/vds-skill/vds-scripts/confluence_orchestrator/tests/test_user_group_management.py +387 -0
  147. package/skills/vds-skill/vds-scripts/diagram_generator/README.md +663 -0
  148. package/skills/vds-skill/vds-scripts/diagram_generator/ci_validate.sh +16 -0
  149. 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
  150. 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
  151. 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
  152. 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
  153. 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
  154. 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
  155. package/skills/vds-skill/vds-scripts/diagram_generator/examples/github-actions-validate.yml +39 -0
  156. package/skills/vds-skill/vds-scripts/diagram_generator/generate_all_diagrams.py +827 -0
  157. package/skills/vds-skill/vds-scripts/diagram_generator/generate_insurance_c4_diagrams.py +261 -0
  158. package/skills/vds-skill/vds-scripts/diagram_generator/generate_insurance_c4_quick.py +486 -0
  159. package/skills/vds-skill/vds-scripts/diagram_generator/pyproject.toml +28 -0
  160. package/skills/vds-skill/vds-scripts/diagram_generator/render_png.py +59 -0
  161. package/skills/vds-skill/vds-scripts/diagram_generator/src/vds_diagram_generator/__init__.py +3 -0
  162. package/skills/vds-skill/vds-scripts/diagram_generator/src/vds_diagram_generator/cli.py +50 -0
  163. package/skills/vds-skill/vds-scripts/diagram_generator/test_c4_hierarchical.py +142 -0
  164. package/skills/vds-skill/vds-scripts/diagram_generator/test_c4_quick.py +131 -0
  165. package/skills/vds-skill/vds-scripts/diagram_generator/tests/__init__.py +0 -0
  166. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_analyzer_completeness.py +260 -0
  167. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_c4_syntax_correctness.py +138 -0
  168. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_component_coverage.py +182 -0
  169. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_mermaid_output.py +80 -0
  170. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_png_generation.py +112 -0
  171. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_scenario_templates.py +15 -0
  172. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_sequence_accuracy.py +93 -0
  173. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_structurizr_export.py +177 -0
  174. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_style_consistency.py +174 -0
  175. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_usecase_generator.py +201 -0
  176. package/skills/vds-skill/vds-scripts/diagram_generator/tests/test_usecase_integration.py +124 -0
  177. package/skills/vds-skill/vds-scripts/docker/.dockerignore +38 -0
  178. package/skills/vds-skill/vds-scripts/docker/ADR.md +392 -0
  179. package/skills/vds-skill/vds-scripts/docker/Dockerfile +68 -0
  180. package/skills/vds-skill/vds-scripts/docker/MIGRATION.md +453 -0
  181. package/skills/vds-skill/vds-scripts/docker/README.md +347 -0
  182. package/skills/vds-skill/vds-scripts/docker/ROLLBACK.md +596 -0
  183. package/skills/vds-skill/vds-scripts/docker/compose.phase2-verification.yml +31 -0
  184. package/skills/vds-skill/vds-scripts/docker/docker-compose.cli.yml +206 -0
  185. package/skills/vds-skill/vds-scripts/docker/docker-compose.infra.yml +276 -0
  186. package/skills/vds-skill/vds-scripts/docker/docker-compose.services.yml +425 -0
  187. package/skills/vds-skill/vds-scripts/docker/infrastructure/init-schemas.sql +177 -0
  188. package/skills/vds-skill/vds-scripts/docker/infrastructure/pgbouncer/pgbouncer.ini +75 -0
  189. package/skills/vds-skill/vds-scripts/docker/infrastructure/pgbouncer/userlist.txt +50 -0
  190. package/skills/vds-skill/vds-scripts/docker/infrastructure/pgbouncer/userlist.txt.template +36 -0
  191. package/skills/vds-skill/vds-scripts/docs/.confluence-evidence/.gitkeep +0 -0
  192. package/skills/vds-skill/vds-scripts/docs/.confluence-evidence/PREFLIGHT.md +132 -0
  193. package/skills/vds-skill/vds-scripts/docs/.confluence-evidence/README.md +84 -0
  194. package/skills/vds-skill/vds-scripts/docs/.confluence.yaml +157 -0
  195. package/skills/vds-skill/vds-scripts/docs/.freshness.yaml +54 -0
  196. package/skills/vds-skill/vds-scripts/docs/README.md +235 -0
  197. package/skills/vds-skill/vds-scripts/docs/agents/README.md +33 -0
  198. package/skills/vds-skill/vds-scripts/docs/agents/explanation/data-flow.md +132 -0
  199. package/skills/vds-skill/vds-scripts/docs/agents/explanation/development-roadmap.md +49 -0
  200. package/skills/vds-skill/vds-scripts/docs/agents/explanation/features-overview.md +62 -0
  201. package/skills/vds-skill/vds-scripts/docs/agents/explanation/index.md +36 -0
  202. package/skills/vds-skill/vds-scripts/docs/agents/explanation/runtime-verification-and-gap-reporting.md +127 -0
  203. package/skills/vds-skill/vds-scripts/docs/agents/explanation/system-architecture.md +139 -0
  204. package/skills/vds-skill/vds-scripts/docs/agents/explanation/whats-new.md +75 -0
  205. package/skills/vds-skill/vds-scripts/docs/agents/explanation/who-ecosystem-introduction.md +65 -0
  206. package/skills/vds-skill/vds-scripts/docs/agents/explanation/who-ecosystem-model.md +41 -0
  207. package/skills/vds-skill/vds-scripts/docs/agents/how-to/02-using-vds-ai-memory.md +98 -0
  208. package/skills/vds-skill/vds-scripts/docs/agents/how-to/03-memory-cross-agent.md +241 -0
  209. package/skills/vds-skill/vds-scripts/docs/agents/how-to/04-using-progress-reports.md +240 -0
  210. package/skills/vds-skill/vds-scripts/docs/agents/how-to/08-semantic-search.md +46 -0
  211. package/skills/vds-skill/vds-scripts/docs/agents/how-to/apply-phase3-migration.md +148 -0
  212. package/skills/vds-skill/vds-scripts/docs/agents/how-to/choose-the-right-command-or-skill.md +34 -0
  213. package/skills/vds-skill/vds-scripts/docs/agents/how-to/contribute-new-orchestrator.md +149 -0
  214. package/skills/vds-skill/vds-scripts/docs/agents/how-to/decision-tree.md +63 -0
  215. package/skills/vds-skill/vds-scripts/docs/agents/how-to/first-audit-run.md +83 -0
  216. package/skills/vds-skill/vds-scripts/docs/agents/how-to/index.md +49 -0
  217. package/skills/vds-skill/vds-scripts/docs/agents/how-to/install-and-bootstrap-who-scripts-and-skills.md +314 -0
  218. package/skills/vds-skill/vds-scripts/docs/agents/how-to/orchestrator-workflows/analytics-pipeline-workflow.md +165 -0
  219. package/skills/vds-skill/vds-scripts/docs/agents/how-to/orchestrator-workflows/code-quality-gate-workflow.md +138 -0
  220. package/skills/vds-skill/vds-scripts/docs/agents/how-to/orchestrator-workflows/confluence-bitbucket-sync-workflow.md +130 -0
  221. package/skills/vds-skill/vds-scripts/docs/agents/how-to/orchestrator-workflows/document-delivery-workflow.md +142 -0
  222. package/skills/vds-skill/vds-scripts/docs/agents/how-to/orchestrator-workflows/memory-progress-workflow.md +140 -0
  223. package/skills/vds-skill/vds-scripts/docs/agents/how-to/orchestrator-workflows/research-spec-audit-workflow.md +135 -0
  224. package/skills/vds-skill/vds-scripts/docs/agents/how-to/phase131-all-project-preparation.md +211 -0
  225. package/skills/vds-skill/vds-scripts/docs/agents/how-to/phase131-bounded-parallel-analysis.md +123 -0
  226. package/skills/vds-skill/vds-scripts/docs/agents/how-to/phase131-confluence-upload-recovery.md +204 -0
  227. package/skills/vds-skill/vds-scripts/docs/agents/how-to/phase132-department-preparation.md +144 -0
  228. package/skills/vds-skill/vds-scripts/docs/agents/how-to/run-ecosystem-daily-report.md +213 -0
  229. package/skills/vds-skill/vds-scripts/docs/agents/how-to/tips-and-tricks.md +138 -0
  230. package/skills/vds-skill/vds-scripts/docs/agents/how-to/troubleshooting-guide.md +221 -0
  231. package/skills/vds-skill/vds-scripts/docs/agents/reference/agent-operational-contract.md +162 -0
  232. package/skills/vds-skill/vds-scripts/docs/agents/reference/alignment-phase179-report.md +144 -0
  233. package/skills/vds-skill/vds-scripts/docs/agents/reference/audit-triage-playbook.md +256 -0
  234. package/skills/vds-skill/vds-scripts/docs/agents/reference/backup-restore.md +132 -0
  235. package/skills/vds-skill/vds-scripts/docs/agents/reference/bitbucket-orchestrator.md +56 -0
  236. package/skills/vds-skill/vds-scripts/docs/agents/reference/brd-orchestrator.md +52 -0
  237. package/skills/vds-skill/vds-scripts/docs/agents/reference/capability-coverage-review.md +51 -0
  238. package/skills/vds-skill/vds-scripts/docs/agents/reference/ci-workflows.md +98 -0
  239. package/skills/vds-skill/vds-scripts/docs/agents/reference/circular-dependency-orchestrator.md +55 -0
  240. package/skills/vds-skill/vds-scripts/docs/agents/reference/cli-commands.md +583 -0
  241. package/skills/vds-skill/vds-scripts/docs/agents/reference/cli-development-standards.md +41 -0
  242. package/skills/vds-skill/vds-scripts/docs/agents/reference/cli-help-matrix.md +84 -0
  243. package/skills/vds-skill/vds-scripts/docs/agents/reference/common-errors.md +126 -0
  244. package/skills/vds-skill/vds-scripts/docs/agents/reference/configuration-reference.md +128 -0
  245. package/skills/vds-skill/vds-scripts/docs/agents/reference/confluence-orchestrator.md +56 -0
  246. package/skills/vds-skill/vds-scripts/docs/agents/reference/confluence-sync-target.md +111 -0
  247. package/skills/vds-skill/vds-scripts/docs/agents/reference/confluence-sync.md +46 -0
  248. package/skills/vds-skill/vds-scripts/docs/agents/reference/db-query-orchestrator.md +93 -0
  249. package/skills/vds-skill/vds-scripts/docs/agents/reference/diagrams-orchestrator.md +52 -0
  250. package/skills/vds-skill/vds-scripts/docs/agents/reference/ecosystem-daily-report.md +229 -0
  251. package/skills/vds-skill/vds-scripts/docs/agents/reference/elastic-orchestrator.md +57 -0
  252. package/skills/vds-skill/vds-scripts/docs/agents/reference/env-git-helper.md +216 -0
  253. package/skills/vds-skill/vds-scripts/docs/agents/reference/evolution-orchestrator.md +113 -0
  254. package/skills/vds-skill/vds-scripts/docs/agents/reference/excel-orchestrator.md +51 -0
  255. package/skills/vds-skill/vds-scripts/docs/agents/reference/git-orchestrator.md +62 -0
  256. package/skills/vds-skill/vds-scripts/docs/agents/reference/google-sheets-orchestrator.md +51 -0
  257. package/skills/vds-skill/vds-scripts/docs/agents/reference/grafana-orchestrator.md +52 -0
  258. package/skills/vds-skill/vds-scripts/docs/agents/reference/hexagonal-orchestrator.md +64 -0
  259. package/skills/vds-skill/vds-scripts/docs/agents/reference/index.md +36 -0
  260. package/skills/vds-skill/vds-scripts/docs/agents/reference/infrastructure-v2.15.md +67 -0
  261. package/skills/vds-skill/vds-scripts/docs/agents/reference/intellij-orchestrator.md +50 -0
  262. package/skills/vds-skill/vds-scripts/docs/agents/reference/jira-orchestrator.md +60 -0
  263. package/skills/vds-skill/vds-scripts/docs/agents/reference/links-orchestrator.md +57 -0
  264. package/skills/vds-skill/vds-scripts/docs/agents/reference/lint-cli.md +99 -0
  265. package/skills/vds-skill/vds-scripts/docs/agents/reference/lsp-orchestrator.md +51 -0
  266. package/skills/vds-skill/vds-scripts/docs/agents/reference/markdown-orchestrator.md +53 -0
  267. package/skills/vds-skill/vds-scripts/docs/agents/reference/mcp-orchestrator.md +88 -0
  268. package/skills/vds-skill/vds-scripts/docs/agents/reference/memory-orchestrator.md +53 -0
  269. package/skills/vds-skill/vds-scripts/docs/agents/reference/metabase-orchestrator.md +51 -0
  270. package/skills/vds-skill/vds-scripts/docs/agents/reference/migration-playbook.md +71 -0
  271. package/skills/vds-skill/vds-scripts/docs/agents/reference/multi-agent-orchestrator.md +52 -0
  272. package/skills/vds-skill/vds-scripts/docs/agents/reference/openapi-orchestrator.md +57 -0
  273. package/skills/vds-skill/vds-scripts/docs/agents/reference/orchestrator-architecture.md +194 -0
  274. package/skills/vds-skill/vds-scripts/docs/agents/reference/orchestrator-comparison-matrix.md +79 -0
  275. package/skills/vds-skill/vds-scripts/docs/agents/reference/orchestrator-index.md +73 -0
  276. package/skills/vds-skill/vds-scripts/docs/agents/reference/pdf-orchestrator.md +57 -0
  277. package/skills/vds-skill/vds-scripts/docs/agents/reference/portable-paths-and-config.md +0 -0
  278. package/skills/vds-skill/vds-scripts/docs/agents/reference/portable-paths-validation-matrix.md +129 -0
  279. package/skills/vds-skill/vds-scripts/docs/agents/reference/progress-orchestrator.md +51 -0
  280. package/skills/vds-skill/vds-scripts/docs/agents/reference/progress-report-cli.md +215 -0
  281. package/skills/vds-skill/vds-scripts/docs/agents/reference/public-interface-orchestrator.md +73 -0
  282. package/skills/vds-skill/vds-scripts/docs/agents/reference/research-orchestrator.md +53 -0
  283. package/skills/vds-skill/vds-scripts/docs/agents/reference/schema-orchestrator.md +57 -0
  284. package/skills/vds-skill/vds-scripts/docs/agents/reference/search-tools.md +34 -0
  285. package/skills/vds-skill/vds-scripts/docs/agents/reference/skills-commands.md +256 -0
  286. package/skills/vds-skill/vds-scripts/docs/agents/reference/skills-reference.md +32 -0
  287. package/skills/vds-skill/vds-scripts/docs/agents/reference/sonarqube-orchestrator.md +62 -0
  288. package/skills/vds-skill/vds-scripts/docs/agents/reference/spec-orchestrator.md +56 -0
  289. package/skills/vds-skill/vds-scripts/docs/agents/reference/structure-orchestrator.md +69 -0
  290. package/skills/vds-skill/vds-scripts/docs/agents/reference/system-requirements.md +76 -0
  291. package/skills/vds-skill/vds-scripts/docs/agents/reference/tasks-orchestrator.md +53 -0
  292. package/skills/vds-skill/vds-scripts/docs/agents/reference/validation-and-sync-notes.md +54 -0
  293. package/skills/vds-skill/vds-scripts/docs/agents/reference/vds-ai-memory-api.md +51 -0
  294. package/skills/vds-skill/vds-scripts/docs/agents/reference/vds-cli-reference.md +34 -0
  295. package/skills/vds-skill/vds-scripts/docs/agents/reference/who-capability-inventory.md +96 -0
  296. package/skills/vds-skill/vds-scripts/docs/agents/reference/who-capability-routing-matrix.md +14 -0
  297. package/skills/vds-skill/vds-scripts/docs/agents/tutorials/feature-progression-guide.md +112 -0
  298. package/skills/vds-skill/vds-scripts/docs/agents/tutorials/index.md +36 -0
  299. package/skills/vds-skill/vds-scripts/docs/agents/tutorials/quick-start.md +50 -0
  300. package/skills/vds-skill/vds-scripts/docs/agents/tutorials/who-skills-and-scripts-onboarding.md +47 -0
  301. package/skills/vds-skill/vds-scripts/docs/agents/tutorials/zero-to-productive-developer.md +339 -0
  302. package/skills/vds-skill/vds-scripts/docs/confluence/IMPLEMENTATION-SUMMARY.md +78 -0
  303. package/skills/vds-skill/vds-scripts/docs/confluence/SYNC-GUIDE.md +47 -0
  304. package/skills/vds-skill/vds-scripts/docs/deployment/offline-docker-image-load.md +59 -0
  305. package/skills/vds-skill/vds-scripts/docs/evolution-auto-run-rollout.md +325 -0
  306. package/skills/vds-skill/vds-scripts/docs/evolution-loop-deep-integration.md +496 -0
  307. package/skills/vds-skill/vds-scripts/docs/evolution-loop-integration-guide.md +359 -0
  308. package/skills/vds-skill/vds-scripts/docs/openspace-schema-snapshot.md +73 -0
  309. package/skills/vds-skill/vds-scripts/docs/operations/sla-mttr-policy.md +44 -0
  310. package/skills/vds-skill/vds-scripts/docs/p0-closure-evidence/SUMMARY.md +58 -0
  311. package/skills/vds-skill/vds-scripts/docs/p4-closure-evidence/.gitkeep +0 -0
  312. package/skills/vds-skill/vds-scripts/docs/p4-closure-evidence/smoke-20260427T024137Z-b95b586b.json +15 -0
  313. package/skills/vds-skill/vds-scripts/docs/p8-preflight-evidence/alembic-and-runtime-advisory-locks.md +45 -0
  314. package/skills/vds-skill/vds-scripts/docs/p8-preflight-evidence/dbos-listen-notify.md +54 -0
  315. package/skills/vds-skill/vds-scripts/docs/p8-preflight-evidence/pgbouncer-search-path-empirical.md +110 -0
  316. package/skills/vds-skill/vds-scripts/docs/p8-preflight-evidence/pgvector-set-local-audit.md +51 -0
  317. package/skills/vds-skill/vds-scripts/docs/p8-preflight-evidence/topology-decision-session-mode.md +57 -0
  318. package/skills/vds-skill/vds-scripts/docs/phases/CHANGELOG.md +103 -0
  319. package/skills/vds-skill/vds-scripts/docs/phases/PHASE_125_COMPLETION_AND_MERGE.md +212 -0
  320. package/skills/vds-skill/vds-scripts/docs/phases/phase125/IMPLEMENTATION_REPORT.md +227 -0
  321. package/skills/vds-skill/vds-scripts/docs/phases/phase125/TSK-125.10-11-implementation-summary.md +196 -0
  322. package/skills/vds-skill/vds-scripts/docs/phases/phase125/profile-patch-ollama-local-anthropic.md +122 -0
  323. package/skills/vds-skill/vds-scripts/docs/phases/phase125_completion_summary.md +369 -0
  324. package/skills/vds-skill/vds-scripts/docs/phases/phase125_llm_analysis_skill.md +164 -0
  325. package/skills/vds-skill/vds-scripts/docs/phases/phase125_merge_complete.md +147 -0
  326. package/skills/vds-skill/vds-scripts/docs/phases/phase125_skill_runtime_closure_20260321.md +91 -0
  327. package/skills/vds-skill/vds-scripts/docs/phases/phase2-portable-paths/closure-handoff-summary-2026-03-23.md +290 -0
  328. package/skills/vds-skill/vds-scripts/docs/phases/phase2-portable-paths/remaining-risk-register-2026-03-25.md +143 -0
  329. package/skills/vds-skill/vds-scripts/docs/phases/phase2-portable-paths/verification-evidence-2026-03-23.md +135 -0
  330. package/skills/vds-skill/vds-scripts/docs/v0-sunset-known-issues.md +88 -0
  331. package/skills/vds-skill/vds-scripts/docs/vi/TRANSLATION-BACKLOG.md +72 -0
  332. package/skills/vds-skill/vds-scripts/docs/vi/agents/README.md +41 -0
  333. package/skills/vds-skill/vds-scripts/docs/vi/agents/explanation/features-overview.md +29 -0
  334. package/skills/vds-skill/vds-scripts/docs/vi/agents/explanation/index.md +14 -0
  335. package/skills/vds-skill/vds-scripts/docs/vi/agents/explanation/runtime-verification-and-gap-reporting.md +129 -0
  336. package/skills/vds-skill/vds-scripts/docs/vi/agents/explanation/whats-new.md +37 -0
  337. package/skills/vds-skill/vds-scripts/docs/vi/agents/explanation/who-ecosystem-introduction.md +21 -0
  338. package/skills/vds-skill/vds-scripts/docs/vi/agents/explanation/who-ecosystem-model.md +36 -0
  339. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/02-using-vds-ai-memory.md +100 -0
  340. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/03-memory-cross-agent.md +243 -0
  341. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/04-using-progress-reports.md +242 -0
  342. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/08-semantic-search.md +16 -0
  343. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/choose-the-right-command-or-skill.md +36 -0
  344. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/decision-tree.md +77 -0
  345. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/first-audit-run.md +85 -0
  346. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/index.md +21 -0
  347. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/install-and-bootstrap-who-scripts-and-skills.md +156 -0
  348. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/orchestrator-workflows/analytics-pipeline-workflow.md +174 -0
  349. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/orchestrator-workflows/code-quality-gate-workflow.md +147 -0
  350. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/orchestrator-workflows/confluence-bitbucket-sync-workflow.md +139 -0
  351. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/orchestrator-workflows/document-delivery-workflow.md +151 -0
  352. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/orchestrator-workflows/memory-progress-workflow.md +149 -0
  353. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/orchestrator-workflows/research-spec-audit-workflow.md +144 -0
  354. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/phase131-all-project-preparation.md +213 -0
  355. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/phase131-bounded-parallel-analysis.md +125 -0
  356. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/phase131-confluence-upload-recovery.md +206 -0
  357. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/phase132-department-preparation.md +146 -0
  358. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/tips-and-tricks.md +34 -0
  359. package/skills/vds-skill/vds-scripts/docs/vi/agents/how-to/troubleshooting-guide.md +36 -0
  360. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/agent-operational-contract.md +98 -0
  361. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/audit-triage-playbook.md +258 -0
  362. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/bitbucket-orchestrator.md +30 -0
  363. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/brd-orchestrator.md +29 -0
  364. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/capability-coverage-review.md +46 -0
  365. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/circular-dependency-orchestrator.md +29 -0
  366. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/cli-commands.md +409 -0
  367. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/cli-development-standards.md +19 -0
  368. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/cli-help-matrix.md +71 -0
  369. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/common-errors.md +133 -0
  370. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/configuration-reference.md +25 -0
  371. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/confluence-orchestrator.md +30 -0
  372. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/db-query-orchestrator.md +34 -0
  373. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/diagrams-orchestrator.md +31 -0
  374. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/elastic-orchestrator.md +30 -0
  375. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/evolution-orchestrator.md +31 -0
  376. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/excel-orchestrator.md +60 -0
  377. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/git-orchestrator.md +31 -0
  378. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/google-sheets-orchestrator.md +60 -0
  379. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/grafana-orchestrator.md +30 -0
  380. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/hexagonal-orchestrator.md +73 -0
  381. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/index.md +25 -0
  382. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/intellij-orchestrator.md +59 -0
  383. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/jira-orchestrator.md +32 -0
  384. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/links-orchestrator.md +66 -0
  385. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/lsp-orchestrator.md +60 -0
  386. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/markdown-orchestrator.md +62 -0
  387. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/mcp-orchestrator.md +34 -0
  388. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/memory-orchestrator.md +45 -0
  389. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/metabase-orchestrator.md +30 -0
  390. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/multi-agent-orchestrator.md +61 -0
  391. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/openapi-orchestrator.md +66 -0
  392. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/orchestrator-architecture.md +24 -0
  393. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/orchestrator-index.md +73 -0
  394. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/pdf-orchestrator.md +30 -0
  395. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/portable-paths-and-config.md +123 -0
  396. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/portable-paths-validation-matrix.md +131 -0
  397. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/progress-orchestrator.md +43 -0
  398. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/progress-report-cli.md +217 -0
  399. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/public-interface-orchestrator.md +82 -0
  400. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/research-orchestrator.md +45 -0
  401. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/schema-orchestrator.md +66 -0
  402. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/search-tools.md +19 -0
  403. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/skills-reference.md +27 -0
  404. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/sonarqube-orchestrator.md +71 -0
  405. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/spec-orchestrator.md +56 -0
  406. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/structure-orchestrator.md +78 -0
  407. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/system-requirements.md +30 -0
  408. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/tasks-orchestrator.md +45 -0
  409. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/validation-and-sync-notes.md +26 -0
  410. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/vds-ai-memory-api.md +53 -0
  411. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/vds-cli-reference.md +34 -0
  412. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/who-capability-inventory.md +98 -0
  413. package/skills/vds-skill/vds-scripts/docs/vi/agents/reference/who-capability-routing-matrix.md +16 -0
  414. package/skills/vds-skill/vds-scripts/docs/vi/agents/tutorials/feature-progression-guide.md +124 -0
  415. package/skills/vds-skill/vds-scripts/docs/vi/agents/tutorials/index.md +13 -0
  416. package/skills/vds-skill/vds-scripts/docs/vi/agents/tutorials/quick-start.md +30 -0
  417. package/skills/vds-skill/vds-scripts/docs/vi/agents/tutorials/who-skills-and-scripts-onboarding.md +42 -0
  418. package/skills/vds-skill/vds-scripts/docs/vi/agents/tutorials/zero-to-productive-developer.md +137 -0
  419. package/skills/vds-skill/vds-scripts/elastic_orchestrator/README.md +450 -0
  420. package/skills/vds-skill/vds-scripts/elastic_orchestrator/pyproject.toml +97 -0
  421. package/skills/vds-skill/vds-scripts/elastic_orchestrator/src/vds_elastic_orchestrator/__init__.py +81 -0
  422. package/skills/vds-skill/vds-scripts/elastic_orchestrator/src/vds_elastic_orchestrator/cli.py +652 -0
  423. package/skills/vds-skill/vds-scripts/elastic_orchestrator/src/vds_elastic_orchestrator/client.py +743 -0
  424. package/skills/vds-skill/vds-scripts/elastic_orchestrator/src/vds_elastic_orchestrator/config.py +208 -0
  425. package/skills/vds-skill/vds-scripts/elastic_orchestrator/src/vds_elastic_orchestrator/errors.py +34 -0
  426. package/skills/vds-skill/vds-scripts/elastic_orchestrator/src/vds_elastic_orchestrator/py.typed +0 -0
  427. package/skills/vds-skill/vds-scripts/elastic_orchestrator/tests/__init__.py +0 -0
  428. package/skills/vds-skill/vds-scripts/elastic_orchestrator/tests/conftest.py +227 -0
  429. package/skills/vds-skill/vds-scripts/elastic_orchestrator/tests/test_client.py +990 -0
  430. package/skills/vds-skill/vds-scripts/elastic_orchestrator/tests/test_config.py +268 -0
  431. package/skills/vds-skill/vds-scripts/elastic_orchestrator/tests/test_e2e_verification.py +272 -0
  432. package/skills/vds-skill/vds-scripts/elastic_orchestrator/tests/test_errors.py +78 -0
  433. package/skills/vds-skill/vds-scripts/excel_orchestrator/README.md +288 -0
  434. package/skills/vds-skill/vds-scripts/excel_orchestrator/RESEARCH_BASED_UPDATES_REPORT.md +261 -0
  435. package/skills/vds-skill/vds-scripts/excel_orchestrator/add_essential_missing_effort.py +255 -0
  436. package/skills/vds-skill/vds-scripts/excel_orchestrator/adjust_effort_complexity.py +184 -0
  437. package/skills/vds-skill/vds-scripts/excel_orchestrator/brd_analysis_and_task_breakdown.py +632 -0
  438. package/skills/vds-skill/vds-scripts/excel_orchestrator/brd_analysis_comprehensive.py +1029 -0
  439. package/skills/vds-skill/vds-scripts/excel_orchestrator/check_overlaps_and_brd_coverage.py +570 -0
  440. package/skills/vds-skill/vds-scripts/excel_orchestrator/clean_remarks_column.py +127 -0
  441. package/skills/vds-skill/vds-scripts/excel_orchestrator/comprehensive_brd_check.py +322 -0
  442. package/skills/vds-skill/vds-scripts/excel_orchestrator/create_buffered_summary.py +119 -0
  443. package/skills/vds-skill/vds-scripts/excel_orchestrator/create_service_totals_sheet.py +118 -0
  444. package/skills/vds-skill/vds-scripts/excel_orchestrator/examples/basic_operations.py +85 -0
  445. package/skills/vds-skill/vds-scripts/excel_orchestrator/expand_all_tasks.py +341 -0
  446. package/skills/vds-skill/vds-scripts/excel_orchestrator/expand_tasks.py +304 -0
  447. package/skills/vds-skill/vds-scripts/excel_orchestrator/fill_brd_references.py +347 -0
  448. package/skills/vds-skill/vds-scripts/excel_orchestrator/fill_remarks_and_colors.py +132 -0
  449. package/skills/vds-skill/vds-scripts/excel_orchestrator/finalize_brd_and_cleanup.py +295 -0
  450. package/skills/vds-skill/vds-scripts/excel_orchestrator/finalize_brd_coverage.py +327 -0
  451. package/skills/vds-skill/vds-scripts/excel_orchestrator/fix_all_formulas.py +99 -0
  452. package/skills/vds-skill/vds-scripts/excel_orchestrator/fix_detail_presentation.py +113 -0
  453. package/skills/vds-skill/vds-scripts/excel_orchestrator/fix_presentation_and_effort.py +116 -0
  454. package/skills/vds-skill/vds-scripts/excel_orchestrator/fix_presentation_consistency.py +231 -0
  455. package/skills/vds-skill/vds-scripts/excel_orchestrator/fix_remarks_matching.py +179 -0
  456. package/skills/vds-skill/vds-scripts/excel_orchestrator/group_tasks_by_service_id.py +210 -0
  457. package/skills/vds-skill/vds-scripts/excel_orchestrator/increase_brd_coverage.py +497 -0
  458. package/skills/vds-skill/vds-scripts/excel_orchestrator/increase_effort_complexity.py +155 -0
  459. package/skills/vds-skill/vds-scripts/excel_orchestrator/organize_and_deduplicate.py +273 -0
  460. package/skills/vds-skill/vds-scripts/excel_orchestrator/pyproject.toml +64 -0
  461. package/skills/vds-skill/vds-scripts/excel_orchestrator/rebuild_all_formulas.py +146 -0
  462. package/skills/vds-skill/vds-scripts/excel_orchestrator/remove_base_multiplier_and_check_duplicates.py +310 -0
  463. package/skills/vds-skill/vds-scripts/excel_orchestrator/remove_duplicate_brd_tasks.py +137 -0
  464. package/skills/vds-skill/vds-scripts/excel_orchestrator/research_based_updates.py +457 -0
  465. package/skills/vds-skill/vds-scripts/excel_orchestrator/restore_e_values.py +172 -0
  466. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/__init__.py +5 -0
  467. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/cli.py +746 -0
  468. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/config.py +74 -0
  469. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/converters.py +226 -0
  470. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/errors.py +88 -0
  471. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/excel_client.py +443 -0
  472. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/formatters.py +211 -0
  473. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/logging.py +57 -0
  474. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/source_contract.py +29 -0
  475. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/target_state_status.py +837 -0
  476. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/ulnc_alignment.py +1291 -0
  477. package/skills/vds-skill/vds-scripts/excel_orchestrator/src/vds_excel_orchestrator/validators.py +164 -0
  478. package/skills/vds-skill/vds-scripts/excel_orchestrator/sync_detail_and_total_sheets.py +211 -0
  479. package/skills/vds-skill/vds-scripts/excel_orchestrator/tests/__init__.py +1 -0
  480. package/skills/vds-skill/vds-scripts/excel_orchestrator/tests/conftest.py +36 -0
  481. package/skills/vds-skill/vds-scripts/excel_orchestrator/tests/test_cli.py +383 -0
  482. package/skills/vds-skill/vds-scripts/excel_orchestrator/tests/test_excel_client.py +129 -0
  483. package/skills/vds-skill/vds-scripts/excel_orchestrator/tests/test_ulnc_alignment.py +373 -0
  484. package/skills/vds-skill/vds-scripts/excel_orchestrator/tests/test_validators.py +64 -0
  485. package/skills/vds-skill/vds-scripts/excel_orchestrator/update_api_database_effort.py +261 -0
  486. package/skills/vds-skill/vds-scripts/excel_orchestrator/update_buffers_inline.py +115 -0
  487. package/skills/vds-skill/vds-scripts/excel_orchestrator/update_complex_services_and_add_new.py +336 -0
  488. package/skills/vds-skill/vds-scripts/excel_orchestrator/update_responsibility_and_fix_rows.py +208 -0
  489. package/skills/vds-skill/vds-scripts/excel_orchestrator/update_task_breakdown_vietnamese.py +309 -0
  490. package/skills/vds-skill/vds-scripts/excel_orchestrator/update_vietnamese_and_responsibility.py +415 -0
  491. package/skills/vds-skill/vds-scripts/excel_orchestrator/verify_brd_coverage_comprehensive.py +401 -0
  492. package/skills/vds-skill/vds-scripts/git_orchestrator/ENHANCEMENT_SUMMARY.md +119 -0
  493. package/skills/vds-skill/vds-scripts/git_orchestrator/README.md +286 -0
  494. package/skills/vds-skill/vds-scripts/git_orchestrator/VERIFICATION_REPORT.md +152 -0
  495. package/skills/vds-skill/vds-scripts/git_orchestrator/pyproject.toml +37 -0
  496. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/__init__.py +30 -0
  497. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/__main__.py +4 -0
  498. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/branch_probe.py +271 -0
  499. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/cli.py +892 -0
  500. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/logging_config.py +63 -0
  501. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/manifest.py +249 -0
  502. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/orchestrator.py +1647 -0
  503. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/protocols.py +35 -0
  504. package/skills/vds-skill/vds-scripts/git_orchestrator/src/vds_git_orchestrator/reporting.py +55 -0
  505. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/__init__.py +0 -0
  506. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/test_cli_settings.py +19 -0
  507. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/test_integration.py +79 -0
  508. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/test_manifest.py +79 -0
  509. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/test_orchestrator.py +207 -0
  510. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/test_public_api.py +235 -0
  511. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/test_resilience.py +343 -0
  512. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/unit/__init__.py +0 -0
  513. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/unit/test_branch_probe.py +327 -0
  514. package/skills/vds-skill/vds-scripts/git_orchestrator/tests/unit/test_protocols.py +132 -0
  515. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/README.md +241 -0
  516. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/pyproject.toml +45 -0
  517. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/__init__.py +69 -0
  518. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/cli.py +568 -0
  519. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/client.py +186 -0
  520. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/config.py +46 -0
  521. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/src/vds_google_sheets_orchestrator/errors.py +41 -0
  522. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/tests/__init__.py +1 -0
  523. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/tests/conftest.py +1 -0
  524. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/tests/unit/__init__.py +1 -0
  525. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/tests/unit/test_cli.py +212 -0
  526. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/tests/unit/test_client.py +24 -0
  527. package/skills/vds-skill/vds-scripts/google_sheets_orchestrator/tests/unit/test_config.py +16 -0
  528. package/skills/vds-skill/vds-scripts/grafana_orchestrator/README.md +572 -0
  529. package/skills/vds-skill/vds-scripts/grafana_orchestrator/pyproject.toml +102 -0
  530. package/skills/vds-skill/vds-scripts/grafana_orchestrator/src/vds_grafana_orchestrator/__init__.py +78 -0
  531. package/skills/vds-skill/vds-scripts/grafana_orchestrator/src/vds_grafana_orchestrator/cli.py +455 -0
  532. package/skills/vds-skill/vds-scripts/grafana_orchestrator/src/vds_grafana_orchestrator/client.py +700 -0
  533. package/skills/vds-skill/vds-scripts/grafana_orchestrator/src/vds_grafana_orchestrator/config.py +243 -0
  534. package/skills/vds-skill/vds-scripts/grafana_orchestrator/src/vds_grafana_orchestrator/errors.py +34 -0
  535. package/skills/vds-skill/vds-scripts/grafana_orchestrator/src/vds_grafana_orchestrator/py.typed +1 -0
  536. package/skills/vds-skill/vds-scripts/grafana_orchestrator/tests/__init__.py +1 -0
  537. package/skills/vds-skill/vds-scripts/grafana_orchestrator/tests/conftest.py +308 -0
  538. package/skills/vds-skill/vds-scripts/grafana_orchestrator/tests/test_client.py +458 -0
  539. package/skills/vds-skill/vds-scripts/grafana_orchestrator/tests/test_config.py +203 -0
  540. package/skills/vds-skill/vds-scripts/grafana_orchestrator/tests/test_errors.py +78 -0
  541. package/skills/vds-skill/vds-scripts/jira_orchestrator/README.md +864 -0
  542. package/skills/vds-skill/vds-scripts/jira_orchestrator/pyproject.toml +43 -0
  543. package/skills/vds-skill/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/__init__.py +65 -0
  544. package/skills/vds-skill/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/adapter.py +1685 -0
  545. package/skills/vds-skill/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/cli.py +2806 -0
  546. package/skills/vds-skill/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/config.py +168 -0
  547. package/skills/vds-skill/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/errors.py +34 -0
  548. package/skills/vds-skill/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/reporting.py +66 -0
  549. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/__init__.py +1 -0
  550. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/conftest.py +86 -0
  551. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_agile_list_payloads.py +54 -0
  552. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_bulk_operations.py +91 -0
  553. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_components.py +56 -0
  554. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_createmeta.py +45 -0
  555. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_dashboard.py +119 -0
  556. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_issue_properties.py +53 -0
  557. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_permissions_compat.py +41 -0
  558. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_reindex.py +42 -0
  559. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_remote_links.py +75 -0
  560. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_transitions.py +90 -0
  561. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_user_management.py +116 -0
  562. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_version_management.py +181 -0
  563. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_adapter_watchers.py +43 -0
  564. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_advanced_search.py +179 -0
  565. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_agile.py +304 -0
  566. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_application_properties.py +243 -0
  567. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_backlog.py +91 -0
  568. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_bulk_operations.py +403 -0
  569. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_cli.py +108 -0
  570. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_components.py +119 -0
  571. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_config.py +166 -0
  572. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_dashboard.py +122 -0
  573. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_discover_fields.py +207 -0
  574. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_errors.py +72 -0
  575. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_filter_management.py +411 -0
  576. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_issue_archiving.py +179 -0
  577. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_issue_links.py +257 -0
  578. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_issue_properties.py +189 -0
  579. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_link_types.py +407 -0
  580. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_parse_set.py +37 -0
  581. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_permissions.py +343 -0
  582. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_reindex.py +81 -0
  583. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_remote_links.py +269 -0
  584. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_security_schemes.py +202 -0
  585. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_transitions_changelog.py +109 -0
  586. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_user_management.py +246 -0
  587. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_version_management.py +503 -0
  588. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_watchers.py +116 -0
  589. package/skills/vds-skill/vds-scripts/jira_orchestrator/tests/test_worklog.py +243 -0
  590. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/README.md +864 -0
  591. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/pyproject.toml +43 -0
  592. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/__init__.py +65 -0
  593. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/adapter.py +1689 -0
  594. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/cli.py +2799 -0
  595. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/config.py +137 -0
  596. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/errors.py +34 -0
  597. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/src/vds_jira_viettelmoney_orchestrator/reporting.py +65 -0
  598. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/__init__.py +1 -0
  599. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/conftest.py +86 -0
  600. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_agile_list_payloads.py +54 -0
  601. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_bulk_operations.py +101 -0
  602. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_components.py +64 -0
  603. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_createmeta.py +45 -0
  604. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_dashboard.py +135 -0
  605. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_issue_properties.py +63 -0
  606. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_permissions_compat.py +42 -0
  607. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_reindex.py +42 -0
  608. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_remote_links.py +89 -0
  609. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_transitions.py +91 -0
  610. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_user_management.py +130 -0
  611. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_version_management.py +189 -0
  612. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_adapter_watchers.py +49 -0
  613. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_advanced_search.py +213 -0
  614. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_agile.py +334 -0
  615. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_application_properties.py +261 -0
  616. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_backlog.py +91 -0
  617. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_bulk_operations.py +443 -0
  618. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_cli.py +106 -0
  619. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_components.py +133 -0
  620. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_config.py +166 -0
  621. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_dashboard.py +130 -0
  622. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_discover_fields.py +207 -0
  623. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_errors.py +61 -0
  624. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_filter_management.py +478 -0
  625. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_issue_archiving.py +181 -0
  626. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_issue_links.py +257 -0
  627. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_issue_properties.py +203 -0
  628. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_link_types.py +426 -0
  629. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_parse_set.py +37 -0
  630. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_permissions.py +358 -0
  631. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_reindex.py +81 -0
  632. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_remote_links.py +292 -0
  633. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_security_schemes.py +218 -0
  634. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_transitions_changelog.py +121 -0
  635. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_user_management.py +283 -0
  636. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_version_management.py +561 -0
  637. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_watchers.py +128 -0
  638. package/skills/vds-skill/vds-scripts/jira_viettelmoney_orchestrator/tests/test_worklog.py +265 -0
  639. package/skills/vds-skill/vds-scripts/llms.txt +159 -0
  640. package/skills/vds-skill/vds-scripts/markdown_orchestrator/README.md +72 -0
  641. package/skills/vds-skill/vds-scripts/markdown_orchestrator/pyproject.toml +39 -0
  642. package/skills/vds-skill/vds-scripts/markdown_orchestrator/src/vds_markdown_orchestrator/__init__.py +5 -0
  643. package/skills/vds-skill/vds-scripts/markdown_orchestrator/src/vds_markdown_orchestrator/cli.py +102 -0
  644. package/skills/vds-skill/vds-scripts/mcp_server/Dockerfile +63 -0
  645. package/skills/vds-skill/vds-scripts/mcp_server/README.md +140 -0
  646. package/skills/vds-skill/vds-scripts/mcp_server/pyproject.toml +41 -0
  647. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/__init__.py +3 -0
  648. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/config.py +36 -0
  649. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/server.py +111 -0
  650. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/tools/__init__.py +15 -0
  651. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/tools/bitbucket_tools.py +47 -0
  652. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/tools/confluence_tools.py +53 -0
  653. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/tools/git_tools.py +71 -0
  654. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/tools/jira_tools.py +63 -0
  655. package/skills/vds-skill/vds-scripts/mcp_server/src/vds_mcp_server/tools/vidp_tools.py +64 -0
  656. package/skills/vds-skill/vds-scripts/mcp_server/tests/__init__.py +1 -0
  657. package/skills/vds-skill/vds-scripts/mcp_server/tests/conftest.py +31 -0
  658. package/skills/vds-skill/vds-scripts/mcp_server/tests/unit/__init__.py +1 -0
  659. package/skills/vds-skill/vds-scripts/mcp_server/tests/unit/test_bitbucket_tools.py +28 -0
  660. package/skills/vds-skill/vds-scripts/mcp_server/tests/unit/test_confluence_tools.py +28 -0
  661. package/skills/vds-skill/vds-scripts/mcp_server/tests/unit/test_git_tools.py +35 -0
  662. package/skills/vds-skill/vds-scripts/mcp_server/tests/unit/test_jira_tools.py +35 -0
  663. package/skills/vds-skill/vds-scripts/mcp_server/tests/verification/__init__.py +6 -0
  664. package/skills/vds-skill/vds-scripts/mcp_server/tests/verification/conftest.py +51 -0
  665. package/skills/vds-skill/vds-scripts/mcp_server/tests/verification/test_mcp_confluence_tools.py +40 -0
  666. package/skills/vds-skill/vds-scripts/mcp_server/tests/verification/test_mcp_jira_tools.py +39 -0
  667. package/skills/vds-skill/vds-scripts/mcp_server/tests/verification/test_mcp_tool_registration.py +50 -0
  668. package/skills/vds-skill/vds-scripts/pdf_orchestrator/.dockerignore +93 -0
  669. package/skills/vds-skill/vds-scripts/pdf_orchestrator/.env.example +40 -0
  670. package/skills/vds-skill/vds-scripts/pdf_orchestrator/.ruff_rules.py +350 -0
  671. package/skills/vds-skill/vds-scripts/pdf_orchestrator/.yamllint.yml +43 -0
  672. package/skills/vds-skill/vds-scripts/pdf_orchestrator/DEVELOPMENT_PLAN.md +80 -0
  673. package/skills/vds-skill/vds-scripts/pdf_orchestrator/Dockerfile +87 -0
  674. package/skills/vds-skill/vds-scripts/pdf_orchestrator/README.md +608 -0
  675. package/skills/vds-skill/vds-scripts/pdf_orchestrator/cli_verification_test/test.md +6 -0
  676. package/skills/vds-skill/vds-scripts/pdf_orchestrator/cli_verification_test/test.pdf +0 -0
  677. package/skills/vds-skill/vds-scripts/pdf_orchestrator/config/alertmanager.yml +83 -0
  678. package/skills/vds-skill/vds-scripts/pdf_orchestrator/config/prometheus.prod.yml +98 -0
  679. package/skills/vds-skill/vds-scripts/pdf_orchestrator/config/prometheus.yml +40 -0
  680. package/skills/vds-skill/vds-scripts/pdf_orchestrator/config/redis.conf +78 -0
  681. package/skills/vds-skill/vds-scripts/pdf_orchestrator/docs/COMPETITIVE_ANALYSIS_REPORT.md +309 -0
  682. package/skills/vds-skill/vds-scripts/pdf_orchestrator/docs/FEATURES_GUIDE.md +518 -0
  683. package/skills/vds-skill/vds-scripts/pdf_orchestrator/docs/MULTI_USER_DEPLOYMENT_GUIDE.md +615 -0
  684. package/skills/vds-skill/vds-scripts/pdf_orchestrator/docs/USER_GUIDE.md +829 -0
  685. package/skills/vds-skill/vds-scripts/pdf_orchestrator/pyproject.toml +87 -0
  686. package/skills/vds-skill/vds-scripts/pdf_orchestrator/pytest.ini +71 -0
  687. package/skills/vds-skill/vds-scripts/pdf_orchestrator/ruff.toml +6 -0
  688. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/debug_security_report.py +59 -0
  689. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/demo_library_selector.py +109 -0
  690. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/generate_project_stats.py +52 -0
  691. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/generate_styled_pdf.py +95 -0
  692. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/migrate_render_pdfs.py +285 -0
  693. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/setup_team.bat +283 -0
  694. package/skills/vds-skill/vds-scripts/pdf_orchestrator/scripts/setup_team.sh +324 -0
  695. package/skills/vds-skill/vds-scripts/pdf_orchestrator/src/vds_pdf_orchestrator/__init__.py +5 -0
  696. package/skills/vds-skill/vds-scripts/pdf_orchestrator/src/vds_pdf_orchestrator/cli.py +542 -0
  697. package/skills/vds-skill/vds-scripts/pdf_orchestrator/src/vds_pdf_orchestrator/config.py +33 -0
  698. package/skills/vds-skill/vds-scripts/pdf_orchestrator/tests/README.md +650 -0
  699. package/skills/vds-skill/vds-scripts/pdf_orchestrator/tests/__init__.py +0 -0
  700. package/skills/vds-skill/vds-scripts/pdf_orchestrator/tests/conftest.py +520 -0
  701. package/skills/vds-skill/vds-scripts/pdf_orchestrator/tests/requirements.txt +51 -0
  702. package/skills/vds-skill/vds-scripts/pdf_orchestrator/tests/run_tests.py +659 -0
  703. package/skills/vds-skill/vds-scripts/pdf_orchestrator/tests/test_config.py +36 -0
  704. package/skills/vds-skill/vds-scripts/platform_core/pyproject.toml +49 -0
  705. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/__init__.py +16 -0
  706. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/alembic/__init__.py +18 -0
  707. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/alembic/runtime.py +139 -0
  708. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/config.py +88 -0
  709. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/credentials.py +40 -0
  710. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/env.py +24 -0
  711. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/errors.py +127 -0
  712. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/http/__init__.py +18 -0
  713. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/http/auth.py +32 -0
  714. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/http/errors.py +47 -0
  715. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/http/pagination.py +65 -0
  716. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/http/retry.py +62 -0
  717. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/http/stack.py +61 -0
  718. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/logging.py +132 -0
  719. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/protocols.py +77 -0
  720. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/serialization.py +80 -0
  721. package/skills/vds-skill/vds-scripts/platform_core/src/vds_platform_core/severity.py +175 -0
  722. package/skills/vds-skill/vds-scripts/platform_core/tests/__init__.py +0 -0
  723. package/skills/vds-skill/vds-scripts/platform_core/tests/conftest.py +1 -0
  724. package/skills/vds-skill/vds-scripts/platform_core/tests/test_alembic_runtime.py +300 -0
  725. package/skills/vds-skill/vds-scripts/platform_core/tests/test_auth.py +84 -0
  726. package/skills/vds-skill/vds-scripts/platform_core/tests/test_config.py +83 -0
  727. package/skills/vds-skill/vds-scripts/platform_core/tests/test_credentials.py +73 -0
  728. package/skills/vds-skill/vds-scripts/platform_core/tests/test_env.py +56 -0
  729. package/skills/vds-skill/vds-scripts/platform_core/tests/test_errors.py +201 -0
  730. package/skills/vds-skill/vds-scripts/platform_core/tests/test_errors_http.py +74 -0
  731. package/skills/vds-skill/vds-scripts/platform_core/tests/test_http_settings.py +116 -0
  732. package/skills/vds-skill/vds-scripts/platform_core/tests/test_logging.py +148 -0
  733. package/skills/vds-skill/vds-scripts/platform_core/tests/test_pagination.py +153 -0
  734. package/skills/vds-skill/vds-scripts/platform_core/tests/test_protocols.py +132 -0
  735. package/skills/vds-skill/vds-scripts/platform_core/tests/test_retry.py +151 -0
  736. package/skills/vds-skill/vds-scripts/platform_core/tests/test_serialization.py +92 -0
  737. package/skills/vds-skill/vds-scripts/platform_core/tests/test_severity.py +178 -0
  738. package/skills/vds-skill/vds-scripts/platform_core/tests/test_stack.py +130 -0
  739. package/skills/vds-skill/vds-scripts/platform_core/uv.lock +341 -0
  740. package/skills/vds-skill/vds-scripts/pyproject.toml +145 -0
  741. package/skills/vds-skill/vds-scripts/pyrightconfig.json +82 -0
  742. package/skills/vds-skill/vds-scripts/repo-manifest.yaml +380 -0
  743. package/skills/vds-skill/vds-scripts/repo-manifest.yaml.example +25 -0
  744. package/skills/vds-skill/vds-scripts/ruff.toml +100 -0
  745. package/skills/vds-skill/vds-scripts/scripts/BRD-Validation-API.postman_collection.json +706 -0
  746. package/skills/vds-skill/vds-scripts/scripts/BRD-Validation-README.md +308 -0
  747. package/skills/vds-skill/vds-scripts/scripts/README.md +271 -0
  748. package/skills/vds-skill/vds-scripts/scripts/_validate_alias_phase2.py +137 -0
  749. package/skills/vds-skill/vds-scripts/scripts/audit-cli-patterns.sh +135 -0
  750. package/skills/vds-skill/vds-scripts/scripts/audit-dashboard.sh +525 -0
  751. package/skills/vds-skill/vds-scripts/scripts/backup.sh +123 -0
  752. package/skills/vds-skill/vds-scripts/scripts/bootstrap_uv.sh +69 -0
  753. package/skills/vds-skill/vds-scripts/scripts/brd-validation-environment.json +51 -0
  754. package/skills/vds-skill/vds-scripts/scripts/brd-validation-test-results.json +13023 -0
  755. package/skills/vds-skill/vds-scripts/scripts/brd_coverage_report.json +276 -0
  756. package/skills/vds-skill/vds-scripts/scripts/check-future-annotations.py +22 -0
  757. package/skills/vds-skill/vds-scripts/scripts/check-invalid-symlinks.py +183 -0
  758. package/skills/vds-skill/vds-scripts/scripts/check-no-debug-markers.py +21 -0
  759. package/skills/vds-skill/vds-scripts/scripts/check-no-unittest.py +21 -0
  760. package/skills/vds-skill/vds-scripts/scripts/ci/assert_no_openspace_commits.sh +37 -0
  761. package/skills/vds-skill/vds-scripts/scripts/ci/verify_branch_protection.sh +64 -0
  762. package/skills/vds-skill/vds-scripts/scripts/closure/phase1_check.sh +483 -0
  763. package/skills/vds-skill/vds-scripts/scripts/closure/phase2_check.sh +500 -0
  764. package/skills/vds-skill/vds-scripts/scripts/create_memory_session.py +36 -0
  765. package/skills/vds-skill/vds-scripts/scripts/deploy-bootstrap.sh +201 -0
  766. package/skills/vds-skill/vds-scripts/scripts/deployment/load_docker_images_offline.sh +90 -0
  767. package/skills/vds-skill/vds-scripts/scripts/dev/cli_smoke.sh +259 -0
  768. package/skills/vds-skill/vds-scripts/scripts/final_completion_report.md +139 -0
  769. package/skills/vds-skill/vds-scripts/scripts/folder_structure_report.json +321 -0
  770. package/skills/vds-skill/vds-scripts/scripts/generate_completion_report.py +132 -0
  771. package/skills/vds-skill/vds-scripts/scripts/generate_intellij_modules.py +154 -0
  772. package/skills/vds-skill/vds-scripts/scripts/init-pgbouncer-userlist.sh +154 -0
  773. package/skills/vds-skill/vds-scripts/scripts/link_integrity_report.json +807 -0
  774. package/skills/vds-skill/vds-scripts/scripts/move_audit_artifact_pages.py +252 -0
  775. package/skills/vds-skill/vds-scripts/scripts/move_audit_artifact_pages_rest.py +165 -0
  776. package/skills/vds-skill/vds-scripts/scripts/move_wrong_dept_pages.py +235 -0
  777. package/skills/vds-skill/vds-scripts/scripts/openspace_bootstrap.sh +56 -0
  778. package/skills/vds-skill/vds-scripts/scripts/openspace_common.sh +75 -0
  779. package/skills/vds-skill/vds-scripts/scripts/openspace_doctor.sh +61 -0
  780. package/skills/vds-skill/vds-scripts/scripts/openspace_sync_shadow.sh +65 -0
  781. package/skills/vds-skill/vds-scripts/scripts/phase7-baseline.sh +77 -0
  782. package/skills/vds-skill/vds-scripts/scripts/preflight/env_check.sh +102 -0
  783. package/skills/vds-skill/vds-scripts/scripts/repair_autopay_reports.sh +173 -0
  784. package/skills/vds-skill/vds-scripts/scripts/rollback_drill.sh +659 -0
  785. package/skills/vds-skill/vds-scripts/scripts/run-audit-in-tmux.sh +286 -0
  786. package/skills/vds-skill/vds-scripts/scripts/run-department-audit.sh +495 -0
  787. package/skills/vds-skill/vds-scripts/scripts/run-project-audit.sh +267 -0
  788. package/skills/vds-skill/vds-scripts/scripts/save_intellij_memories.py +112 -0
  789. package/skills/vds-skill/vds-scripts/scripts/save_memories_to_vds_ai.py +81 -0
  790. package/skills/vds-skill/vds-scripts/scripts/save_memories_vds_style.py +133 -0
  791. package/skills/vds-skill/vds-scripts/scripts/search_intellij_memories.py +48 -0
  792. package/skills/vds-skill/vds-scripts/scripts/setup_intellij_workspace.py +71 -0
  793. package/skills/vds-skill/vds-scripts/scripts/smoke-test-deploy.sh +137 -0
  794. package/skills/vds-skill/vds-scripts/scripts/smoke_deploy_lib.py +205 -0
  795. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/README.md +89 -0
  796. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/confluence_sync_coordinator.sh +27 -0
  797. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/coordination.sh +114 -0
  798. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/diagram_coordinator.sh +25 -0
  799. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/docs_root.sh +22 -0
  800. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/generate_diagrams.sh +22 -0
  801. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/markdown_coordinator.sh +25 -0
  802. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/progress_dashboard.sh +17 -0
  803. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/schema_coordinator.sh +25 -0
  804. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/sync_confluence.sh +30 -0
  805. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/update_dependencies.sh +19 -0
  806. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/validate_links.sh +86 -0
  807. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/validate_markdown.sh +52 -0
  808. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/validate_schemas.sh +26 -0
  809. package/skills/vds-skill/vds-scripts/scripts/target-state-automation/validate_structure.sh +98 -0
  810. package/skills/vds-skill/vds-scripts/scripts/tests/__init__.py +1 -0
  811. package/skills/vds-skill/vds-scripts/scripts/tests/test_dockerfile_correctness.py +815 -0
  812. package/skills/vds-skill/vds-scripts/scripts/tests/test_makefile_loadouts.py +560 -0
  813. package/skills/vds-skill/vds-scripts/scripts/tests/test_smoke_deploy.py +313 -0
  814. package/skills/vds-skill/vds-scripts/scripts/tests/test_verify_alembic.py +581 -0
  815. package/skills/vds-skill/vds-scripts/scripts/tests/test_verify_infra_topology.py +254 -0
  816. package/skills/vds-skill/vds-scripts/scripts/update_modules_xml.py +194 -0
  817. package/skills/vds-skill/vds-scripts/scripts/uv-workspace-alignment-verification-2026-03-25.md +128 -0
  818. package/skills/vds-skill/vds-scripts/scripts/uv-workspace-alignment-verification-2026-04-18.md +100 -0
  819. package/skills/vds-skill/vds-scripts/scripts/validate-cli-standardization.sh +188 -0
  820. package/skills/vds-skill/vds-scripts/scripts/validate_brd_coverage.py +197 -0
  821. package/skills/vds-skill/vds-scripts/scripts/validate_folder_structure.py +234 -0
  822. package/skills/vds-skill/vds-scripts/scripts/validate_link_integrity.py +274 -0
  823. package/skills/vds-skill/vds-scripts/scripts/vami017-caller-compat-report.md +62 -0
  824. package/skills/vds-skill/vds-scripts/scripts/vami017-phase-b-scaffold-notes.md +79 -0
  825. package/skills/vds-skill/vds-scripts/scripts/vds_sh_helpers.sh +180 -0
  826. package/skills/vds-skill/vds-scripts/scripts/verification/phase2_portable_paths_ubuntu_docker.sh +26 -0
  827. package/skills/vds-skill/vds-scripts/scripts/verify-infra-topology.py +868 -0
  828. package/skills/vds-skill/vds-scripts/scripts/verify-memory-cli-e2e.sh +598 -0
  829. package/skills/vds-skill/vds-scripts/scripts/verify-worktree-features.sh +306 -0
  830. package/skills/vds-skill/vds-scripts/scripts/worktree-add.sh +128 -0
  831. package/skills/vds-skill/vds-scripts/scripts/worktree-remove.sh +112 -0
  832. package/skills/vds-skill/vds-scripts/scripts/worktree_compose.sh +269 -0
  833. package/skills/vds-skill/vds-scripts/scripts/worktree_uv.sh +77 -0
  834. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/IMPLEMENTATION_AUDIT.md +376 -0
  835. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/README.md +507 -0
  836. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/pyproject.toml +106 -0
  837. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/scripts/ensure_symlink.sh +38 -0
  838. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/__init__.py +164 -0
  839. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/batch.py +212 -0
  840. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/cli.py +1407 -0
  841. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/client.py +608 -0
  842. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/config.py +260 -0
  843. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/diff.py +220 -0
  844. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/errors.py +34 -0
  845. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/external_sca.py +932 -0
  846. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/portfolio.py +225 -0
  847. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/pr.py +505 -0
  848. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/reports.py +342 -0
  849. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/scanner.py +351 -0
  850. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/src/vds_sonarqube_orchestrator/webhooks.py +269 -0
  851. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/__init__.py +0 -0
  852. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/conftest.py +134 -0
  853. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_batch.py +419 -0
  854. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_config.py +145 -0
  855. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_errors.py +78 -0
  856. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_external_sca.py +466 -0
  857. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_pr.py +471 -0
  858. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_reports.py +511 -0
  859. package/skills/vds-skill/vds-scripts/sonarqube_orchestrator/tests/test_webhooks.py +660 -0
  860. package/skills/vds-skill/vds-scripts/uv.lock +5046 -0
  861. package/skills/vds-skill/vds-scripts/vds_agent_core/CHANGELOG.md +36 -0
  862. package/skills/vds-skill/vds-scripts/vds_agent_core/README.md +453 -0
  863. package/skills/vds-skill/vds-scripts/vds_agent_core/docs/PHASE9A_ASSESSMENT.md +50 -0
  864. package/skills/vds-skill/vds-scripts/vds_agent_core/docs/embedding.md +468 -0
  865. package/skills/vds-skill/vds-scripts/vds_agent_core/pyproject.toml +51 -0
  866. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/__init__.py +29 -0
  867. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/agents/__init__.py +26 -0
  868. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/agents/hooks.py +119 -0
  869. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/agents/loop.py +864 -0
  870. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/agents/tools.py +41 -0
  871. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/config.py +252 -0
  872. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/__init__.py +55 -0
  873. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/_cascade.py +143 -0
  874. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/budget.py +353 -0
  875. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/cache.py +373 -0
  876. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/embedding.py +815 -0
  877. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/provider.py +173 -0
  878. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/llm/schemas.py +45 -0
  879. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/observability/__init__.py +77 -0
  880. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/observability/decorators.py +258 -0
  881. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/observability/jsonl_exporter.py +236 -0
  882. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/observability/tracer.py +497 -0
  883. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/profiles.py +2015 -0
  884. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/runtime/__init__.py +0 -0
  885. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/runtime/agent_id.py +60 -0
  886. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/security/__init__.py +13 -0
  887. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/security/credentials.py +106 -0
  888. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/skills/__init__.py +1 -0
  889. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/skills/executor.py +238 -0
  890. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/skills/manager.py +381 -0
  891. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/skills/policy.py +568 -0
  892. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/workflows/__init__.py +19 -0
  893. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/workflows/langgraph_runner.py +102 -0
  894. package/skills/vds-skill/vds-scripts/vds_agent_core/src/vds_agent_core/workflows/protocols.py +81 -0
  895. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/__init__.py +0 -0
  896. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/conftest.py +62 -0
  897. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/integration/__init__.py +0 -0
  898. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/integration/test_audit_loop_hooks_integration.py +135 -0
  899. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/integration/test_audit_observability_integration.py +246 -0
  900. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/integration/test_public_api_stability.py +91 -0
  901. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/__init__.py +0 -0
  902. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/__init__.py +0 -0
  903. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_call_site_parallelism.py +30 -0
  904. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_dimension_guardrail.py +25 -0
  905. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_drop_in_provider_extensibility.py +76 -0
  906. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_embedding.py +393 -0
  907. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_embedding_cache.py +302 -0
  908. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_embedding_extra.py +696 -0
  909. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_embedding_subclass.py +49 -0
  910. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_no_provider_leakage_in_env.py +34 -0
  911. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_provider_auto_route.py +48 -0
  912. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_runtime_log_clean.py +111 -0
  913. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/llm/test_w7_logic_fixes.py +219 -0
  914. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/profiles/__init__.py +0 -0
  915. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/profiles/test_embedding_block_parser.py +194 -0
  916. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/profiles/test_env_resolver_allowlist.py +141 -0
  917. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/profiles/test_profile_authorization.py +158 -0
  918. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/profiles/test_profiles_w3_extra.py +547 -0
  919. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/profiles/test_real_audit_profile_compat.py +129 -0
  920. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/runtime/__init__.py +0 -0
  921. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/runtime/test_for_agent.py +322 -0
  922. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/runtime/test_w9_cascade_edges.py +369 -0
  923. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/security/__init__.py +0 -0
  924. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/security/test_credentials.py +132 -0
  925. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_agent_loop.py +663 -0
  926. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_agent_loop_coverage.py +429 -0
  927. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_agents_hooks_defaults.py +22 -0
  928. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_budget.py +155 -0
  929. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_budget_coverage.py +264 -0
  930. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_budget_tracking_only.py +71 -0
  931. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_cache.py +251 -0
  932. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_cache_context.py +62 -0
  933. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_config.py +155 -0
  934. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_langgraph_runner.py +45 -0
  935. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_langgraph_runner_coverage.py +98 -0
  936. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_llm_cache_deep.py +113 -0
  937. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_observability_decorators.py +697 -0
  938. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_observability_hooks.py +217 -0
  939. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_observability_jsonl_exporter.py +542 -0
  940. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_observability_jsonl_wiring.py +313 -0
  941. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_observability_tracer.py +896 -0
  942. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_profiles.py +1571 -0
  943. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_profiles_coverage.py +444 -0
  944. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_provider.py +316 -0
  945. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_schemas.py +63 -0
  946. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_skill_executor.py +297 -0
  947. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_skill_manager.py +370 -0
  948. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_skill_manager_coverage.py +364 -0
  949. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_skill_policy.py +402 -0
  950. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_skill_rubric.py +47 -0
  951. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_tools.py +51 -0
  952. package/skills/vds-skill/vds-scripts/vds_agent_core/tests/unit/test_workflow_protocols.py +136 -0
  953. package/skills/vds-skill/vds-scripts/vds_cli/README.md +201 -0
  954. package/skills/vds-skill/vds-scripts/vds_cli/VERIFICATION_REPORT.md +41 -0
  955. package/skills/vds-skill/vds-scripts/vds_cli/pyproject.toml +50 -0
  956. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/__init__.py +3 -0
  957. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/assets/git-credential-helper.py +235 -0
  958. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/cli.py +1126 -0
  959. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/commands/__init__.py +1 -0
  960. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/commands/lint_cli.py +389 -0
  961. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/confluence_sync.py +846 -0
  962. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs/consumption/__init__.py +7 -0
  963. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs/consumption/funnel.py +105 -0
  964. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs/consumption/scanner.py +211 -0
  965. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs/freshness/report.py +90 -0
  966. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs/types.py +27 -0
  967. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs_cmd.py +672 -0
  968. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs_metrics.py +75 -0
  969. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/docs_sync.py +1171 -0
  970. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/ecosystem/__init__.py +39 -0
  971. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/ecosystem/report.py +439 -0
  972. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/ecosystem_docs.py +164 -0
  973. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/env.py +111 -0
  974. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/env_git_helper.py +281 -0
  975. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/google_sheets_orchestrator/__init__.py +3 -0
  976. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/google_sheets_orchestrator/google_sheets_orchestrator.py +173 -0
  977. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/router.py +232 -0
  978. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/skills_cmd.py +274 -0
  979. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/sync_api.py +613 -0
  980. package/skills/vds-skill/vds-scripts/vds_cli/src/vds_cli/sync_service.py +283 -0
  981. package/skills/vds-skill/vds-scripts/vds_cli/tests/conftest.py +62 -0
  982. package/skills/vds-skill/vds-scripts/vds_cli/tests/test_env_git_helper_lifecycle.py +261 -0
  983. package/skills/vds-skill/vds-scripts/vds_cli/tests/test_git_credential_helper.py +240 -0
  984. package/skills/vds-skill/vds-scripts/vds_cli/tests/test_router_help_proxy.py +43 -0
  985. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_cli.py +241 -0
  986. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_cli_DOC004.py +110 -0
  987. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_confluence_sync.py +315 -0
  988. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_confluence_sync_wave7.py +375 -0
  989. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_consumption_funnel.py +106 -0
  990. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_consumption_scanner.py +144 -0
  991. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_docs_cmd.py +89 -0
  992. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_docs_cmd_wave8.py +161 -0
  993. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_docs_metrics.py +16 -0
  994. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_docs_quality_score.py +61 -0
  995. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_docs_sync.py +417 -0
  996. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_ecosystem_cli_dashboard.py +667 -0
  997. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_ecosystem_cli_dashboard_rendering.py +143 -0
  998. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_ecosystem_docs.py +63 -0
  999. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_env.py +85 -0
  1000. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_freshness_report.py +125 -0
  1001. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_lint_cli.py +224 -0
  1002. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_router.py +101 -0
  1003. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_skills_cmd.py +419 -0
  1004. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_sync_api.py +357 -0
  1005. package/skills/vds-skill/vds-scripts/vds_cli/tests/unit/test_sync_service.py +170 -0
  1006. package/skills/vds-skill/vds-scripts/vds_cli/tests/verification/conftest.py +51 -0
  1007. package/skills/vds-skill/vds-scripts/vds_cli/tests/verification/test_bitbucket_real.py +32 -0
  1008. package/skills/vds-skill/vds-scripts/vds_cli/tests/verification/test_confluence_real.py +32 -0
  1009. package/skills/vds-skill/vds-scripts/vds_cli/tests/verification/test_jira_real.py +40 -0
  1010. package/skills/vds-skill/vds-scripts/vds_cli_common/README.md +190 -0
  1011. package/skills/vds-skill/vds-scripts/vds_cli_common/pyproject.toml +96 -0
  1012. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/__init__.py +36 -0
  1013. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/app.py +55 -0
  1014. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/completers.py +139 -0
  1015. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/context.py +201 -0
  1016. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/env.py +163 -0
  1017. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/errors.py +440 -0
  1018. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/output.py +284 -0
  1019. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/paths.py +78 -0
  1020. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/testing.py +211 -0
  1021. package/skills/vds-skill/vds-scripts/vds_cli_common/src/vds_cli_common/version.py +85 -0
  1022. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/__init__.py +0 -0
  1023. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_app.py +126 -0
  1024. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_completers.py +148 -0
  1025. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_context.py +192 -0
  1026. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_env.py +235 -0
  1027. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_errors.py +275 -0
  1028. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_output.py +229 -0
  1029. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_paths.py +61 -0
  1030. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_testing.py +138 -0
  1031. package/skills/vds-skill/vds-scripts/vds_cli_common/tests/test_version.py +64 -0
  1032. package/skills/vds-skill/vds-scripts/vidp_orchestrator/README.md +31 -0
  1033. package/skills/vds-skill/vds-scripts/vidp_orchestrator/pyproject.toml +50 -0
  1034. package/skills/vds-skill/vds-scripts/vidp_orchestrator/src/vds_vidp_orchestrator/__init__.py +26 -0
  1035. package/skills/vds-skill/vds-scripts/vidp_orchestrator/src/vds_vidp_orchestrator/cli.py +246 -0
  1036. package/skills/vds-skill/vds-scripts/vidp_orchestrator/src/vds_vidp_orchestrator/client.py +104 -0
  1037. package/skills/vds-skill/vds-scripts/vidp_orchestrator/src/vds_vidp_orchestrator/config.py +82 -0
  1038. package/skills/vds-skill/vds-scripts/vidp_orchestrator/src/vds_vidp_orchestrator/workflows.json +3 -0
  1039. package/skills/vds-skill/vds-scripts/vidp_orchestrator/src/vds_vidp_orchestrator/workflows.py +130 -0
@@ -0,0 +1,2806 @@
1
+ """Typer CLI for Jira using the SDK-only adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import re
8
+ import sys
9
+ from collections.abc import Sequence
10
+ from datetime import UTC, datetime
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ import structlog
15
+ import typer
16
+ from vds_platform_core.logging import configure_structlog
17
+
18
+ from .adapter import JiraAdapter
19
+ from .config import JiraSettings, SettingsError, load_settings
20
+ from .errors import AuthError, VDSClientError
21
+ from .reporting import RunSummary, write_reports
22
+
23
+ app = typer.Typer(add_completion=False, help="Jira operations orchestrator (SDK-only)")
24
+ logger = logging.getLogger("jira_cli")
25
+
26
+ APP_NAME = "vds-jira-orchestrator"
27
+ APP_VERSION = "2025.11.0"
28
+
29
+ _REPORT_DIR = (Path(__file__).resolve().parents[3] / "reports" / "jira_runs").resolve()
30
+
31
+
32
+ def _emit_json(payload: Any) -> None:
33
+ text = json.dumps(payload, indent=2, sort_keys=True)
34
+ if sys.stdout.isatty():
35
+ typer.echo(text)
36
+ else:
37
+ sys.stdout.write(text + "\n")
38
+
39
+
40
+ def _parse_set(items: Sequence[str], *, allow_scalar_json: bool = False) -> dict[str, Any]:
41
+ out: dict[str, Any] = {}
42
+ for raw in items:
43
+ if "=" not in raw:
44
+ raise typer.BadParameter(f"Invalid --set item '{raw}', expected key=value")
45
+ k, v = raw.split("=", 1)
46
+ k = k.strip()
47
+ v = v.strip()
48
+ if not k:
49
+ raise typer.BadParameter(f"Invalid key in --set '{raw}'")
50
+
51
+ # Try parsing value as JSON for complex field structures (e.g., customfields)
52
+ try:
53
+ val = json.loads(v)
54
+ if isinstance(val, (dict, list)) or allow_scalar_json:
55
+ out[k] = val
56
+ else:
57
+ out[k] = v
58
+ except json.JSONDecodeError:
59
+ # Fallback to string if not valid JSON
60
+ out[k] = v
61
+
62
+ return out
63
+
64
+
65
+ # ---------------------------------------------------------------------------
66
+ # Field metadata helpers
67
+ # ---------------------------------------------------------------------------
68
+
69
+
70
+ def _normalize_allowed_values(field_info: dict[str, Any]) -> list[dict[str, str]]:
71
+ allowed_values: list[dict[str, str]] = []
72
+ for raw in field_info.get("allowedValues", []) or []:
73
+ if not isinstance(raw, dict):
74
+ continue
75
+ value = raw.get("value") or raw.get("name") or raw.get("displayName")
76
+ if value is None:
77
+ continue
78
+ allowed_values.append({"id": str(raw.get("id") or ""), "value": str(value)})
79
+ return allowed_values
80
+
81
+
82
+ def _merge_allowed_values_from_editmeta(raw_fields: dict[str, Any], editmeta_fields: dict[str, Any]) -> None:
83
+ for field_id, field_info in raw_fields.items():
84
+ if not isinstance(field_info, dict):
85
+ continue
86
+ if field_info.get("allowedValues"):
87
+ continue
88
+ edit_info = editmeta_fields.get(field_id)
89
+ if not isinstance(edit_info, dict):
90
+ continue
91
+ if not edit_info.get("allowedValues"):
92
+ continue
93
+ merged = dict(field_info)
94
+ merged["allowedValues"] = edit_info["allowedValues"]
95
+ raw_fields[field_id] = merged
96
+
97
+
98
+ def _build_field_data(field_id: str, field_info: dict[str, Any]) -> dict[str, Any]:
99
+ field_name = str(field_info.get("name") or "")
100
+ field_data = {
101
+ "id": field_id,
102
+ "name": field_name,
103
+ "type": field_info.get("schema", {}).get("type", "unknown"),
104
+ "required": bool(field_info.get("required", False)),
105
+ "is_custom": field_id.startswith("customfield_"),
106
+ }
107
+ allowed_values = _normalize_allowed_values(field_info)
108
+ if allowed_values:
109
+ field_data["allowedValues"] = allowed_values
110
+ return field_data
111
+
112
+
113
+ def _field_matches_filter(field_id: str, field_name: str, field_filter: str) -> bool:
114
+ token = field_filter.strip().lower()
115
+ if not token:
116
+ return True
117
+ return token in field_name.lower() or token in field_id.lower()
118
+
119
+
120
+ def _collect_fields(
121
+ raw_fields: dict[str, Any],
122
+ *,
123
+ field_filter: str | None,
124
+ include_system: bool,
125
+ ) -> list[dict[str, Any]]:
126
+ fields: list[dict[str, Any]] = []
127
+ for field_id, field_info in raw_fields.items():
128
+ field_name = str(field_info.get("name") or "")
129
+ if field_filter and not _field_matches_filter(field_id, field_name, field_filter):
130
+ continue
131
+ is_custom = field_id.startswith("customfield_")
132
+ if not include_system and not is_custom:
133
+ continue
134
+ fields.append(_build_field_data(field_id, field_info))
135
+ return fields
136
+
137
+
138
+ def _extract_createmeta_fields(meta: dict[str, Any], project: str, issuetype: str) -> dict[str, Any]:
139
+ found_project = None
140
+ for p in meta.get("projects", []) or []:
141
+ project_key = str(p.get("key", ""))
142
+ project_id = str(p.get("id", ""))
143
+ if project_key.lower() == project.lower() or project_id == project:
144
+ found_project = p
145
+ break
146
+ if not found_project:
147
+ raise typer.BadParameter(f"Project '{project}' not found in metadata response")
148
+
149
+ found_issuetype = None
150
+ for it in found_project.get("issuetypes", []) or []:
151
+ it_name = str(it.get("name", ""))
152
+ it_id = str(it.get("id", ""))
153
+ if it_name.lower() == issuetype.lower() or it_id == issuetype:
154
+ found_issuetype = it
155
+ break
156
+ if not found_issuetype:
157
+ available_types = [it.get("name") for it in found_project.get("issuetypes", [])]
158
+ raise typer.BadParameter(
159
+ f"Issue type '{issuetype}' not found in project '{project}'. Available types: {available_types}"
160
+ )
161
+
162
+ return found_issuetype.get("fields", {})
163
+
164
+
165
+ def _extract_required_field_ref(message: str) -> str | None:
166
+ match = re.search(r"(?:Field ['\"])?(.+?)(?:['\"])? is required", message, re.IGNORECASE)
167
+ if not match:
168
+ return None
169
+ return match.group(1).strip()
170
+
171
+
172
+ def _extract_error_field_refs(message: str) -> list[str]:
173
+ refs: set[str] = set()
174
+ required_ref = _extract_required_field_ref(message)
175
+ if required_ref:
176
+ refs.add(required_ref)
177
+
178
+ for ref in re.findall(r"customfield_\d+", message, re.IGNORECASE):
179
+ refs.add(ref)
180
+
181
+ errors_match = re.search(r"errors\s*[:=]\s*{([^}]+)}", message, re.IGNORECASE)
182
+ if errors_match:
183
+ for key in re.findall(r"[\"']([A-Za-z][A-Za-z0-9_]+)[\"']\s*:", errors_match.group(1)):
184
+ if key not in {"errorMessages", "errors"}:
185
+ refs.add(key)
186
+
187
+ return [ref for ref in sorted(refs) if ref]
188
+
189
+
190
+ def _match_fields_by_refs(fields: list[dict[str, Any]], refs: Sequence[str]) -> list[dict[str, Any]]:
191
+ matched: list[dict[str, Any]] = []
192
+ seen: set[str] = set()
193
+ for ref in refs:
194
+ token = ref.strip().lower()
195
+ if not token:
196
+ continue
197
+ for field in fields:
198
+ field_id = str(field.get("id") or "")
199
+ field_name = str(field.get("name") or "")
200
+ if token in field_id.lower() or token in field_name.lower():
201
+ if field_id and field_id in seen:
202
+ continue
203
+ matched.append(field)
204
+ if field_id:
205
+ seen.add(field_id)
206
+ return matched
207
+
208
+
209
+ def _format_allowed_values(allowed_values: list[dict[str, str]], limit: int = 20) -> str:
210
+ if not allowed_values:
211
+ return ""
212
+ parts: list[str] = []
213
+ for item in allowed_values[:limit]:
214
+ value = item.get("value") or ""
215
+ ident = item.get("id") or ""
216
+ if ident:
217
+ parts.append(f"{value} (id: {ident})")
218
+ else:
219
+ parts.append(value)
220
+ if len(allowed_values) > limit:
221
+ parts.append(f"... (+{len(allowed_values) - limit} more)")
222
+ return ", ".join([p for p in parts if p])
223
+
224
+
225
+ # ---------------------------------------------------------------------------
226
+ # Remote link helpers
227
+ # ---------------------------------------------------------------------------
228
+
229
+
230
+ def _build_remote_link_payload(
231
+ *,
232
+ url: str | None,
233
+ title: str | None,
234
+ summary: str | None,
235
+ relationship: str | None,
236
+ global_id: str | None,
237
+ application_type: str | None,
238
+ application_name: str | None,
239
+ icon_url: str | None,
240
+ icon_title: str | None,
241
+ data_file: Path | None,
242
+ ) -> dict[str, Any]:
243
+ if data_file:
244
+ if not data_file.exists():
245
+ raise typer.BadParameter(f"Data file not found: {data_file}")
246
+ try:
247
+ with data_file.open() as f:
248
+ parsed = json.load(f)
249
+ except json.JSONDecodeError as exc:
250
+ raise typer.BadParameter(f"Invalid JSON in {data_file}: {exc}") from exc
251
+ if not isinstance(parsed, dict):
252
+ raise typer.BadParameter("Remote link data file must contain a JSON object")
253
+ return parsed
254
+
255
+ if not url:
256
+ raise typer.BadParameter("--url required when --data-file is not provided")
257
+
258
+ payload: dict[str, Any] = {"object": {"url": url}}
259
+
260
+ if title:
261
+ payload["object"]["title"] = title
262
+ if summary:
263
+ payload["object"]["summary"] = summary
264
+ if icon_url:
265
+ icon: dict[str, Any] = {"url16x16": icon_url}
266
+ if icon_title:
267
+ icon["title"] = icon_title
268
+ payload["object"]["icon"] = icon
269
+ if relationship:
270
+ payload["relationship"] = relationship
271
+ if global_id:
272
+ payload["globalId"] = global_id
273
+ if application_type or application_name:
274
+ if not (application_type and application_name):
275
+ raise typer.BadParameter("--application-type and --application-name must be provided together")
276
+ payload["application"] = {"type": application_type, "name": application_name}
277
+
278
+ return payload
279
+
280
+
281
+ # Global state for report directory and markdown flag
282
+ _report_dir_global: Path = _REPORT_DIR
283
+ _markdown_global: bool = True
284
+ _reports_global: bool = True
285
+
286
+
287
+ @app.callback(invoke_without_command=True)
288
+ def main(
289
+ report_dir: Path | None = typer.Option(None, help=f"Directory for run reports (default: {_REPORT_DIR})"),
290
+ markdown: bool = typer.Option(True, help="Emit Markdown summaries alongside JSON reports"),
291
+ reports: bool = typer.Option(True, "--reports/--no-reports", help="Persist run reports to disk"),
292
+ json_only: bool = typer.Option(
293
+ False,
294
+ "--json-only",
295
+ help="Emit JSON to stdout only (disables reports and markdown)",
296
+ ),
297
+ structured_logs: bool = typer.Option(
298
+ False, "--structured-logs/--no-structured-logs", help="Emit structured JSON logs to stderr"
299
+ ),
300
+ version: bool = typer.Option(
301
+ False,
302
+ "--version",
303
+ help="Show vds-jira-orchestrator version and exit",
304
+ is_eager=True,
305
+ ),
306
+ ) -> None:
307
+ if version:
308
+ typer.echo(f"{APP_NAME} {APP_VERSION}")
309
+ raise typer.Exit() from None
310
+ global _report_dir_global, _markdown_global, _reports_global
311
+ if json_only:
312
+ markdown = False
313
+ reports = False
314
+ _report_dir_global = report_dir.resolve() if report_dir else _REPORT_DIR
315
+ _markdown_global = markdown
316
+ _reports_global = reports
317
+ if structured_logs or json_only:
318
+ configure_structlog(
319
+ json_logs=structured_logs, quiet=json_only and not structured_logs, cache_logger=structured_logs
320
+ )
321
+ if structured_logs:
322
+ global logger
323
+ logger = structlog.get_logger("jira_cli") # type: ignore[assignment]
324
+
325
+
326
+ def _record_summary(
327
+ *, command: str, args: list[str], exit_code: int, started_at: datetime, finished_at: datetime
328
+ ) -> None:
329
+ global _report_dir_global, _markdown_global
330
+ summary = RunSummary(
331
+ command=command,
332
+ args=args,
333
+ exit_code=exit_code,
334
+ duration_ms=int((finished_at - started_at).total_seconds() * 1000),
335
+ started_at=started_at,
336
+ finished_at=finished_at,
337
+ )
338
+ if _reports_global:
339
+ write_reports(summary, base_dir=_report_dir_global, include_markdown=_markdown_global)
340
+
341
+
342
+ @app.command("projects")
343
+ def cmd_projects(
344
+ search: str | None = typer.Option(
345
+ None,
346
+ "--search",
347
+ "-s",
348
+ help="Case-insensitive substring filter on project name or key",
349
+ ),
350
+ limit: int | None = typer.Option(
351
+ None,
352
+ "--limit",
353
+ "-l",
354
+ help="Maximum number of projects to return (applied after filtering)",
355
+ ),
356
+ ) -> None:
357
+ """List JIRA projects accessible to the current user."""
358
+ started_at = datetime.now(UTC)
359
+ code = 0
360
+ args_summary = []
361
+ if search:
362
+ args_summary.append(f"search={search}")
363
+ if limit is not None:
364
+ args_summary.append(f"limit={limit}")
365
+ try:
366
+ s = load_settings(strict=True)
367
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
368
+ payload = adapter.list_projects()
369
+
370
+ if search:
371
+ needle = search.lower()
372
+ payload = [p for p in payload if needle in p.get("name", "").lower() or needle in p.get("key", "").lower()]
373
+ if limit is not None:
374
+ payload = payload[:limit]
375
+
376
+ _emit_json({"count": len(payload), "projects": payload})
377
+ except (AuthError, VDSClientError, SettingsError) as exc:
378
+ code = 1
379
+ logger.exception("projects_failed %s", exc)
380
+ typer.echo(f"Error: {exc}", err=True)
381
+ finally:
382
+ finished_at = datetime.now(UTC)
383
+ _record_summary(
384
+ command="projects",
385
+ args=args_summary,
386
+ exit_code=code,
387
+ started_at=started_at,
388
+ finished_at=finished_at,
389
+ )
390
+ if code != 0:
391
+ raise typer.Exit(code=code) from None
392
+
393
+
394
+ @app.command("component")
395
+ def cmd_component(
396
+ action: str = typer.Argument(..., help="Action: list, get, create, update, delete"),
397
+ project_key: str | None = typer.Option(None, "--project-key", "-P", help="Project key (for list)"),
398
+ component_id: str | None = typer.Option(None, "--component-id", "-c", help="Component ID (for get/update/delete)"),
399
+ name: str | None = typer.Option(None, "--name", help="Component name (for create/update)"),
400
+ description: str | None = typer.Option(None, "--description", help="Component description"),
401
+ lead_account_id: str | None = typer.Option(None, "--lead-account-id", help="Account ID of component lead"),
402
+ assignee_type: str | None = typer.Option(
403
+ None, "--assignee-type", help="Assignee type (e.g., PROJECT_LEAD, COMPONENT_LEAD)"
404
+ ),
405
+ is_assignee_type_valid: bool | None = typer.Option(
406
+ None, "--assignee-valid/--no-assignee-valid", help="Assignee validity flag"
407
+ ),
408
+ data_file: Path | None = typer.Option(None, "--data-file", help="JSON file for component payload"),
409
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operations (create/update/delete)"),
410
+ ) -> None:
411
+ """Manage project components."""
412
+ started_at = datetime.now(UTC)
413
+ code = 0
414
+ summary_args = [
415
+ action,
416
+ f"project_key={project_key}",
417
+ f"component_id={component_id}",
418
+ f"data_file={data_file}",
419
+ ]
420
+
421
+ def _build_payload() -> dict[str, Any]:
422
+ if data_file:
423
+ if not data_file.exists():
424
+ raise typer.BadParameter(f"Data file not found: {data_file}")
425
+ try:
426
+ parsed = json.loads(data_file.read_text())
427
+ except json.JSONDecodeError as exc:
428
+ raise typer.BadParameter(f"Invalid JSON in {data_file}: {exc}") from exc
429
+ if not isinstance(parsed, dict):
430
+ raise typer.BadParameter("--data-file must contain a JSON object")
431
+ return parsed
432
+ payload: dict[str, Any] = {}
433
+ if name:
434
+ payload["name"] = name
435
+ if description is not None:
436
+ payload["description"] = description
437
+ if lead_account_id:
438
+ payload["leadAccountId"] = lead_account_id
439
+ if assignee_type:
440
+ payload["assigneeType"] = assignee_type
441
+ if is_assignee_type_valid is not None:
442
+ payload["isAssigneeTypeValid"] = is_assignee_type_valid
443
+ if not payload:
444
+ raise typer.BadParameter("Provide --data-file or at least one field option for create/update")
445
+ return payload
446
+
447
+ try:
448
+ s = JiraSettings()
449
+ s.require_basic_or_token()
450
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
451
+
452
+ if action == "list":
453
+ if not project_key:
454
+ raise typer.BadParameter("--project-key required for list")
455
+ project = adapter.get_project(project_key, expand="components")
456
+ components = project.get("components", []) if isinstance(project, dict) else []
457
+ _emit_json({"count": len(components), "components": components})
458
+
459
+ elif action == "get":
460
+ if not component_id:
461
+ raise typer.BadParameter("--component-id required for get")
462
+ payload = adapter.get_component(component_id)
463
+ _emit_json(payload)
464
+
465
+ elif action == "create":
466
+ if not yes:
467
+ raise typer.BadParameter("--yes required for create")
468
+ payload = _build_payload()
469
+ if (
470
+ not project_key
471
+ and "project" not in payload
472
+ and "projectId" not in payload
473
+ and "projectKey" not in payload
474
+ ):
475
+ raise typer.BadParameter(
476
+ "--project-key required (or include project/projectId in --data-file) for create"
477
+ )
478
+ if project_key and "project" not in payload and "projectId" not in payload:
479
+ payload["project"] = project_key
480
+ result = adapter.create_component(payload)
481
+ _emit_json(result)
482
+
483
+ elif action == "update":
484
+ if not yes:
485
+ raise typer.BadParameter("--yes required for update")
486
+ if not component_id:
487
+ raise typer.BadParameter("--component-id required for update")
488
+ payload = _build_payload()
489
+ result = adapter.update_component(payload, component_id)
490
+ _emit_json(result)
491
+
492
+ elif action == "delete":
493
+ if not yes:
494
+ raise typer.BadParameter("--yes required for delete")
495
+ if not component_id:
496
+ raise typer.BadParameter("--component-id required for delete")
497
+ adapter.delete_component(component_id)
498
+ _emit_json({"status": "deleted", "component_id": component_id})
499
+
500
+ else:
501
+ raise typer.BadParameter(f"Unknown action: {action}")
502
+
503
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
504
+ code = 1
505
+ logger.exception("component_command_failed %s", exc)
506
+ typer.echo(f"Error: {exc}", err=True)
507
+ finally:
508
+ finished_at = datetime.now(UTC)
509
+ _record_summary(
510
+ command="component",
511
+ args=summary_args,
512
+ exit_code=code,
513
+ started_at=started_at,
514
+ finished_at=finished_at,
515
+ )
516
+ if code != 0:
517
+ raise typer.Exit(code=code) from None
518
+
519
+
520
+ @app.command("version")
521
+ def cmd_version(
522
+ action: str = typer.Argument(..., help="Action: list, create, update"),
523
+ project_key: str | None = typer.Option(None, "--project-key", "-P", help="Project key (for list/create)"),
524
+ project_id: str | None = typer.Option(None, "--project-id", help="Project ID (for create)"),
525
+ version_id: str | None = typer.Option(None, "--version-id", help="Version ID (for update)"),
526
+ name: str | None = typer.Option(None, "--name", help="Version name (for create/update)"),
527
+ description: str | None = typer.Option(None, "--description", help="Version description (for update)"),
528
+ start: int | None = typer.Option(None, "--start", help="Start index for pagination (for list)"),
529
+ limit: int | None = typer.Option(None, "--limit", help="Limit for pagination (for list)"),
530
+ order_by: str | None = typer.Option(
531
+ None, "--order-by", help="Order by field: sequence, name, startDate, releaseDate (for list)"
532
+ ),
533
+ query: str | None = typer.Option(None, "--query", help="Query filter (for list)"),
534
+ status: str | None = typer.Option(None, "--status", help="Status filter (for list)"),
535
+ archived: bool | None = typer.Option(None, "--archived/--no-archived", help="Archived flag (for create/update)"),
536
+ released: bool | None = typer.Option(None, "--released/--no-released", help="Released flag (for create/update)"),
537
+ start_date: str | None = typer.Option(None, "--start-date", help="Start date (YYYY-MM-DD) (for update)"),
538
+ release_date: str | None = typer.Option(None, "--release-date", help="Release date (YYYY-MM-DD) (for update)"),
539
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operations (create/update)"),
540
+ ) -> None:
541
+ """Manage project versions."""
542
+ started_at = datetime.now(UTC)
543
+ code = 0
544
+ summary_args = [
545
+ action,
546
+ f"project_key={project_key}",
547
+ f"project_id={project_id}",
548
+ f"version_id={version_id}",
549
+ f"name={name}",
550
+ ]
551
+
552
+ try:
553
+ s = JiraSettings()
554
+ s.require_basic_or_token()
555
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
556
+
557
+ if action == "list":
558
+ if not project_key:
559
+ raise typer.BadParameter("--project-key required for list")
560
+ payload = adapter.get_project_versions_paginated(
561
+ project_key=project_key,
562
+ start=start,
563
+ limit=limit,
564
+ order_by=order_by,
565
+ query=query,
566
+ status=status,
567
+ )
568
+ _emit_json(payload)
569
+
570
+ elif action == "create":
571
+ if not yes:
572
+ raise typer.BadParameter("Refusing to create version without --yes")
573
+ if not project_key:
574
+ raise typer.BadParameter("--project-key required for create")
575
+ if not project_id:
576
+ raise typer.BadParameter("--project-id required for create")
577
+ if not name:
578
+ raise typer.BadParameter("--name required for create")
579
+ result = adapter.add_version(
580
+ key=project_key,
581
+ project_id=project_id,
582
+ version=name,
583
+ is_archived=archived if archived is not None else False,
584
+ is_released=released if released is not None else False,
585
+ )
586
+ _emit_json(result)
587
+
588
+ elif action == "update":
589
+ if not yes:
590
+ raise typer.BadParameter("Refusing to update version without --yes")
591
+ if not version_id:
592
+ raise typer.BadParameter("--version-id required for update")
593
+ # At least one field must be provided for update
594
+ if not any(
595
+ [name, description is not None, archived is not None, released is not None, start_date, release_date]
596
+ ):
597
+ raise typer.BadParameter(
598
+ "At least one update field required (--name, --description, --archived, --released, --start-date, --release-date)"
599
+ )
600
+ result = adapter.update_version(
601
+ version=version_id,
602
+ name=name,
603
+ description=description,
604
+ is_archived=archived,
605
+ is_released=released,
606
+ start_date=start_date,
607
+ release_date=release_date,
608
+ )
609
+ _emit_json(result)
610
+
611
+ else:
612
+ raise typer.BadParameter(f"Unknown action: {action}")
613
+
614
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
615
+ code = 1
616
+ logger.exception("version_command_failed %s", exc)
617
+ typer.echo(f"Error: {exc}", err=True)
618
+ finally:
619
+ finished_at = datetime.now(UTC)
620
+ _record_summary(
621
+ command="version",
622
+ args=summary_args,
623
+ exit_code=code,
624
+ started_at=started_at,
625
+ finished_at=finished_at,
626
+ )
627
+ if code != 0:
628
+ raise typer.Exit(code=code) from None
629
+
630
+
631
+ @app.command("search")
632
+ def cmd_search(
633
+ jql: str = typer.Argument(..., help="JQL query string"),
634
+ limit: int = typer.Option(25, "--limit", help="Number of results"),
635
+ fields: str | None = typer.Option(None, "--fields", help="Comma separated fields"),
636
+ start: int = typer.Option(0, "--start", help="Start index for pagination (advanced search)"),
637
+ advanced: bool = typer.Option(False, "--advanced", help="Use advanced search with pagination"),
638
+ json_only: bool = typer.Option(False, "--json-only", help="Emit JSON payload for this command"),
639
+ ) -> None:
640
+ """Search issues using JQL. Use --advanced for pagination support."""
641
+ started_at = datetime.now(UTC)
642
+ code = 0
643
+ try:
644
+ s = JiraSettings()
645
+ s.require_basic_or_token()
646
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
647
+
648
+ fields_list = [f.strip() for f in fields.split(",")] if fields else None
649
+ if advanced or start > 0:
650
+ payload = adapter.search_issues_advanced(jql, start=start, limit=limit, fields=fields_list)
651
+ else:
652
+ payload = adapter.jql(jql, limit=limit, fields=fields_list)
653
+ _emit_json(payload)
654
+ except (AuthError, VDSClientError) as exc:
655
+ code = 1
656
+ logger.exception("search_failed %s", exc)
657
+ typer.echo(f"Error: {exc}", err=True)
658
+ finally:
659
+ finished_at = datetime.now(UTC)
660
+ args_list = [jql, f"limit={limit}"]
661
+ if start > 0:
662
+ args_list.append(f"start={start}")
663
+ if advanced:
664
+ args_list.append("advanced=True")
665
+ if json_only:
666
+ args_list.append("json_only=True")
667
+ _record_summary(
668
+ command="search",
669
+ args=args_list,
670
+ exit_code=code,
671
+ started_at=started_at,
672
+ finished_at=finished_at,
673
+ )
674
+ if code != 0:
675
+ raise typer.Exit(code=code) from None
676
+
677
+
678
+ @app.command("search-by-filter")
679
+ def cmd_search_by_filter(
680
+ filter_id: int = typer.Argument(..., help="Filter ID"),
681
+ limit: int = typer.Option(50, "--limit", help="Number of results"),
682
+ fields: str | None = typer.Option(None, "--fields", help="Comma separated fields"),
683
+ start: int = typer.Option(0, "--start", help="Start index for pagination"),
684
+ ) -> None:
685
+ """Search issues using a saved filter."""
686
+ started_at = datetime.now(UTC)
687
+ code = 0
688
+ try:
689
+ s = JiraSettings()
690
+ s.require_basic_or_token()
691
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
692
+
693
+ fields_list = [f.strip() for f in fields.split(",")] if fields else None
694
+ payload = adapter.search_issues_by_filter(filter_id, start=start, limit=limit, fields=fields_list)
695
+ _emit_json(payload)
696
+ except (AuthError, VDSClientError) as exc:
697
+ code = 1
698
+ logger.exception("search_by_filter_failed %s", exc)
699
+ typer.echo(f"Error: {exc}", err=True)
700
+ finally:
701
+ finished_at = datetime.now(UTC)
702
+ args_list = [str(filter_id), f"limit={limit}"]
703
+ if start > 0:
704
+ args_list.append(f"start={start}")
705
+ _record_summary(
706
+ command="search-by-filter",
707
+ args=args_list,
708
+ exit_code=code,
709
+ started_at=started_at,
710
+ finished_at=finished_at,
711
+ )
712
+ if code != 0:
713
+ raise typer.Exit(code=code) from None
714
+
715
+
716
+ @app.command("issue")
717
+ def cmd_issue(
718
+ key: str = typer.Argument(..., help="Issue key (e.g., NTTC-123)"),
719
+ ) -> None:
720
+ """Show details for a single JIRA issue by key."""
721
+ started_at = datetime.now(UTC)
722
+ code = 0
723
+ try:
724
+ s = JiraSettings()
725
+ s.require_basic_or_token()
726
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
727
+ payload = adapter.get_issue(key)
728
+ _emit_json(payload)
729
+ except (AuthError, VDSClientError) as exc:
730
+ code = 1
731
+ logger.exception("issue_failed %s", exc)
732
+ typer.echo(f"Error: {exc}", err=True)
733
+ finally:
734
+ finished_at = datetime.now(UTC)
735
+ _record_summary(command="issue", args=[key], exit_code=code, started_at=started_at, finished_at=finished_at)
736
+ if code != 0:
737
+ raise typer.Exit(code=code) from None
738
+
739
+
740
+ @app.command("bulk-update")
741
+ def cmd_bulk_update(
742
+ issue_keys: str = typer.Option(..., "--issue-keys", help="Comma-separated issue keys (e.g., NTTC-1,NTTC-2)"),
743
+ fields: str | None = typer.Option(None, "--fields", help="JSON string or '*all' for all fields"),
744
+ fields_file: Path | None = typer.Option(None, "--fields-file", help="JSON file containing field updates"),
745
+ yes: bool = typer.Option(False, "--yes", help="Confirm bulk update operation"),
746
+ ) -> None:
747
+ """Bulk update multiple issues."""
748
+ started_at = datetime.now(UTC)
749
+ code = 0
750
+ summary_args = [f"issue_keys={issue_keys}", f"fields_file={fields_file}"]
751
+
752
+ def _load_fields() -> str:
753
+ if fields_file:
754
+ if not fields_file.exists():
755
+ raise typer.BadParameter(f"Fields file not found: {fields_file}")
756
+ try:
757
+ parsed = json.loads(fields_file.read_text())
758
+ return json.dumps(parsed)
759
+ except json.JSONDecodeError as exc:
760
+ raise typer.BadParameter(f"Invalid JSON in {fields_file}: {exc}") from exc
761
+ if fields:
762
+ # Validate JSON if provided
763
+ if fields != "*all":
764
+ try:
765
+ json.loads(fields)
766
+ except json.JSONDecodeError as exc:
767
+ raise typer.BadParameter(f"Invalid JSON in --fields: {exc}") from exc
768
+ return fields
769
+ return "*all"
770
+
771
+ try:
772
+ if not yes:
773
+ raise typer.BadParameter("Refusing to bulk update issues without --yes")
774
+ s = JiraSettings()
775
+ s.require_basic_or_token()
776
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
777
+
778
+ key_list = [k.strip() for k in issue_keys.split(",") if k.strip()]
779
+ if not key_list:
780
+ raise typer.BadParameter("At least one issue key required in --issue-keys")
781
+ fields_value = _load_fields()
782
+ result = adapter.bulk_update_issue_field(key_list=key_list, fields=fields_value)
783
+ _emit_json(result)
784
+
785
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
786
+ code = 1
787
+ logger.exception("bulk_update_failed %s", exc)
788
+ typer.echo(f"Error: {exc}", err=True)
789
+ finally:
790
+ finished_at = datetime.now(UTC)
791
+ _record_summary(
792
+ command="bulk-update",
793
+ args=summary_args,
794
+ exit_code=code,
795
+ started_at=started_at,
796
+ finished_at=finished_at,
797
+ )
798
+ if code != 0:
799
+ raise typer.Exit(code=code) from None
800
+
801
+
802
+ @app.command("field-append")
803
+ def cmd_field_append(
804
+ issue_key: str = typer.Option(..., "--issue-key", "-k", help="Issue key (e.g., NTTC-123)"),
805
+ field: str = typer.Option(..., "--field", help="Field ID (e.g., customfield_10000)"),
806
+ value: str | None = typer.Option(None, "--value", help="JSON string value to append"),
807
+ value_file: Path | None = typer.Option(None, "--value-file", help="JSON file containing value to append"),
808
+ no_notify: bool = typer.Option(False, "--no-notify", help="Don't notify users"),
809
+ yes: bool = typer.Option(False, "--yes", help="Confirm field append operation"),
810
+ ) -> None:
811
+ """Append value to issue field (for multi-select fields)."""
812
+ started_at = datetime.now(UTC)
813
+ code = 0
814
+ summary_args = [f"issue_key={issue_key}", f"field={field}", f"value_file={value_file}"]
815
+
816
+ def _load_value() -> dict[str, Any]:
817
+ if value_file:
818
+ if not value_file.exists():
819
+ raise typer.BadParameter(f"Value file not found: {value_file}")
820
+ try:
821
+ parsed = json.loads(value_file.read_text())
822
+ if not isinstance(parsed, dict):
823
+ raise typer.BadParameter("--value-file must contain a JSON object")
824
+ return parsed
825
+ except json.JSONDecodeError as exc:
826
+ raise typer.BadParameter(f"Invalid JSON in {value_file}: {exc}") from exc
827
+ if not value:
828
+ raise typer.BadParameter("Provide --value or --value-file")
829
+ try:
830
+ parsed = json.loads(value)
831
+ if not isinstance(parsed, dict):
832
+ raise typer.BadParameter("--value must be a JSON object")
833
+ return parsed
834
+ except json.JSONDecodeError as exc:
835
+ raise typer.BadParameter(f"Invalid JSON in --value: {exc}") from exc
836
+
837
+ try:
838
+ if not yes:
839
+ raise typer.BadParameter("Refusing to append field value without --yes")
840
+ s = JiraSettings()
841
+ s.require_basic_or_token()
842
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
843
+
844
+ value_dict = _load_value()
845
+ result = adapter.issue_field_value_append(
846
+ issue_id_or_key=issue_key, field=field, value=value_dict, notify_users=not no_notify
847
+ )
848
+ _emit_json(result)
849
+
850
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
851
+ code = 1
852
+ logger.exception("field_append_failed %s", exc)
853
+ typer.echo(f"Error: {exc}", err=True)
854
+ finally:
855
+ finished_at = datetime.now(UTC)
856
+ _record_summary(
857
+ command="field-append",
858
+ args=summary_args,
859
+ exit_code=code,
860
+ started_at=started_at,
861
+ finished_at=finished_at,
862
+ )
863
+ if code != 0:
864
+ raise typer.Exit(code=code) from None
865
+
866
+
867
+ @app.command("issue-archive")
868
+ def cmd_issue_archive(
869
+ issue_key: str = typer.Option(..., "--issue-key", "-k", help="Issue key (e.g., NTTC-123)"),
870
+ yes: bool = typer.Option(False, "--yes", help="Confirm archive operation"),
871
+ ) -> None:
872
+ """Archive an issue."""
873
+ started_at = datetime.now(UTC)
874
+ code = 0
875
+ summary_args = [f"issue_key={issue_key}"]
876
+
877
+ try:
878
+ if not yes:
879
+ raise typer.BadParameter("Refusing to archive issue without --yes")
880
+ s = JiraSettings()
881
+ s.require_basic_or_token()
882
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
883
+
884
+ adapter.issue_archive(issue_key)
885
+ _emit_json({"status": "archived", "issue_key": issue_key})
886
+
887
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
888
+ code = 1
889
+ logger.exception("issue_archive_failed %s", exc)
890
+ typer.echo(f"Error: {exc}", err=True)
891
+ finally:
892
+ finished_at = datetime.now(UTC)
893
+ _record_summary(
894
+ command="issue-archive",
895
+ args=summary_args,
896
+ exit_code=code,
897
+ started_at=started_at,
898
+ finished_at=finished_at,
899
+ )
900
+ if code != 0:
901
+ raise typer.Exit(code=code) from None
902
+
903
+
904
+ @app.command("issue-restore")
905
+ def cmd_issue_restore(
906
+ issue_key: str = typer.Option(..., "--issue-key", "-k", help="Issue key (e.g., NTTC-123)"),
907
+ yes: bool = typer.Option(False, "--yes", help="Confirm restore operation"),
908
+ ) -> None:
909
+ """Restore an archived issue."""
910
+ started_at = datetime.now(UTC)
911
+ code = 0
912
+ summary_args = [f"issue_key={issue_key}"]
913
+
914
+ try:
915
+ if not yes:
916
+ raise typer.BadParameter("Refusing to restore issue without --yes")
917
+ s = JiraSettings()
918
+ s.require_basic_or_token()
919
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
920
+
921
+ adapter.issue_restore(issue_key)
922
+ _emit_json({"status": "restored", "issue_key": issue_key})
923
+
924
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
925
+ code = 1
926
+ logger.exception("issue_restore_failed %s", exc)
927
+ typer.echo(f"Error: {exc}", err=True)
928
+ finally:
929
+ finished_at = datetime.now(UTC)
930
+ _record_summary(
931
+ command="issue-restore",
932
+ args=summary_args,
933
+ exit_code=code,
934
+ started_at=started_at,
935
+ finished_at=finished_at,
936
+ )
937
+ if code != 0:
938
+ raise typer.Exit(code=code) from None
939
+
940
+
941
+ @app.command("property")
942
+ def cmd_property(
943
+ action: str = typer.Argument(..., help="Action: get, set"),
944
+ property_id: str | None = typer.Option(None, "--property-id", help="Property ID (for set)"),
945
+ key: str | None = typer.Option(None, "--key", help="Property key (for get)"),
946
+ permission_level: str | None = typer.Option(None, "--permission-level", help="Permission level (for get)"),
947
+ key_filter: str | None = typer.Option(None, "--key-filter", help="Key filter pattern (for get)"),
948
+ value: str | None = typer.Option(None, "--value", help="Property value (for set)"),
949
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation (for set)"),
950
+ ) -> None:
951
+ """Manage JIRA application properties."""
952
+ started_at = datetime.now(UTC)
953
+ code = 0
954
+ summary_args = [action]
955
+
956
+ try:
957
+ s = JiraSettings()
958
+ s.require_basic_or_token()
959
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
960
+
961
+ if action == "get":
962
+ summary_args.extend([f"key={key}", f"permission_level={permission_level}", f"key_filter={key_filter}"])
963
+ payload = adapter.get_property(key=key, permission_level=permission_level, key_filter=key_filter)
964
+ _emit_json(payload)
965
+
966
+ elif action == "set":
967
+ if not yes:
968
+ raise typer.BadParameter("Refusing to set property without --yes")
969
+ if not property_id:
970
+ raise typer.BadParameter("--property-id required for set")
971
+ if value is None:
972
+ raise typer.BadParameter("--value required for set")
973
+ summary_args.extend([f"property_id={property_id}", f"value={value}"])
974
+ adapter.set_property(property_id, value)
975
+ _emit_json({"status": "set", "property_id": property_id, "value": value})
976
+
977
+ else:
978
+ raise typer.BadParameter(f"Unknown action: {action}")
979
+
980
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
981
+ code = 1
982
+ logger.exception("property_failed %s", exc)
983
+ typer.echo(f"Error: {exc}", err=True)
984
+ finally:
985
+ finished_at = datetime.now(UTC)
986
+ _record_summary(
987
+ command="property",
988
+ args=summary_args,
989
+ exit_code=code,
990
+ started_at=started_at,
991
+ finished_at=finished_at,
992
+ )
993
+ if code != 0:
994
+ raise typer.Exit(code=code) from None
995
+
996
+
997
+ @app.command("settings")
998
+ def cmd_settings(
999
+ action: str = typer.Argument(..., help="Action: advanced"),
1000
+ ) -> None:
1001
+ """Manage JIRA settings."""
1002
+ started_at = datetime.now(UTC)
1003
+ code = 0
1004
+ summary_args = [action]
1005
+
1006
+ try:
1007
+ s = JiraSettings()
1008
+ s.require_basic_or_token()
1009
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1010
+
1011
+ if action == "advanced":
1012
+ payload = adapter.get_advanced_settings()
1013
+ _emit_json(payload)
1014
+ else:
1015
+ raise typer.BadParameter(f"Unknown action: {action}")
1016
+
1017
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1018
+ code = 1
1019
+ logger.exception("settings_failed %s", exc)
1020
+ typer.echo(f"Error: {exc}", err=True)
1021
+ finally:
1022
+ finished_at = datetime.now(UTC)
1023
+ _record_summary(
1024
+ command="settings",
1025
+ args=summary_args,
1026
+ exit_code=code,
1027
+ started_at=started_at,
1028
+ finished_at=finished_at,
1029
+ )
1030
+ if code != 0:
1031
+ raise typer.Exit(code=code) from None
1032
+
1033
+
1034
+ @app.command("create")
1035
+ def cmd_create(
1036
+ project: str = typer.Option(..., "--project", help="Project key"),
1037
+ issuetype: str = typer.Option(..., "--issuetype", help="Issue type name (e.g., Task, Bug)"),
1038
+ summary: str = typer.Option(..., "--summary", help="Summary"),
1039
+ description: str | None = typer.Option(None, "--description", help="Description"),
1040
+ assignee: str | None = typer.Option(None, "--assignee", help="Assignee username (Server/DC)"),
1041
+ labels: str | None = typer.Option(None, "--labels", help="Comma-separated labels"),
1042
+ set_field: list[str] = typer.Option(
1043
+ [], "--set", help="Additional key=value field pairs (e.g., customfield_12345=10001)"
1044
+ ),
1045
+ set_json: bool = typer.Option(
1046
+ False,
1047
+ "--set-json",
1048
+ help="Allow JSON scalar values (e.g., true, 1) in --set (default: objects/arrays only)",
1049
+ ),
1050
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation"),
1051
+ dry_run: bool = typer.Option(False, "--dry-run", help="Validate payload without sending"),
1052
+ ) -> None:
1053
+ """Create a new JIRA issue."""
1054
+ started_at = datetime.now(UTC)
1055
+ code = 0
1056
+ args = [f"project={project}", f"issuetype={issuetype}"]
1057
+ try:
1058
+ if not yes and not dry_run:
1059
+ raise typer.BadParameter("Refusing to create without --yes (or use --dry-run)")
1060
+
1061
+ s = JiraSettings()
1062
+ s.require_basic_or_token()
1063
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1064
+
1065
+ label_list = [x.strip() for x in labels.split(",")] if labels else None
1066
+ extras = _parse_set(set_field, allow_scalar_json=set_json) if set_field else {}
1067
+
1068
+ # Build core payload
1069
+ payload = {
1070
+ "project": {"key": project},
1071
+ "summary": summary,
1072
+ "issuetype": {"name": issuetype},
1073
+ }
1074
+ if description:
1075
+ payload["description"] = description
1076
+ if assignee:
1077
+ payload["assignee"] = {"name": assignee}
1078
+ if label_list:
1079
+ payload["labels"] = label_list
1080
+ if extras:
1081
+ payload.update(extras)
1082
+
1083
+ if dry_run:
1084
+ typer.echo(f"[DRY RUN] Validating payload for {project} / {issuetype}...", err=True)
1085
+
1086
+ # Basic validation: Check if required fields from metadata are present
1087
+ # This is "best effort" validation
1088
+ field_name_map: dict[str, str] = {}
1089
+ try:
1090
+ meta = adapter.get_createmeta(project_key=project, expand="projects.issuetypes.fields")
1091
+
1092
+ # Find project and issuetype
1093
+ found_it = None
1094
+ if "projects" in meta:
1095
+ for p in meta["projects"]:
1096
+ project_key = str(p.get("key", ""))
1097
+ project_id = str(p.get("id", ""))
1098
+ if project_key.lower() == project.lower() or project_id == project:
1099
+ for it in p.get("issuetypes", []):
1100
+ it_name = str(it.get("name", ""))
1101
+ it_id = str(it.get("id", ""))
1102
+ if it_name.lower() == issuetype.lower() or it_id == issuetype:
1103
+ found_it = it
1104
+ break
1105
+
1106
+ if found_it:
1107
+ missing = []
1108
+ fields_meta = found_it.get("fields", {})
1109
+ field_name_map = {
1110
+ fid: str(fmeta.get("name"))
1111
+ for fid, fmeta in fields_meta.items()
1112
+ if isinstance(fmeta, dict) and fmeta.get("name")
1113
+ }
1114
+ for fid, fmeta in fields_meta.items():
1115
+ if fmeta.get("required") and not fmeta.get("hasDefaultValue"):
1116
+ # Check if field is in payload
1117
+ # Note: Payload keys might be field IDs (customfield_X) or system names (summary)
1118
+ # We need to check both
1119
+ fname = fmeta.get("name", "").lower()
1120
+ if fid not in payload and fname not in payload:
1121
+ # Special case mapping for standard fields that might match
1122
+ is_present = False
1123
+ if fid in {"issuetype", "project"}:
1124
+ is_present = True
1125
+
1126
+ if not is_present:
1127
+ missing.append(f"{fmeta.get('name')} ({fid})")
1128
+
1129
+ if missing:
1130
+ typer.echo("[DRY RUN] ⚠️ Warning: The following REQUIRED fields appear missing:", err=True)
1131
+ for m in missing:
1132
+ typer.echo(f" - {m}", err=True)
1133
+ typer.echo("Tip: Use --set to provide them.", err=True)
1134
+ else:
1135
+ typer.echo("[DRY RUN] ✅ All required fields appear to be present.", err=True)
1136
+ except Exception as e:
1137
+ typer.echo(f"[DRY RUN] Metadata validation skipped due to warning: {e}", err=True)
1138
+
1139
+ output = {"dry_run": True, "payload": payload}
1140
+ if field_name_map:
1141
+ output["field_name_map"] = field_name_map
1142
+ _emit_json(output)
1143
+ return
1144
+
1145
+ # Execute creation
1146
+ res = adapter.create_issue(
1147
+ project_key=project,
1148
+ summary=summary,
1149
+ issuetype=issuetype,
1150
+ description=description,
1151
+ assignee=assignee,
1152
+ labels=label_list or [],
1153
+ extra_fields=extras,
1154
+ )
1155
+ _emit_json(res)
1156
+
1157
+ except VDSClientError as exc:
1158
+ code = 1
1159
+ msg = str(exc)
1160
+ logger.exception("create_failed %s", exc)
1161
+ typer.echo(f"Error: {exc}", err=True)
1162
+
1163
+ required_ref = _extract_required_field_ref(msg)
1164
+ field_refs = _extract_error_field_refs(msg)
1165
+ matched_fields: list[dict[str, Any]] = []
1166
+
1167
+ if field_refs:
1168
+ try:
1169
+ meta = adapter.get_createmeta(project_key=project, issuetype=issuetype)
1170
+ raw_fields = _extract_createmeta_fields(meta, project, issuetype)
1171
+ all_fields = _collect_fields(raw_fields, field_filter=None, include_system=True)
1172
+ matched_fields = _match_fields_by_refs(all_fields, field_refs)
1173
+ except Exception:
1174
+ matched_fields = []
1175
+
1176
+ if matched_fields:
1177
+ typer.echo("\n--- Field Details ---", err=True)
1178
+ for field_data in matched_fields:
1179
+ field_name = field_data.get("name") or ""
1180
+ field_id = field_data.get("id") or ""
1181
+ typer.echo(f"Field: {field_name} (id: {field_id})", err=True)
1182
+ allowed_values = field_data.get("allowedValues") or []
1183
+ allowed_text = _format_allowed_values(allowed_values)
1184
+ if allowed_text:
1185
+ typer.echo(f" Allowed values: {allowed_text}", err=True)
1186
+
1187
+ if required_ref:
1188
+ set_field_ref = required_ref
1189
+ if matched_fields:
1190
+ for field_data in matched_fields:
1191
+ field_id = str(field_data.get("id") or "")
1192
+ field_name = str(field_data.get("name") or "")
1193
+ if required_ref.lower() in field_id.lower() or required_ref.lower() in field_name.lower():
1194
+ if field_id:
1195
+ set_field_ref = field_id
1196
+ break
1197
+ typer.echo("\n--- Troubleshooting Guide ---", err=True)
1198
+ typer.echo(f"It looks like a required field '{required_ref}' is missing.", err=True)
1199
+ typer.echo("To find the correct ID and allowed values, run:", err=True)
1200
+ typer.echo(
1201
+ f" vds-cli jira discover-fields --project {project} --issuetype {issuetype} "
1202
+ f'--include-system --field "{required_ref}"',
1203
+ err=True,
1204
+ )
1205
+ typer.echo("\nThen use --set to provide the value:", err=True)
1206
+ typer.echo(f' --set \'{set_field_ref}={{"value": "..."}}\'', err=True)
1207
+
1208
+ except (AuthError, typer.BadParameter) as exc:
1209
+ code = 1
1210
+ logger.exception("create_failed %s", exc)
1211
+ typer.echo(f"Error: {exc}", err=True)
1212
+ finally:
1213
+ finished_at = datetime.now(UTC)
1214
+ _record_summary(
1215
+ command="create",
1216
+ args=[*args, f"set_json={set_json}", f"dry_run={dry_run}", f"yes={yes}"],
1217
+ exit_code=code,
1218
+ started_at=started_at,
1219
+ finished_at=finished_at,
1220
+ )
1221
+ if code != 0:
1222
+ raise typer.Exit(code=code) from None
1223
+
1224
+
1225
+ @app.command("update")
1226
+ def cmd_update(
1227
+ key: str = typer.Argument(..., help="Issue key"),
1228
+ set_field: list[str] = typer.Option([], "--set", help="key=value pairs to update (repeatable)"),
1229
+ set_json: bool = typer.Option(
1230
+ False,
1231
+ "--set-json",
1232
+ help="Allow JSON scalar values (e.g., true, 1) in --set (default: objects/arrays only)",
1233
+ ),
1234
+ add_label: list[str] = typer.Option([], "--add-label", help="Labels to add"),
1235
+ remove_label: list[str] = typer.Option([], "--remove-label", help="Labels to remove"),
1236
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation"),
1237
+ dry_run: bool = typer.Option(False, "--dry-run", help="Validate payload without sending"),
1238
+ ) -> None:
1239
+ """Update fields on an existing JIRA issue."""
1240
+ started_at = datetime.now(UTC)
1241
+ code = 0
1242
+ try:
1243
+ if not yes and not dry_run:
1244
+ raise typer.BadParameter("Refusing to update without --yes (or use --dry-run)")
1245
+ s = JiraSettings()
1246
+ s.require_basic_or_token()
1247
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1248
+ fields: dict[str, Any] = _parse_set(set_field, allow_scalar_json=set_json)
1249
+ if add_label or remove_label:
1250
+ current = adapter.get_issue(key)
1251
+ cur_labels = list(current.get("fields", {}).get("labels") or [])
1252
+ if add_label:
1253
+ cur_labels = sorted(set(cur_labels + add_label))
1254
+ if remove_label:
1255
+ cur_labels = [label for label in cur_labels if label not in set(remove_label)]
1256
+ fields["labels"] = cur_labels
1257
+ if dry_run and not yes:
1258
+ _emit_json({"dry_run": True, "request": {"key": key, "fields": fields}})
1259
+ else:
1260
+ res = adapter.update_issue_fields(key, fields)
1261
+ _emit_json(res)
1262
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1263
+ code = 1
1264
+ logger.exception("update_failed %s", exc)
1265
+ typer.echo(f"Error: {exc}", err=True)
1266
+ finally:
1267
+ finished_at = datetime.now(UTC)
1268
+ _record_summary(
1269
+ command="update",
1270
+ args=[
1271
+ key,
1272
+ *set_field,
1273
+ f"set_json={set_json}",
1274
+ f"add_label={add_label}",
1275
+ f"remove_label={remove_label}",
1276
+ f"dry_run={dry_run}",
1277
+ f"yes={yes}",
1278
+ ],
1279
+ exit_code=code,
1280
+ started_at=started_at,
1281
+ finished_at=finished_at,
1282
+ )
1283
+ if code != 0:
1284
+ raise typer.Exit(code=code) from None
1285
+
1286
+
1287
+ @app.command("comment")
1288
+ def cmd_comment(
1289
+ key: str = typer.Argument(..., help="Issue key"),
1290
+ message: str = typer.Option(..., "--message", help="Comment message"),
1291
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation"),
1292
+ ) -> None:
1293
+ """Add a comment to a JIRA issue."""
1294
+ started_at = datetime.now(UTC)
1295
+ code = 0
1296
+ try:
1297
+ if not yes:
1298
+ raise typer.BadParameter("Refusing to comment without --yes")
1299
+ s = JiraSettings()
1300
+ s.require_basic_or_token()
1301
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1302
+ res = adapter.add_comment(key, message)
1303
+ _emit_json(res)
1304
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1305
+ code = 1
1306
+ logger.exception("comment_failed %s", exc)
1307
+ typer.echo(f"Error: {exc}", err=True)
1308
+ finally:
1309
+ finished_at = datetime.now(UTC)
1310
+ _record_summary(command="comment", args=[key], exit_code=code, started_at=started_at, finished_at=finished_at)
1311
+ if code != 0:
1312
+ raise typer.Exit(code=code) from None
1313
+
1314
+
1315
+ @app.command("transition")
1316
+ def cmd_transition(
1317
+ key: str = typer.Argument(..., help="Issue key"),
1318
+ to: str = typer.Option(..., "--to", help="Target status name"),
1319
+ admin: bool = typer.Option(False, "--admin", help="Acknowledge administrative action"),
1320
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation"),
1321
+ ) -> None:
1322
+ """Transition a JIRA issue to a new workflow status."""
1323
+ started_at = datetime.now(UTC)
1324
+ code = 0
1325
+ try:
1326
+ if not (admin and yes):
1327
+ raise typer.BadParameter("Refusing to transition without --admin and --yes")
1328
+ s = JiraSettings()
1329
+ s.require_basic_or_token()
1330
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1331
+ adapter.transition_issue(key, to)
1332
+ _emit_json({"key": key, "transitioned_to": to})
1333
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1334
+ code = 1
1335
+ logger.exception("transition_failed %s", exc)
1336
+ typer.echo(f"Error: {exc}", err=True)
1337
+ finally:
1338
+ finished_at = datetime.now(UTC)
1339
+ _record_summary(
1340
+ command="transition", args=[key, f"to={to}"], exit_code=code, started_at=started_at, finished_at=finished_at
1341
+ )
1342
+ if code != 0:
1343
+ raise typer.Exit(code=code) from None
1344
+
1345
+
1346
+ @app.command("delete")
1347
+ def cmd_delete(
1348
+ key: str = typer.Argument(..., help="Issue key"),
1349
+ yes: bool = typer.Option(False, "--yes", help="Confirm deletion"),
1350
+ force: bool = typer.Option(False, "--force", help="Double confirm deletion"),
1351
+ ) -> None:
1352
+ """Delete a JIRA issue (requires permissions)."""
1353
+ started_at = datetime.now(UTC)
1354
+ code = 0
1355
+ try:
1356
+ if not (yes and force):
1357
+ raise typer.BadParameter("Refusing to delete without --yes and --force")
1358
+ s = JiraSettings()
1359
+ s.require_basic_or_token()
1360
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1361
+ adapter.delete_issue(key)
1362
+ _emit_json({"deleted": key})
1363
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1364
+ code = 1
1365
+ logger.exception("delete_failed %s", exc)
1366
+ typer.echo(f"Error: {exc}", err=True)
1367
+ finally:
1368
+ finished_at = datetime.now(UTC)
1369
+ _record_summary(command="delete", args=[key], exit_code=code, started_at=started_at, finished_at=finished_at)
1370
+ if code != 0:
1371
+ raise typer.Exit(code=code) from None
1372
+
1373
+
1374
+ @app.command("createmeta")
1375
+ def cmd_createmeta(
1376
+ project: str = typer.Option(..., "--project", help="Project key"),
1377
+ issuetype: str = typer.Option(..., "--issuetype", help="Issue type name"),
1378
+ expand: str | None = typer.Option(None, "--expand", help="Expand options"),
1379
+ ) -> None:
1380
+ """Get create metadata for issue creation (field definitions, allowed values)."""
1381
+ started_at = datetime.now(UTC)
1382
+ code = 0
1383
+ try:
1384
+ s = JiraSettings()
1385
+ s.require_basic_or_token()
1386
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1387
+ payload = adapter.get_createmeta(project_key=project, issuetype=issuetype, expand=expand)
1388
+ _emit_json(payload)
1389
+ except (AuthError, VDSClientError) as exc:
1390
+ code = 1
1391
+ logger.exception("createmeta_failed %s", exc)
1392
+ typer.echo(f"Error: {exc}", err=True)
1393
+ finally:
1394
+ finished_at = datetime.now(UTC)
1395
+ _record_summary(
1396
+ command="createmeta",
1397
+ args=[f"project={project}", f"issuetype={issuetype}"],
1398
+ exit_code=code,
1399
+ started_at=started_at,
1400
+ finished_at=finished_at,
1401
+ )
1402
+ if code != 0:
1403
+ raise typer.Exit(code=code) from None
1404
+
1405
+
1406
+ @app.command("custom-fields")
1407
+ def cmd_custom_fields(
1408
+ search: str | None = typer.Option(None, "--search", help="Search filter"),
1409
+ start: int = typer.Option(1, "--start", help="Start index"),
1410
+ limit: int = typer.Option(50, "--limit", help="Number of results"),
1411
+ ) -> None:
1412
+ """Get existing custom fields or find by filter."""
1413
+ started_at = datetime.now(UTC)
1414
+ code = 0
1415
+ try:
1416
+ s = JiraSettings()
1417
+ s.require_basic_or_token()
1418
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1419
+ payload = adapter.get_custom_fields(search=search, start=start, limit=limit)
1420
+ _emit_json({"count": len(payload), "custom_fields": payload})
1421
+ except (AuthError, VDSClientError) as exc:
1422
+ code = 1
1423
+ logger.exception("custom_fields_failed %s", exc)
1424
+ typer.echo(f"Error: {exc}", err=True)
1425
+ finally:
1426
+ finished_at = datetime.now(UTC)
1427
+ _record_summary(
1428
+ command="custom-fields",
1429
+ args=[f"search={search}", f"start={start}", f"limit={limit}"],
1430
+ exit_code=code,
1431
+ started_at=started_at,
1432
+ finished_at=finished_at,
1433
+ )
1434
+ if code != 0:
1435
+ raise typer.Exit(code=code) from None
1436
+
1437
+
1438
+ @app.command("board")
1439
+ def cmd_board(
1440
+ action: str = typer.Argument(..., help="Action: list, get, create, delete, config, issues, properties, velocity"),
1441
+ board_id: int | None = typer.Option(None, "--board-id", help="Board ID"),
1442
+ filter_id: int | None = typer.Option(None, "--filter-id", help="Filter ID (for create/get-by-filter)"),
1443
+ name: str | None = typer.Option(None, "--name", help="Board name (for create)"),
1444
+ board_type: str | None = typer.Option(None, "--type", help="Board type: scrum, kanban (for create)"),
1445
+ project_key: str | None = typer.Option(None, "--project", help="Project key (for list)"),
1446
+ limit: int = typer.Option(50, "--limit", help="Number of results"),
1447
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation"),
1448
+ ) -> None:
1449
+ """Agile board operations."""
1450
+ started_at = datetime.now(UTC)
1451
+ code = 0
1452
+ try:
1453
+ s = JiraSettings()
1454
+ s.require_basic_or_token()
1455
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1456
+
1457
+ if action == "list":
1458
+ payload = adapter.get_all_agile_boards(project_key=project_key, board_type=board_type, start=0, limit=limit)
1459
+ _emit_json({"count": len(payload), "boards": payload})
1460
+ elif action == "get":
1461
+ if not board_id and not filter_id:
1462
+ raise typer.BadParameter("Either --board-id or --filter-id required")
1463
+ if filter_id:
1464
+ payload = adapter.get_agile_board_by_filter_id(filter_id)
1465
+ else:
1466
+ payload = adapter.get_agile_board(board_id) # type: ignore[arg-type]
1467
+ _emit_json(payload)
1468
+ elif action == "create":
1469
+ if not yes:
1470
+ raise typer.BadParameter("Refusing to create without --yes")
1471
+ if not name or not board_type or not filter_id:
1472
+ raise typer.BadParameter("--name, --type, and --filter-id required for create")
1473
+ payload = adapter.create_agile_board(name, board_type, filter_id)
1474
+ _emit_json(payload)
1475
+ elif action == "delete":
1476
+ if not (yes and board_id):
1477
+ raise typer.BadParameter("Refusing to delete without --yes and --board-id")
1478
+ adapter.delete_agile_board(board_id) # type: ignore[arg-type]
1479
+ _emit_json({"deleted": board_id})
1480
+ elif action == "config":
1481
+ if not board_id:
1482
+ raise typer.BadParameter("--board-id required")
1483
+ payload = adapter.get_agile_board_configuration(board_id) # type: ignore[arg-type]
1484
+ _emit_json(payload)
1485
+ elif action == "issues":
1486
+ if not board_id:
1487
+ raise typer.BadParameter("--board-id required")
1488
+ payload = adapter.get_issues_for_board(board_id) # type: ignore[arg-type]
1489
+ _emit_json({"count": len(payload), "issues": payload})
1490
+ elif action == "properties":
1491
+ if not board_id:
1492
+ raise typer.BadParameter("--board-id required")
1493
+ payload = adapter.get_agile_board_properties(board_id) # type: ignore[arg-type]
1494
+ _emit_json({"count": len(payload), "properties": payload})
1495
+ elif action == "velocity":
1496
+ if not board_id:
1497
+ raise typer.BadParameter("--board-id required")
1498
+ payload = adapter.get_agile_board_refined_velocity(board_id) # type: ignore[arg-type]
1499
+ _emit_json(payload)
1500
+ else:
1501
+ raise typer.BadParameter(f"Unknown action: {action}")
1502
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1503
+ code = 1
1504
+ logger.exception("board_failed %s", exc)
1505
+ typer.echo(f"Error: {exc}", err=True)
1506
+ finally:
1507
+ finished_at = datetime.now(UTC)
1508
+ _record_summary(
1509
+ command="board",
1510
+ args=[action, f"board_id={board_id}", f"limit={limit}"],
1511
+ exit_code=code,
1512
+ started_at=started_at,
1513
+ finished_at=finished_at,
1514
+ )
1515
+ if code != 0:
1516
+ raise typer.Exit(code=code) from None
1517
+
1518
+
1519
+ @app.command("epic")
1520
+ def cmd_epic(
1521
+ action: str = typer.Argument(..., help="Action: issues, list, issues-for-epic"),
1522
+ epic_key: str | None = typer.Option(None, "--epic-key", help="Epic key (for issues)"),
1523
+ board_id: int | None = typer.Option(None, "--board-id", help="Board ID (for list/issues-for-epic)"),
1524
+ epic_id: int | None = typer.Option(None, "--epic-id", help="Epic ID (for issues-for-epic)"),
1525
+ done: bool = typer.Option(False, "--done", help="Include done epics (for list)"),
1526
+ limit: int = typer.Option(50, "--limit", help="Number of results"),
1527
+ ) -> None:
1528
+ """Epic operations."""
1529
+ started_at = datetime.now(UTC)
1530
+ code = 0
1531
+ try:
1532
+ s = JiraSettings()
1533
+ s.require_basic_or_token()
1534
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1535
+
1536
+ if action == "issues":
1537
+ if not epic_key:
1538
+ raise typer.BadParameter("--epic-key required")
1539
+ payload = adapter.epic_issues(epic_key)
1540
+ _emit_json({"count": len(payload), "issues": payload})
1541
+ elif action == "list":
1542
+ if not board_id:
1543
+ raise typer.BadParameter("--board-id required")
1544
+ payload = adapter.get_epics(board_id, done=done, start=0, limit=limit) # type: ignore[arg-type]
1545
+ _emit_json({"count": len(payload), "epics": payload})
1546
+ elif action == "issues-for-epic":
1547
+ if not board_id or not epic_id:
1548
+ raise typer.BadParameter("--board-id and --epic-id required")
1549
+ payload = adapter.get_issues_for_epic(
1550
+ board_id, epic_id, jql="", validate_query="", fields="*all", expand="", start=0, limit=limit
1551
+ ) # type: ignore[arg-type]
1552
+ _emit_json({"count": len(payload), "issues": payload})
1553
+ else:
1554
+ raise typer.BadParameter(f"Unknown action: {action}")
1555
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1556
+ code = 1
1557
+ logger.exception("epic_failed %s", exc)
1558
+ typer.echo(f"Error: {exc}", err=True)
1559
+ finally:
1560
+ finished_at = datetime.now(UTC)
1561
+ _record_summary(
1562
+ command="epic",
1563
+ args=[action, f"epic_key={epic_key}", f"board_id={board_id}"],
1564
+ exit_code=code,
1565
+ started_at=started_at,
1566
+ finished_at=finished_at,
1567
+ )
1568
+ if code != 0:
1569
+ raise typer.Exit(code=code) from None
1570
+
1571
+
1572
+ @app.command("sprint")
1573
+ def cmd_sprint(
1574
+ action: str = typer.Argument(..., help="Action: list, issues, versions, create, rename, add-issues"),
1575
+ board_id: int | None = typer.Option(None, "--board-id", help="Board ID"),
1576
+ sprint_id: int | None = typer.Option(None, "--sprint-id", help="Sprint ID"),
1577
+ state: str | None = typer.Option(None, "--state", help="Sprint state filter"),
1578
+ sprint_name: str | None = typer.Option(None, "--name", help="Sprint name (for create/rename)"),
1579
+ start_date: str | None = typer.Option(None, "--start-date", help="Start date (for create/rename)"),
1580
+ end_date: str | None = typer.Option(None, "--end-date", help="End date (for create/rename)"),
1581
+ goal: str | None = typer.Option(None, "--goal", help="Sprint goal (for create)"),
1582
+ issues: str | None = typer.Option(None, "--issues", help="Comma-separated issue keys (for add-issues)"),
1583
+ limit: int = typer.Option(50, "--limit", help="Number of results"),
1584
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation"),
1585
+ ) -> None:
1586
+ """Sprint operations."""
1587
+ started_at = datetime.now(UTC)
1588
+ code = 0
1589
+ try:
1590
+ s = JiraSettings()
1591
+ s.require_basic_or_token()
1592
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1593
+
1594
+ if action == "list":
1595
+ if not board_id:
1596
+ raise typer.BadParameter("--board-id required")
1597
+ payload = adapter.get_all_sprints_from_board(board_id, state=state, start=0, limit=limit) # type: ignore[arg-type]
1598
+ _emit_json({"count": len(payload), "sprints": payload})
1599
+ elif action == "issues":
1600
+ if not board_id:
1601
+ raise typer.BadParameter("--board-id required")
1602
+ payload = adapter.get_all_issues_for_sprint_in_board(board_id, state=state, start=0, limit=limit) # type: ignore[arg-type]
1603
+ _emit_json({"count": len(payload), "issues": payload})
1604
+ elif action == "versions":
1605
+ if not board_id:
1606
+ raise typer.BadParameter("--board-id required")
1607
+ payload = adapter.get_all_versions_from_board(board_id, start=0, limit=limit) # type: ignore[arg-type]
1608
+ _emit_json({"count": len(payload), "versions": payload})
1609
+ elif action == "create":
1610
+ if not yes:
1611
+ raise typer.BadParameter("Refusing to create without --yes")
1612
+ if not sprint_name or not board_id:
1613
+ raise typer.BadParameter("--name and --board-id required for create")
1614
+ payload = adapter.create_sprint(
1615
+ sprint_name,
1616
+ board_id,
1617
+ start_datetime=start_date,
1618
+ end_datetime=end_date,
1619
+ goal=goal, # type: ignore[arg-type]
1620
+ )
1621
+ _emit_json(payload)
1622
+ elif action == "rename":
1623
+ if not yes:
1624
+ raise typer.BadParameter("Refusing to rename without --yes")
1625
+ if not sprint_id:
1626
+ raise typer.BadParameter("--sprint-id required")
1627
+ payload = adapter.rename_sprint(sprint_id, name=sprint_name, start_date=start_date, end_date=end_date) # type: ignore[arg-type]
1628
+ _emit_json(payload)
1629
+ elif action == "add-issues":
1630
+ if not yes:
1631
+ raise typer.BadParameter("Refusing to add issues without --yes")
1632
+ if not sprint_id or not issues:
1633
+ raise typer.BadParameter("--sprint-id and --issues required")
1634
+ issues_list = [i.strip() for i in issues.split(",")]
1635
+ adapter.add_issues_to_sprint(sprint_id, issues_list) # type: ignore[arg-type]
1636
+ _emit_json({"sprint_id": sprint_id, "issues_added": issues_list})
1637
+ else:
1638
+ raise typer.BadParameter(f"Unknown action: {action}")
1639
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1640
+ code = 1
1641
+ logger.exception("sprint_failed %s", exc)
1642
+ typer.echo(f"Error: {exc}", err=True)
1643
+ finally:
1644
+ finished_at = datetime.now(UTC)
1645
+ _record_summary(
1646
+ command="sprint",
1647
+ args=[action, f"board_id={board_id}", f"sprint_id={sprint_id}"],
1648
+ exit_code=code,
1649
+ started_at=started_at,
1650
+ finished_at=finished_at,
1651
+ )
1652
+ if code != 0:
1653
+ raise typer.Exit(code=code) from None
1654
+
1655
+
1656
+ @app.command("backlog")
1657
+ def cmd_backlog(
1658
+ action: str = typer.Argument(..., help="Action: move, add"),
1659
+ issues: str = typer.Option(..., "--issues", help="Comma-separated issue keys"),
1660
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation"),
1661
+ ) -> None:
1662
+ """Backlog operations."""
1663
+ started_at = datetime.now(UTC)
1664
+ code = 0
1665
+ try:
1666
+ if not yes:
1667
+ raise typer.BadParameter("Refusing to modify backlog without --yes")
1668
+ s = JiraSettings()
1669
+ s.require_basic_or_token()
1670
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1671
+ issues_list = [i.strip() for i in issues.split(",")]
1672
+
1673
+ if action == "move":
1674
+ adapter.move_issues_to_backlog(issues_list)
1675
+ _emit_json({"action": "moved_to_backlog", "issues": issues_list})
1676
+ elif action == "add":
1677
+ adapter.add_issues_to_backlog(issues_list)
1678
+ _emit_json({"action": "added_to_backlog", "issues": issues_list})
1679
+ else:
1680
+ raise typer.BadParameter(f"Unknown action: {action}")
1681
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1682
+ code = 1
1683
+ logger.exception("backlog_failed %s", exc)
1684
+ typer.echo(f"Error: {exc}", err=True)
1685
+ finally:
1686
+ finished_at = datetime.now(UTC)
1687
+ _record_summary(
1688
+ command="backlog",
1689
+ args=[action, f"issues={issues}"],
1690
+ exit_code=code,
1691
+ started_at=started_at,
1692
+ finished_at=finished_at,
1693
+ )
1694
+ if code != 0:
1695
+ raise typer.Exit(code=code) from None
1696
+
1697
+
1698
+ @app.command("worklog")
1699
+ def cmd_worklog(
1700
+ action: str = typer.Argument(..., help="Action: get, add, updated, deleted"),
1701
+ issue_key: str | None = typer.Option(None, "--issue-key", help="Issue key (for get/add)"),
1702
+ time_spent: str | None = typer.Option(None, "--time-spent", help="Time spent (e.g., '2h 30m') (for add)"),
1703
+ comment: str | None = typer.Option(None, "--comment", help="Worklog comment (for add)"),
1704
+ started: str | None = typer.Option(None, "--started", help="Start datetime in ISO format (for add)"),
1705
+ time_sec: int | None = typer.Option(None, "--time-sec", help="Time in seconds (alternative to --time-spent)"),
1706
+ since: str | None = typer.Option(None, "--since", help="Timestamp for updated/deleted (YYYY-MM-DD HH:mm or ISO)"),
1707
+ expand: str | None = typer.Option(None, "--expand", help="Expand parameter (for updated)"),
1708
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation"),
1709
+ ) -> None:
1710
+ """Worklog operations."""
1711
+ started_at = datetime.now(UTC)
1712
+ code = 0
1713
+ try:
1714
+ s = JiraSettings()
1715
+ s.require_basic_or_token()
1716
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1717
+
1718
+ if action == "get":
1719
+ if not issue_key:
1720
+ raise typer.BadParameter("--issue-key required for get")
1721
+ payload = adapter.get_worklogs(issue_key)
1722
+ _emit_json(payload)
1723
+ elif action == "add":
1724
+ if not yes:
1725
+ raise typer.BadParameter("Refusing to add worklog without --yes")
1726
+ if not issue_key:
1727
+ raise typer.BadParameter("--issue-key required for add")
1728
+ if not time_spent and time_sec is None:
1729
+ raise typer.BadParameter("--time-spent or --time-sec required for add")
1730
+ payload = adapter.issue_add_worklog(
1731
+ issue_key=issue_key,
1732
+ time_spent=time_spent or "",
1733
+ comment=comment,
1734
+ started=started,
1735
+ time_sec=time_sec,
1736
+ )
1737
+ _emit_json(payload)
1738
+ elif action == "updated":
1739
+ if not since:
1740
+ raise typer.BadParameter("--since required for updated")
1741
+ payload = adapter.get_updated_worklogs(since, expand=expand)
1742
+ _emit_json(payload)
1743
+ elif action == "deleted":
1744
+ if not since:
1745
+ raise typer.BadParameter("--since required for deleted")
1746
+ payload = adapter.get_deleted_worklogs(since)
1747
+ _emit_json(payload)
1748
+ else:
1749
+ raise typer.BadParameter(f"Unknown action: {action}")
1750
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1751
+ code = 1
1752
+ logger.exception("worklog_failed %s", exc)
1753
+ typer.echo(f"Error: {exc}", err=True)
1754
+ finally:
1755
+ finished_at = datetime.now(UTC)
1756
+ _record_summary(
1757
+ command="worklog",
1758
+ args=[action, f"issue_key={issue_key}", f"since={since}"],
1759
+ exit_code=code,
1760
+ started_at=started_at,
1761
+ finished_at=finished_at,
1762
+ )
1763
+ if code != 0:
1764
+ raise typer.Exit(code=code) from None
1765
+
1766
+
1767
+ @app.command("transitions")
1768
+ def cmd_transitions(
1769
+ issue_key: str = typer.Argument(..., help="Issue key"),
1770
+ ) -> None:
1771
+ """Get available transitions for an issue."""
1772
+ started_at = datetime.now(UTC)
1773
+ code = 0
1774
+ try:
1775
+ s = JiraSettings()
1776
+ s.require_basic_or_token()
1777
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1778
+
1779
+ payload = adapter.get_issue_transitions(issue_key)
1780
+ _emit_json(payload)
1781
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1782
+ code = 1
1783
+ logger.exception("transitions_failed %s", exc)
1784
+ typer.echo(f"Error: {exc}", err=True)
1785
+ finally:
1786
+ finished_at = datetime.now(UTC)
1787
+ _record_summary(
1788
+ command="transitions",
1789
+ args=[issue_key],
1790
+ exit_code=code,
1791
+ started_at=started_at,
1792
+ finished_at=finished_at,
1793
+ )
1794
+ if code != 0:
1795
+ raise typer.Exit(code=code) from None
1796
+
1797
+
1798
+ @app.command("changelog")
1799
+ def cmd_changelog(
1800
+ action: str = typer.Argument(..., help="Action: get, status"),
1801
+ issue_key: str = typer.Argument(..., help="Issue key"),
1802
+ start: int | None = typer.Option(None, "--start", help="Start index for pagination"),
1803
+ limit: int | None = typer.Option(None, "--limit", help="Limit for pagination"),
1804
+ ) -> None:
1805
+ """Get issue changelog (change history)."""
1806
+ started_at = datetime.now(UTC)
1807
+ code = 0
1808
+ try:
1809
+ s = JiraSettings()
1810
+ s.require_basic_or_token()
1811
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1812
+
1813
+ if action == "get":
1814
+ payload = adapter.get_issue_changelog(issue_key, start=start, limit=limit)
1815
+ _emit_json(payload)
1816
+ elif action == "status":
1817
+ payload = adapter.get_issue_status_changelog(issue_key)
1818
+ _emit_json(payload)
1819
+ else:
1820
+ raise typer.BadParameter(f"Unknown action: {action}")
1821
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1822
+ code = 1
1823
+ logger.exception("changelog_failed %s", exc)
1824
+ typer.echo(f"Error: {exc}", err=True)
1825
+ finally:
1826
+ finished_at = datetime.now(UTC)
1827
+ _record_summary(
1828
+ command="changelog",
1829
+ args=[action, issue_key, f"start={start}", f"limit={limit}"],
1830
+ exit_code=code,
1831
+ started_at=started_at,
1832
+ finished_at=finished_at,
1833
+ )
1834
+ if code != 0:
1835
+ raise typer.Exit(code=code) from None
1836
+
1837
+
1838
+ @app.command("filter")
1839
+ def cmd_filter(
1840
+ action: str = typer.Argument(..., help="Action: list, get, create, update, delete"),
1841
+ filter_id: int | None = typer.Option(None, "--filter-id", help="Filter ID (for get, update, delete)"),
1842
+ owner: str | None = typer.Option(None, "--owner", help="Owner username (for list)"),
1843
+ name: str | None = typer.Option(None, "--name", help="Filter name (for create, update)"),
1844
+ jql: str | None = typer.Option(None, "--jql", help="JQL query (for create, update)"),
1845
+ description: str | None = typer.Option(None, "--description", help="Filter description (for create, update)"),
1846
+ favourite: bool | None = typer.Option(
1847
+ None, "--favourite/--no-favourite", help="Mark as favourite (for create, update)"
1848
+ ),
1849
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation (for create, update, delete)"),
1850
+ ) -> None:
1851
+ """Filter management operations."""
1852
+ started_at = datetime.now(UTC)
1853
+ code = 0
1854
+ try:
1855
+ s = JiraSettings()
1856
+ s.require_basic_or_token()
1857
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1858
+
1859
+ if action == "list":
1860
+ payload = adapter.get_filters(owner=owner)
1861
+ _emit_json({"filters": payload, "count": len(payload)})
1862
+ elif action == "get":
1863
+ if not filter_id:
1864
+ raise typer.BadParameter("--filter-id required for get")
1865
+ payload = adapter.get_filter(filter_id)
1866
+ _emit_json(payload)
1867
+ elif action == "create":
1868
+ if not yes:
1869
+ raise typer.BadParameter("Refusing to create filter without --yes")
1870
+ if not name:
1871
+ raise typer.BadParameter("--name required for create")
1872
+ if not jql:
1873
+ raise typer.BadParameter("--jql required for create")
1874
+ payload = adapter.create_filter(
1875
+ name=name, jql=jql, description=description, favourite=favourite if favourite is not None else False
1876
+ )
1877
+ _emit_json(payload)
1878
+ elif action == "update":
1879
+ if not yes:
1880
+ raise typer.BadParameter("Refusing to update filter without --yes")
1881
+ if not filter_id:
1882
+ raise typer.BadParameter("--filter-id required for update")
1883
+ if not (name or jql or description is not None or favourite is not None):
1884
+ raise typer.BadParameter(
1885
+ "At least one of --name, --jql, --description, or --favourite must be provided for update"
1886
+ )
1887
+ payload = adapter.update_filter(
1888
+ filter_id=filter_id, name=name, jql=jql, description=description, favourite=favourite
1889
+ )
1890
+ _emit_json(payload)
1891
+ elif action == "delete":
1892
+ if not yes:
1893
+ raise typer.BadParameter("Refusing to delete filter without --yes")
1894
+ if not filter_id:
1895
+ raise typer.BadParameter("--filter-id required for delete")
1896
+ adapter.delete_filter(filter_id)
1897
+ _emit_json({"status": "success", "filter_id": filter_id, "message": "Filter deleted"})
1898
+ else:
1899
+ raise typer.BadParameter(f"Unknown action: {action}")
1900
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1901
+ code = 1
1902
+ logger.exception("filter_failed %s", exc)
1903
+ typer.echo(f"Error: {exc}", err=True)
1904
+ finally:
1905
+ finished_at = datetime.now(UTC)
1906
+ args_list: list[str] = [action]
1907
+ if filter_id:
1908
+ args_list.append(f"filter_id={filter_id}")
1909
+ if owner:
1910
+ args_list.append(f"owner={owner}")
1911
+ if name:
1912
+ args_list.append(f"name={name}")
1913
+ if jql:
1914
+ args_list.append(f"jql={jql}")
1915
+ if description:
1916
+ args_list.append(f"description={description}")
1917
+ if favourite is not None:
1918
+ args_list.append(f"favourite={favourite}")
1919
+ _record_summary(
1920
+ command="filter",
1921
+ args=args_list,
1922
+ exit_code=code,
1923
+ started_at=started_at,
1924
+ finished_at=finished_at,
1925
+ )
1926
+ if code != 0:
1927
+ raise typer.Exit(code=code) from None
1928
+
1929
+
1930
+ @app.command("dashboard")
1931
+ def cmd_dashboard(
1932
+ action: str = typer.Argument(..., help="Action: get, list"),
1933
+ dashboard_id: str | None = typer.Option(None, "--dashboard-id", help="Dashboard ID (for get)"),
1934
+ filter_str: str | None = typer.Option(None, "--filter", help="Filter string (for list)"),
1935
+ start: int = typer.Option(0, "--start", help="Start index for pagination (for list)"),
1936
+ limit: int = typer.Option(10, "--limit", help="Limit for pagination (for list)"),
1937
+ ) -> None:
1938
+ """Dashboard operations."""
1939
+ started_at = datetime.now(UTC)
1940
+ code = 0
1941
+ try:
1942
+ s = JiraSettings()
1943
+ s.require_basic_or_token()
1944
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
1945
+
1946
+ if action == "get":
1947
+ if not dashboard_id:
1948
+ raise typer.BadParameter("--dashboard-id required for get")
1949
+ # Try to parse as int, otherwise use as string
1950
+ try:
1951
+ dashboard_id_int = int(dashboard_id)
1952
+ payload = adapter.get_dashboard(dashboard_id_int)
1953
+ except ValueError:
1954
+ payload = adapter.get_dashboard(dashboard_id)
1955
+ _emit_json(payload)
1956
+ elif action == "list":
1957
+ payload = adapter.get_dashboards(filter=filter_str or "", start=start, limit=limit)
1958
+ _emit_json(payload)
1959
+ else:
1960
+ raise typer.BadParameter(f"Unknown action: {action}")
1961
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
1962
+ code = 1
1963
+ logger.exception("dashboard_failed %s", exc)
1964
+ typer.echo(f"Error: {exc}", err=True)
1965
+ finally:
1966
+ finished_at = datetime.now(UTC)
1967
+ _record_summary(
1968
+ command="dashboard",
1969
+ args=[action, f"dashboard_id={dashboard_id}", f"filter={filter_str}", f"start={start}", f"limit={limit}"],
1970
+ exit_code=code,
1971
+ started_at=started_at,
1972
+ finished_at=finished_at,
1973
+ )
1974
+ if code != 0:
1975
+ raise typer.Exit(code=code) from None
1976
+
1977
+
1978
+ @app.command("issue-link")
1979
+ def cmd_issue_link(
1980
+ action: str = typer.Argument(..., help="Action: create, delete"),
1981
+ link_type: str | None = typer.Option(None, "--type", help="Issue link type name (e.g., Blocks)"),
1982
+ inward_issue: str | None = typer.Option(
1983
+ None, "--inward-issue", help="Inward issue key (the issue being blocked/related)"
1984
+ ),
1985
+ outward_issue: str | None = typer.Option(
1986
+ None, "--outward-issue", help="Outward issue key (the issue that blocks/relates)"
1987
+ ),
1988
+ link_id: str | None = typer.Option(None, "--link-id", help="Issue link ID (required for delete)"),
1989
+ comment: str | None = typer.Option(None, "--comment", help="Optional comment for the created link"),
1990
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation"),
1991
+ dry_run: bool = typer.Option(False, "--dry-run", help="Validate payload without sending"),
1992
+ ) -> None:
1993
+ """Issue-to-issue link operations."""
1994
+ started_at = datetime.now(UTC)
1995
+ code = 0
1996
+ try:
1997
+ s = JiraSettings()
1998
+ s.require_basic_or_token()
1999
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
2000
+
2001
+ if action == "create":
2002
+ if not yes and not dry_run:
2003
+ raise typer.BadParameter("--yes required for create (or use --dry-run)")
2004
+ if not link_type:
2005
+ raise typer.BadParameter("--type required for create")
2006
+ if not inward_issue:
2007
+ raise typer.BadParameter("--inward-issue required for create")
2008
+ if not outward_issue:
2009
+ raise typer.BadParameter("--outward-issue required for create")
2010
+
2011
+ # Atlassian semantics for "Blocks":
2012
+ # - inwardIssue: issue on the "is blocked by" side (the blocker)
2013
+ # - outwardIssue: issue on the "blocks" side (the blocked issue)
2014
+ payload: dict[str, Any] = {
2015
+ "type": {"name": link_type},
2016
+ "inwardIssue": {"key": outward_issue},
2017
+ "outwardIssue": {"key": inward_issue},
2018
+ }
2019
+ if comment:
2020
+ payload["comment"] = {"body": comment}
2021
+
2022
+ if dry_run and not yes:
2023
+ _emit_json({"dry_run": True, "request": payload})
2024
+ else:
2025
+ result = adapter.create_issue_link(payload)
2026
+ _emit_json(
2027
+ {
2028
+ "status": "created",
2029
+ "type": link_type,
2030
+ "inward_issue": inward_issue,
2031
+ "outward_issue": outward_issue,
2032
+ "result": result,
2033
+ }
2034
+ )
2035
+ elif action == "delete":
2036
+ if not yes and not dry_run:
2037
+ raise typer.BadParameter("--yes required for delete (or use --dry-run)")
2038
+ if not link_id:
2039
+ raise typer.BadParameter("--link-id required for delete")
2040
+
2041
+ parsed_link_id: str | int
2042
+ try:
2043
+ parsed_link_id = int(link_id)
2044
+ except ValueError:
2045
+ parsed_link_id = link_id
2046
+
2047
+ if dry_run and not yes:
2048
+ _emit_json({"dry_run": True, "request": {"action": "delete", "link_id": parsed_link_id}})
2049
+ else:
2050
+ adapter.remove_issue_link(parsed_link_id)
2051
+ _emit_json({"status": "deleted", "link_id": parsed_link_id})
2052
+ else:
2053
+ raise typer.BadParameter(f"Unknown action: {action}")
2054
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
2055
+ code = 1
2056
+ logger.exception("issue_link_failed %s", exc)
2057
+ typer.echo(f"Error: {exc}", err=True)
2058
+ finally:
2059
+ finished_at = datetime.now(UTC)
2060
+ _record_summary(
2061
+ command="issue-link",
2062
+ args=[
2063
+ action,
2064
+ f"type={link_type}",
2065
+ f"inward_issue={inward_issue}",
2066
+ f"outward_issue={outward_issue}",
2067
+ f"link_id={link_id}",
2068
+ f"comment={comment}",
2069
+ f"yes={yes}",
2070
+ f"dry_run={dry_run}",
2071
+ ],
2072
+ exit_code=code,
2073
+ started_at=started_at,
2074
+ finished_at=finished_at,
2075
+ )
2076
+ if code != 0:
2077
+ raise typer.Exit(code=code) from None
2078
+
2079
+
2080
+ @app.command("link-type")
2081
+ def cmd_link_type(
2082
+ action: str = typer.Argument(..., help="Action: list, get, create, update, delete"),
2083
+ link_type_id: str | None = typer.Option(None, "--link-type-id", help="Link type ID (for get, update, delete)"),
2084
+ name: str | None = typer.Option(None, "--name", help="Link type name (for create, update)"),
2085
+ inward: str | None = typer.Option(None, "--inward", help="Inward description (for create, update)"),
2086
+ outward: str | None = typer.Option(None, "--outward", help="Outward description (for create, update)"),
2087
+ data_file: Path | None = typer.Option(
2088
+ None, "--data-file", help="JSON file with link type data (for create, update)"
2089
+ ),
2090
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation (for create, update, delete)"),
2091
+ ) -> None:
2092
+ """Issue link type operations."""
2093
+ started_at = datetime.now(UTC)
2094
+ code = 0
2095
+ try:
2096
+ s = JiraSettings()
2097
+ s.require_basic_or_token()
2098
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
2099
+
2100
+ if action == "list":
2101
+ payload = adapter.get_issue_link_types()
2102
+ _emit_json({"count": len(payload), "link_types": payload})
2103
+ elif action == "get":
2104
+ if not link_type_id:
2105
+ raise typer.BadParameter("--link-type-id required for get")
2106
+ payload = adapter.get_issue_link_type(link_type_id)
2107
+ _emit_json(payload)
2108
+ elif action == "create":
2109
+ if not yes:
2110
+ raise typer.BadParameter("Refusing to create link type without --yes")
2111
+ if data_file:
2112
+ if not data_file.exists():
2113
+ raise typer.BadParameter(f"Data file not found: {data_file}")
2114
+ with data_file.open() as f:
2115
+ data = json.load(f)
2116
+ else:
2117
+ if not name or not inward or not outward:
2118
+ raise typer.BadParameter("--name, --inward, and --outward required for create (or use --data-file)")
2119
+ data = {"name": name, "inward": inward, "outward": outward}
2120
+ payload = adapter.create_issue_link_type(data)
2121
+ _emit_json(payload)
2122
+ elif action == "update":
2123
+ if not yes:
2124
+ raise typer.BadParameter("Refusing to update link type without --yes")
2125
+ if not link_type_id:
2126
+ raise typer.BadParameter("--link-type-id required for update")
2127
+ if data_file:
2128
+ if not data_file.exists():
2129
+ raise typer.BadParameter(f"Data file not found: {data_file}")
2130
+ with data_file.open() as f:
2131
+ data = json.load(f)
2132
+ else:
2133
+ if not name or not inward or not outward:
2134
+ raise typer.BadParameter("--name, --inward, and --outward required for update (or use --data-file)")
2135
+ data = {"name": name, "inward": inward, "outward": outward}
2136
+ payload = adapter.update_issue_link_type(link_type_id, data)
2137
+ _emit_json(payload)
2138
+ elif action == "delete":
2139
+ if not yes:
2140
+ raise typer.BadParameter("Refusing to delete link type without --yes")
2141
+ if not link_type_id:
2142
+ raise typer.BadParameter("--link-type-id required for delete")
2143
+ adapter.delete_issue_link_type(link_type_id)
2144
+ _emit_json({"status": "deleted", "link_type_id": link_type_id})
2145
+ else:
2146
+ raise typer.BadParameter(f"Unknown action: {action}")
2147
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
2148
+ code = 1
2149
+ logger.exception("link_type_failed %s", exc)
2150
+ typer.echo(f"Error: {exc}", err=True)
2151
+ finally:
2152
+ finished_at = datetime.now(UTC)
2153
+ _record_summary(
2154
+ command="link-type",
2155
+ args=[
2156
+ action,
2157
+ f"link_type_id={link_type_id}",
2158
+ f"name={name}",
2159
+ f"inward={inward}",
2160
+ f"outward={outward}",
2161
+ f"data_file={data_file}",
2162
+ f"yes={yes}",
2163
+ ],
2164
+ exit_code=code,
2165
+ started_at=started_at,
2166
+ finished_at=finished_at,
2167
+ )
2168
+ if code != 0:
2169
+ raise typer.Exit(code=code) from None
2170
+
2171
+
2172
+ @app.command("remote-link")
2173
+ def cmd_remote_link(
2174
+ action: str = typer.Argument(..., help="Action: list, get, create, update, delete"),
2175
+ issue_key: str = typer.Option(..., "--issue-key", "-k", help="Issue key (e.g., NTTC-123)"),
2176
+ remote_link_id: str | None = typer.Option(
2177
+ None, "--remote-link-id", "-r", help="Remote link ID (for get/update/delete)"
2178
+ ),
2179
+ url: str | None = typer.Option(None, "--url", help="Remote link target URL (for create/update)"),
2180
+ title: str | None = typer.Option(None, "--title", help="Remote link title"),
2181
+ summary: str | None = typer.Option(None, "--summary", help="Remote link summary/description"),
2182
+ relationship: str | None = typer.Option(None, "--relationship", help="Relationship description"),
2183
+ global_id: str | None = typer.Option(None, "--global-id", help="Global ID for remote link"),
2184
+ application_type: str | None = typer.Option(None, "--application-type", help="Application type for remote link"),
2185
+ application_name: str | None = typer.Option(None, "--application-name", help="Application name for remote link"),
2186
+ icon_url: str | None = typer.Option(None, "--icon-url", help="Icon URL for the remote link object"),
2187
+ icon_title: str | None = typer.Option(None, "--icon-title", help="Icon title for the remote link object"),
2188
+ data_file: Path | None = typer.Option(None, "--data-file", help="JSON file describing the remote link payload"),
2189
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operations (create/update/delete)"),
2190
+ ) -> None:
2191
+ """Manage Jira issue remote links."""
2192
+ started_at = datetime.now(UTC)
2193
+ code = 0
2194
+ summary_args: list[str] = [
2195
+ action,
2196
+ f"issue={issue_key}",
2197
+ f"remote_link_id={remote_link_id}",
2198
+ f"data_file={data_file}",
2199
+ ]
2200
+
2201
+ def _parse_remote_link_identifier(value: str | None) -> str | int | None:
2202
+ if value is None:
2203
+ return None
2204
+ try:
2205
+ return int(value)
2206
+ except ValueError:
2207
+ return value
2208
+
2209
+ try:
2210
+ s = JiraSettings()
2211
+ s.require_basic_or_token()
2212
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
2213
+
2214
+ if action == "list":
2215
+ payload = adapter.get_issue_remote_links(issue_key)
2216
+ _emit_json({"count": len(payload), "remote_links": payload})
2217
+
2218
+ elif action == "get":
2219
+ remote_id = _parse_remote_link_identifier(remote_link_id)
2220
+ if remote_id is None:
2221
+ raise typer.BadParameter("--remote-link-id required for get")
2222
+ payload = adapter.get_issue_remote_link_by_id(issue_key, remote_id)
2223
+ _emit_json(payload)
2224
+
2225
+ elif action == "create":
2226
+ if not yes:
2227
+ raise typer.BadParameter("--yes required for create")
2228
+ payload_data = _build_remote_link_payload(
2229
+ url=url,
2230
+ title=title,
2231
+ summary=summary,
2232
+ relationship=relationship,
2233
+ global_id=global_id,
2234
+ application_type=application_type,
2235
+ application_name=application_name,
2236
+ icon_url=icon_url,
2237
+ icon_title=icon_title,
2238
+ data_file=data_file,
2239
+ )
2240
+ result = adapter.create_or_update_issue_remote_link(issue_key, payload_data)
2241
+ _emit_json(result)
2242
+
2243
+ elif action == "update":
2244
+ if not yes:
2245
+ raise typer.BadParameter("--yes required for update")
2246
+ remote_id = _parse_remote_link_identifier(remote_link_id)
2247
+ if remote_id is None:
2248
+ raise typer.BadParameter("--remote-link-id required for update")
2249
+ payload_data = _build_remote_link_payload(
2250
+ url=url,
2251
+ title=title,
2252
+ summary=summary,
2253
+ relationship=relationship,
2254
+ global_id=global_id,
2255
+ application_type=application_type,
2256
+ application_name=application_name,
2257
+ icon_url=icon_url,
2258
+ icon_title=icon_title,
2259
+ data_file=data_file,
2260
+ )
2261
+ result = adapter.update_issue_remote_link_by_id(issue_key, remote_id, payload_data)
2262
+ _emit_json(result)
2263
+
2264
+ elif action == "delete":
2265
+ if not yes:
2266
+ raise typer.BadParameter("--yes required for delete")
2267
+ remote_id = _parse_remote_link_identifier(remote_link_id)
2268
+ if remote_id is None:
2269
+ raise typer.BadParameter("--remote-link-id required for delete")
2270
+ adapter.delete_issue_remote_link_by_id(issue_key, remote_id)
2271
+ _emit_json({"status": "deleted", "issue": issue_key, "remote_link_id": remote_id})
2272
+
2273
+ else:
2274
+ raise typer.BadParameter(f"Unknown action: {action}")
2275
+
2276
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
2277
+ code = 1
2278
+ logger.exception("remote_link_command_failed %s", exc)
2279
+ typer.echo(f"Error: {exc}", err=True)
2280
+ finally:
2281
+ finished_at = datetime.now(UTC)
2282
+ _record_summary(
2283
+ command="remote-link",
2284
+ args=summary_args,
2285
+ exit_code=code,
2286
+ started_at=started_at,
2287
+ finished_at=finished_at,
2288
+ )
2289
+ if code != 0:
2290
+ raise typer.Exit(code=code) from None
2291
+
2292
+
2293
+ @app.command("issue-property")
2294
+ def cmd_issue_property(
2295
+ action: str = typer.Argument(..., help="Action: list, get, set, delete"),
2296
+ issue_key: str = typer.Option(..., "--issue-key", "-k", help="Issue key (e.g., NTTC-123)"),
2297
+ property_key: str | None = typer.Option(
2298
+ None, "--property-key", "-p", help="Property key (required for get/set/delete)"
2299
+ ),
2300
+ data_file: Path | None = typer.Option(None, "--data-file", help="JSON file containing property value (for set)"),
2301
+ value: str | None = typer.Option(None, "--value", help="JSON string value (for set when no file provided)"),
2302
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operations (set/delete)"),
2303
+ ) -> None:
2304
+ """Manage Jira issue properties."""
2305
+ started_at = datetime.now(UTC)
2306
+ code = 0
2307
+ summary_args = [
2308
+ action,
2309
+ f"issue={issue_key}",
2310
+ f"property_key={property_key}",
2311
+ f"data_file={data_file}",
2312
+ ]
2313
+
2314
+ def _load_value() -> dict[str, Any]:
2315
+ if data_file:
2316
+ if not data_file.exists():
2317
+ raise typer.BadParameter(f"Data file not found: {data_file}")
2318
+ try:
2319
+ return json.loads(data_file.read_text())
2320
+ except json.JSONDecodeError as exc:
2321
+ raise typer.BadParameter(f"Invalid JSON in {data_file}: {exc}") from exc
2322
+ if value is None:
2323
+ raise typer.BadParameter("Provide --value or --data-file for set action")
2324
+ try:
2325
+ parsed = json.loads(value)
2326
+ except json.JSONDecodeError as exc:
2327
+ raise typer.BadParameter(f"Invalid JSON for --value: {exc}") from exc
2328
+ if not isinstance(parsed, dict):
2329
+ raise typer.BadParameter("--value must decode to a JSON object")
2330
+ return parsed
2331
+
2332
+ try:
2333
+ s = JiraSettings()
2334
+ s.require_basic_or_token()
2335
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
2336
+
2337
+ if action == "list":
2338
+ payload = adapter.get_issue_property_keys(issue_key)
2339
+ _emit_json(payload)
2340
+
2341
+ elif action == "get":
2342
+ if not property_key:
2343
+ raise typer.BadParameter("--property-key required for get")
2344
+ payload = adapter.get_issue_property(issue_key, property_key)
2345
+ _emit_json(payload)
2346
+
2347
+ elif action == "set":
2348
+ if not yes:
2349
+ raise typer.BadParameter("--yes required for set")
2350
+ if not property_key:
2351
+ raise typer.BadParameter("--property-key required for set")
2352
+ payload = _load_value()
2353
+ adapter.set_issue_property(issue_key, property_key, payload)
2354
+ _emit_json({"status": "updated", "issue": issue_key, "property_key": property_key})
2355
+
2356
+ elif action == "delete":
2357
+ if not yes:
2358
+ raise typer.BadParameter("--yes required for delete")
2359
+ if not property_key:
2360
+ raise typer.BadParameter("--property-key required for delete")
2361
+ adapter.delete_issue_property(issue_key, property_key)
2362
+ _emit_json({"status": "deleted", "issue": issue_key, "property_key": property_key})
2363
+
2364
+ else:
2365
+ raise typer.BadParameter(f"Unknown action: {action}")
2366
+
2367
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
2368
+ code = 1
2369
+ logger.exception("issue_property_command_failed %s", exc)
2370
+ typer.echo(f"Error: {exc}", err=True)
2371
+ finally:
2372
+ finished_at = datetime.now(UTC)
2373
+ _record_summary(
2374
+ command="issue-property",
2375
+ args=summary_args,
2376
+ exit_code=code,
2377
+ started_at=started_at,
2378
+ finished_at=finished_at,
2379
+ )
2380
+ if code != 0:
2381
+ raise typer.Exit(code=code) from None
2382
+
2383
+
2384
+ @app.command("watcher")
2385
+ def cmd_watcher(
2386
+ action: str = typer.Argument(..., help="Action: list, add, remove"),
2387
+ issue_key: str = typer.Option(..., "--issue-key", "-k", help="Issue key (e.g., NTTC-123)"),
2388
+ account: str | None = typer.Option(
2389
+ None,
2390
+ "--account",
2391
+ "-a",
2392
+ help="Username or accountId (required for add/remove)",
2393
+ ),
2394
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operations (add/remove)"),
2395
+ ) -> None:
2396
+ """Manage issue watchers."""
2397
+ started_at = datetime.now(UTC)
2398
+ code = 0
2399
+ summary_args = [action, f"issue={issue_key}", f"account={account}"]
2400
+
2401
+ try:
2402
+ s = JiraSettings()
2403
+ s.require_basic_or_token()
2404
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
2405
+
2406
+ if action == "list":
2407
+ payload = adapter.issue_get_watchers(issue_key)
2408
+ _emit_json(payload)
2409
+
2410
+ elif action == "add":
2411
+ if not yes:
2412
+ raise typer.BadParameter("--yes required for add")
2413
+ if not account:
2414
+ raise typer.BadParameter("--account required for add")
2415
+ adapter.issue_add_watcher(issue_key, account)
2416
+ _emit_json({"status": "added", "issue": issue_key, "account": account})
2417
+
2418
+ elif action == "remove":
2419
+ if not yes:
2420
+ raise typer.BadParameter("--yes required for remove")
2421
+ if not account:
2422
+ raise typer.BadParameter("--account required for remove")
2423
+ adapter.issue_delete_watcher(issue_key, account)
2424
+ _emit_json({"status": "removed", "issue": issue_key, "account": account})
2425
+
2426
+ else:
2427
+ raise typer.BadParameter(f"Unknown action: {action}")
2428
+
2429
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
2430
+ code = 1
2431
+ logger.exception("watcher_command_failed %s", exc)
2432
+ typer.echo(f"Error: {exc}", err=True)
2433
+ finally:
2434
+ finished_at = datetime.now(UTC)
2435
+ _record_summary(
2436
+ command="watcher",
2437
+ args=summary_args,
2438
+ exit_code=code,
2439
+ started_at=started_at,
2440
+ finished_at=finished_at,
2441
+ )
2442
+ if code != 0:
2443
+ raise typer.Exit(code=code) from None
2444
+
2445
+
2446
+ @app.command("security-scheme")
2447
+ def cmd_security_scheme(
2448
+ action: str = typer.Argument(..., help="Action: list, get"),
2449
+ scheme_id: str | None = typer.Option(None, "--scheme-id", help="Scheme ID (for get)"),
2450
+ only_levels: bool = typer.Option(False, "--only-levels", help="Return only security levels (for get)"),
2451
+ ) -> None:
2452
+ """Issue security scheme operations."""
2453
+ started_at = datetime.now(UTC)
2454
+ code = 0
2455
+ try:
2456
+ s = JiraSettings()
2457
+ s.require_basic_or_token()
2458
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
2459
+
2460
+ if action == "list":
2461
+ payload = adapter.get_issue_security_schemes()
2462
+ _emit_json({"count": len(payload), "security_schemes": payload})
2463
+ elif action == "get":
2464
+ if not scheme_id:
2465
+ raise typer.BadParameter("--scheme-id required for get")
2466
+ payload = adapter.get_issue_security_scheme(scheme_id, only_levels=only_levels)
2467
+ _emit_json(payload)
2468
+ else:
2469
+ raise typer.BadParameter(f"Unknown action: {action}")
2470
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
2471
+ code = 1
2472
+ logger.exception("security_scheme_failed %s", exc)
2473
+ typer.echo(f"Error: {exc}", err=True)
2474
+ finally:
2475
+ finished_at = datetime.now(UTC)
2476
+ _record_summary(
2477
+ command="security-scheme",
2478
+ args=[action, f"scheme_id={scheme_id}", f"only_levels={only_levels}"],
2479
+ exit_code=code,
2480
+ started_at=started_at,
2481
+ finished_at=finished_at,
2482
+ )
2483
+ if code != 0:
2484
+ raise typer.Exit(code=code) from None
2485
+
2486
+
2487
+ @app.command("permissions")
2488
+ def cmd_permissions(
2489
+ action: str = typer.Argument(..., help="Action: all, check"),
2490
+ permissions: str | None = typer.Option(None, "--permissions", help="Comma-separated permission names (for check)"),
2491
+ project_key: str | None = typer.Option(None, "--project-key", help="Project key (for check)"),
2492
+ issue_key: str | None = typer.Option(None, "--issue-key", help="Issue key (for check)"),
2493
+ ) -> None:
2494
+ """Permission operations."""
2495
+ started_at = datetime.now(UTC)
2496
+ code = 0
2497
+ try:
2498
+ s = JiraSettings()
2499
+ s.require_basic_or_token()
2500
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
2501
+
2502
+ if action == "all":
2503
+ payload = adapter.get_all_permissions()
2504
+ _emit_json({"count": len(payload), "permissions": payload})
2505
+ elif action == "check":
2506
+ if not permissions:
2507
+ raise typer.BadParameter("--permissions required for check")
2508
+ perms_list = [p.strip() for p in permissions.split(",")]
2509
+ payload = adapter.get_permissions(
2510
+ permissions=perms_list,
2511
+ project_key=project_key,
2512
+ issue_key=issue_key,
2513
+ )
2514
+ _emit_json(payload)
2515
+ else:
2516
+ raise typer.BadParameter(f"Unknown action: {action}")
2517
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
2518
+ code = 1
2519
+ logger.exception("permissions_failed %s", exc)
2520
+ typer.echo(f"Error: {exc}", err=True)
2521
+ finally:
2522
+ finished_at = datetime.now(UTC)
2523
+ _record_summary(
2524
+ command="permissions",
2525
+ args=[action, f"permissions={permissions}", f"project_key={project_key}", f"issue_key={issue_key}"],
2526
+ exit_code=code,
2527
+ started_at=started_at,
2528
+ finished_at=finished_at,
2529
+ )
2530
+ if code != 0:
2531
+ raise typer.Exit(code=code) from None
2532
+
2533
+
2534
+ @app.command("permission-scheme")
2535
+ def cmd_permission_scheme(
2536
+ project_key: str = typer.Argument(..., help="Project key"),
2537
+ expand: str | None = typer.Option(None, "--expand", help="Expand options (comma-separated)"),
2538
+ ) -> None:
2539
+ """Get project permission scheme."""
2540
+ started_at = datetime.now(UTC)
2541
+ code = 0
2542
+ try:
2543
+ s = JiraSettings()
2544
+ s.require_basic_or_token()
2545
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
2546
+ payload = adapter.get_project_permission_scheme(project_key, expand=expand)
2547
+ _emit_json(payload)
2548
+ except (AuthError, VDSClientError) as exc:
2549
+ code = 1
2550
+ logger.exception("permission_scheme_failed %s", exc)
2551
+ typer.echo(f"Error: {exc}", err=True)
2552
+ finally:
2553
+ finished_at = datetime.now(UTC)
2554
+ _record_summary(
2555
+ command="permission-scheme",
2556
+ args=[project_key, f"expand={expand}"],
2557
+ exit_code=code,
2558
+ started_at=started_at,
2559
+ finished_at=finished_at,
2560
+ )
2561
+ if code != 0:
2562
+ raise typer.Exit(code=code) from None
2563
+
2564
+
2565
+ @app.command("issue-security-scheme")
2566
+ def cmd_issue_security_scheme(
2567
+ project_key: str = typer.Argument(..., help="Project key"),
2568
+ only_levels: bool = typer.Option(False, "--only-levels", help="Return only security levels"),
2569
+ ) -> None:
2570
+ """Get project issue security scheme."""
2571
+ started_at = datetime.now(UTC)
2572
+ code = 0
2573
+ try:
2574
+ s = JiraSettings()
2575
+ s.require_basic_or_token()
2576
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
2577
+ payload = adapter.get_project_issue_security_scheme(project_key, only_levels=only_levels)
2578
+ _emit_json(payload)
2579
+ except (AuthError, VDSClientError) as exc:
2580
+ code = 1
2581
+ logger.exception("issue_security_scheme_failed %s", exc)
2582
+ typer.echo(f"Error: {exc}", err=True)
2583
+ finally:
2584
+ finished_at = datetime.now(UTC)
2585
+ _record_summary(
2586
+ command="issue-security-scheme",
2587
+ args=[project_key, f"only_levels={only_levels}"],
2588
+ exit_code=code,
2589
+ started_at=started_at,
2590
+ finished_at=finished_at,
2591
+ )
2592
+ if code != 0:
2593
+ raise typer.Exit(code=code) from None
2594
+
2595
+
2596
+ @app.command("reindex")
2597
+ def cmd_reindex(
2598
+ action: str = typer.Argument(..., help="Action: start, status, with-type"),
2599
+ reindex_type: str | None = typer.Option(None, "--type", help="Reindex type (for with-type)"),
2600
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation"),
2601
+ ) -> None:
2602
+ """Reindex operations."""
2603
+ started_at = datetime.now(UTC)
2604
+ code = 0
2605
+ try:
2606
+ s = JiraSettings()
2607
+ s.require_basic_or_token()
2608
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
2609
+
2610
+ if action == "start":
2611
+ if not yes:
2612
+ raise typer.BadParameter("Refusing to start reindex without --yes")
2613
+ payload = adapter.reindex()
2614
+ _emit_json(payload)
2615
+ elif action == "status":
2616
+ payload = adapter.reindex_status()
2617
+ _emit_json(payload)
2618
+ elif action == "with-type":
2619
+ if not yes:
2620
+ raise typer.BadParameter("Refusing to reindex without --yes")
2621
+ if not reindex_type:
2622
+ raise typer.BadParameter("--type required for with-type")
2623
+ payload = adapter.reindex_with_type(reindex_type)
2624
+ _emit_json(payload)
2625
+ else:
2626
+ raise typer.BadParameter(f"Unknown action: {action}")
2627
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
2628
+ code = 1
2629
+ logger.exception("reindex_failed %s", exc)
2630
+ typer.echo(f"Error: {exc}", err=True)
2631
+ finally:
2632
+ finished_at = datetime.now(UTC)
2633
+ _record_summary(
2634
+ command="reindex",
2635
+ args=[action, f"type={reindex_type}"],
2636
+ exit_code=code,
2637
+ started_at=started_at,
2638
+ finished_at=finished_at,
2639
+ )
2640
+ if code != 0:
2641
+ raise typer.Exit(code=code) from None
2642
+
2643
+
2644
+ @app.command("user")
2645
+ def cmd_user(
2646
+ action: str = typer.Argument(..., help="Action: search, groups, deactivate"),
2647
+ query: str | None = typer.Option(None, "--query", help="Search query (for search)"),
2648
+ account_id: str | None = typer.Option(None, "--account-id", help="Account ID (for groups)"),
2649
+ username: str | None = typer.Option(None, "--username", help="Username (for deactivate)"),
2650
+ start: int = typer.Option(0, "--start", help="Start index for pagination (for search)"),
2651
+ limit: int = typer.Option(50, "--limit", help="Limit for pagination (for search)"),
2652
+ include_inactive: bool = typer.Option(False, "--include-inactive", help="Include inactive users (for search)"),
2653
+ yes: bool = typer.Option(False, "--yes", help="Confirm write operation (for deactivate)"),
2654
+ ) -> None:
2655
+ """User management operations."""
2656
+ started_at = datetime.now(UTC)
2657
+ code = 0
2658
+ try:
2659
+ s = JiraSettings()
2660
+ s.require_basic_or_token()
2661
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
2662
+
2663
+ if action == "search":
2664
+ if not query:
2665
+ raise typer.BadParameter("--query required for search")
2666
+ payload = adapter.find_users(query=query, start=start, limit=limit, include_inactive=include_inactive)
2667
+ _emit_json({"count": len(payload), "users": payload})
2668
+ elif action == "groups":
2669
+ if not account_id:
2670
+ raise typer.BadParameter("--account-id required for groups")
2671
+ payload = adapter.get_user_groups(account_id)
2672
+ _emit_json({"count": len(payload), "groups": payload})
2673
+ elif action == "deactivate":
2674
+ if not yes:
2675
+ raise typer.BadParameter("Refusing to deactivate user without --yes")
2676
+ if not username:
2677
+ raise typer.BadParameter("--username required for deactivate")
2678
+ adapter.deactivate_user(username)
2679
+ _emit_json({"status": "deactivated", "username": username})
2680
+ else:
2681
+ raise typer.BadParameter(f"Unknown action: {action}")
2682
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
2683
+ code = 1
2684
+ logger.exception("user_failed %s", exc)
2685
+ typer.echo(f"Error: {exc}", err=True)
2686
+ finally:
2687
+ finished_at = datetime.now(UTC)
2688
+ _record_summary(
2689
+ command="user",
2690
+ args=[
2691
+ action,
2692
+ f"query={query}",
2693
+ f"account_id={account_id}",
2694
+ f"username={username}",
2695
+ f"start={start}",
2696
+ f"limit={limit}",
2697
+ f"include_inactive={include_inactive}",
2698
+ f"yes={yes}",
2699
+ ],
2700
+ exit_code=code,
2701
+ started_at=started_at,
2702
+ finished_at=finished_at,
2703
+ )
2704
+ if code != 0:
2705
+ raise typer.Exit(code=code) from None
2706
+
2707
+
2708
+ if __name__ == "__main__":
2709
+ app()
2710
+
2711
+
2712
+ @app.command("discover-fields")
2713
+ def cmd_discover_fields(
2714
+ project: str = typer.Option(..., "--project", help="Project key"),
2715
+ issuetype: str = typer.Option(..., "--issuetype", help="Issue type name (e.g., Task, Bug)"),
2716
+ field: str | None = typer.Option(
2717
+ None, "--field", help="Optional: Filter by field name or ID (case-insensitive substring)"
2718
+ ),
2719
+ include_system: bool = typer.Option(False, "--include-system", help="Include system (non-custom) fields"),
2720
+ issue_key: str | None = typer.Option(
2721
+ None, "--issue-key", help="Issue key for editmeta fallback when createmeta is unavailable"
2722
+ ),
2723
+ ) -> None:
2724
+ """Discover custom fields (default), requirements, and allowed values for a project/issuetype."""
2725
+ started_at = datetime.now(UTC)
2726
+ code = 0
2727
+ try:
2728
+ s = JiraSettings()
2729
+ s.require_basic_or_token()
2730
+ adapter = JiraAdapter(url=str(s.base_url), username=s.username, password=s.password, token=s.token)
2731
+
2732
+ raw_fields: dict[str, Any] | None = None
2733
+ editmeta_fields: dict[str, Any] | None = None
2734
+ createmeta_error: Exception | None = None
2735
+
2736
+ try:
2737
+ meta = adapter.get_createmeta(project_key=project, issuetype=issuetype)
2738
+ raw_fields = _extract_createmeta_fields(meta, project, issuetype)
2739
+ except (VDSClientError, typer.BadParameter) as exc:
2740
+ createmeta_error = exc
2741
+
2742
+ if issue_key:
2743
+ try:
2744
+ editmeta = adapter.get_editmeta(issue_key)
2745
+ editmeta_fields = editmeta.get("fields", {})
2746
+ except VDSClientError as exc:
2747
+ if createmeta_error:
2748
+ raise typer.BadParameter(
2749
+ f"Failed to load create metadata for {project}/{issuetype}: {createmeta_error}. "
2750
+ f"Editmeta fallback for {issue_key} also failed: {exc}"
2751
+ ) from exc
2752
+ editmeta_fields = None
2753
+ typer.echo(f"Warning: editmeta fallback unavailable: {exc}", err=True)
2754
+
2755
+ if raw_fields is None or not raw_fields:
2756
+ if editmeta_fields:
2757
+ raw_fields = editmeta_fields
2758
+ elif createmeta_error:
2759
+ raise typer.BadParameter(
2760
+ f"Failed to load create metadata for {project}/{issuetype}: {createmeta_error}"
2761
+ ) from createmeta_error
2762
+ elif editmeta_fields:
2763
+ _merge_allowed_values_from_editmeta(raw_fields, editmeta_fields)
2764
+
2765
+ if raw_fields is None:
2766
+ if createmeta_error:
2767
+ raise createmeta_error
2768
+ raw_fields = {}
2769
+
2770
+ fields = _collect_fields(raw_fields, field_filter=field, include_system=include_system)
2771
+
2772
+ duration_ms = int((datetime.now(UTC) - started_at).total_seconds() * 1000)
2773
+ output = {
2774
+ "project": project,
2775
+ "issueType": issuetype,
2776
+ "count": len(fields),
2777
+ "duration_ms": duration_ms,
2778
+ "fields": sorted(fields, key=lambda x: (not x["required"], x["name"])),
2779
+ }
2780
+
2781
+ if (len(fields) <= 50 and duration_ms > 2000) or (len(fields) <= 200 and duration_ms > 5000):
2782
+ logger.warning("discover_fields_slow duration_ms=%s count=%s", duration_ms, len(fields))
2783
+
2784
+ _emit_json(output)
2785
+
2786
+ except (AuthError, VDSClientError, typer.BadParameter) as exc:
2787
+ code = 1
2788
+ logger.exception("discover_fields_failed %s", exc)
2789
+ typer.echo(f"Error: {exc}", err=True)
2790
+ finally:
2791
+ finished_at = datetime.now(UTC)
2792
+ _record_summary(
2793
+ command="discover-fields",
2794
+ args=[
2795
+ f"project={project}",
2796
+ f"issuetype={issuetype}",
2797
+ f"field={field}",
2798
+ f"include_system={include_system}",
2799
+ f"issue_key={issue_key}",
2800
+ ],
2801
+ exit_code=code,
2802
+ started_at=started_at,
2803
+ finished_at=finished_at,
2804
+ )
2805
+ if code != 0:
2806
+ raise typer.Exit(code=code) from None