bmsdna-devtools 0.43.0__tar.gz → 0.44.0__tar.gz

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 (132) hide show
  1. bmsdna_devtools-0.43.0/README.md → bmsdna_devtools-0.44.0/PKG-INFO +95 -54
  2. bmsdna_devtools-0.43.0/PKG-INFO → bmsdna_devtools-0.44.0/README.md +77 -71
  3. bmsdna_devtools-0.44.0/bmsdna/devtools/ado_auth.py +28 -0
  4. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/app_service_logs.py +38 -16
  5. bmsdna_devtools-0.44.0/bmsdna/devtools/azure_arm.py +98 -0
  6. bmsdna_devtools-0.44.0/bmsdna/devtools/azure_auth.py +30 -0
  7. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/cli.py +118 -100
  8. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/cli_tools.py +5 -8
  9. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/find_repo.py +87 -46
  10. bmsdna_devtools-0.44.0/bmsdna/devtools/gh_args.py +72 -0
  11. bmsdna_devtools-0.44.0/bmsdna/devtools/gh_issue.py +719 -0
  12. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/gh_pr.py +421 -175
  13. bmsdna_devtools-0.44.0/bmsdna/devtools/github_api.py +180 -0
  14. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/issue_do.py +4 -6
  15. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/logs.py +39 -45
  16. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/pr_build.py +174 -32
  17. bmsdna_devtools-0.44.0/bmsdna/devtools/pr_create_args.py +281 -0
  18. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/pyproject.toml +2 -1
  19. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/skills/bmsdna-devtools/SKILL.md +16 -15
  20. bmsdna_devtools-0.44.0/tests/fake_github.py +78 -0
  21. bmsdna_devtools-0.44.0/tests/test_ado_auth.py +71 -0
  22. bmsdna_devtools-0.44.0/tests/test_app_service_logs.py +152 -0
  23. bmsdna_devtools-0.44.0/tests/test_azure_arm.py +136 -0
  24. bmsdna_devtools-0.44.0/tests/test_cli_pr_create.py +461 -0
  25. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_cli_pr_id.py +1 -1
  26. bmsdna_devtools-0.44.0/tests/test_cli_version.py +16 -0
  27. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_find_repo.py +218 -104
  28. bmsdna_devtools-0.44.0/tests/test_gh_issue.py +722 -0
  29. bmsdna_devtools-0.44.0/tests/test_gh_pr.py +1320 -0
  30. bmsdna_devtools-0.44.0/tests/test_github_api.py +180 -0
  31. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_issue_do.py +7 -0
  32. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_issue_for_me.py +52 -52
  33. bmsdna_devtools-0.44.0/tests/test_logs.py +162 -0
  34. bmsdna_devtools-0.44.0/tests/test_pr_build_create_options.py +166 -0
  35. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_pr_choice.py +42 -27
  36. bmsdna_devtools-0.44.0/tests/test_pr_create_args.py +152 -0
  37. bmsdna_devtools-0.44.0/tests/test_pr_description_not_truncated.py +200 -0
  38. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_pr_info.py +21 -16
  39. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/uv.lock +203 -1
  40. bmsdna_devtools-0.43.0/bmsdna/devtools/ado_auth.py +0 -44
  41. bmsdna_devtools-0.43.0/bmsdna/devtools/gh_issue.py +0 -541
  42. bmsdna_devtools-0.43.0/tests/test_ado_auth.py +0 -62
  43. bmsdna_devtools-0.43.0/tests/test_app_service_logs.py +0 -39
  44. bmsdna_devtools-0.43.0/tests/test_cli_pr_create.py +0 -569
  45. bmsdna_devtools-0.43.0/tests/test_gh_issue.py +0 -611
  46. bmsdna_devtools-0.43.0/tests/test_gh_pr.py +0 -1072
  47. bmsdna_devtools-0.43.0/tests/test_pr_description_not_truncated.py +0 -138
  48. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/.github/workflows/auto-release.yml +0 -0
  49. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/.github/workflows/python-publish.yml +0 -0
  50. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/.github/workflows/python-test.yml +0 -0
  51. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/.gitignore +0 -0
  52. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/.python-version +0 -0
  53. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/__init__.py +0 -0
  54. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/_api_dump.py +0 -0
  55. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/ado_issue.py +0 -0
  56. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/agent_mentions.py +0 -0
  57. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/api_usage.py +0 -0
  58. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/bdt_config.py +0 -0
  59. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/commit.py +0 -0
  60. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/dead_code.py +0 -0
  61. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/env_config.py +0 -0
  62. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/env_files.py +0 -0
  63. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/find_injection.py +0 -0
  64. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/gitrepo.py +0 -0
  65. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/injection_frontend.py +0 -0
  66. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/injection_python.py +0 -0
  67. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/issue_show.py +0 -0
  68. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/issue_watch.py +0 -0
  69. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/lint.py +0 -0
  70. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/lint_findings.py +0 -0
  71. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/lint_models.py +0 -0
  72. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/lint_sql.py +0 -0
  73. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/lint_sql_files.py +0 -0
  74. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/lint_tooling.py +0 -0
  75. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/lint_typescript.py +0 -0
  76. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/pr_choice.py +0 -0
  77. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/pr_info.py +0 -0
  78. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/pr_issue_link.py +0 -0
  79. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/pr_labels.py +0 -0
  80. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/pr_markdown.py +0 -0
  81. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/pull.py +0 -0
  82. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/testdb.py +0 -0
  83. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/translate.py +0 -0
  84. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/usage/__init__.py +0 -0
  85. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/usage/cli.py +0 -0
  86. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/usage/pipeline.py +0 -0
  87. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/usage/pricing.py +0 -0
  88. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/usage/report.py +0 -0
  89. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/usage/sql/00_schema.sql +0 -0
  90. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/usage/sql/01_ingest_claude.sql +0 -0
  91. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/usage/sql/01_ingest_codex.sql +0 -0
  92. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/usage/sql/02_messages.sql +0 -0
  93. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/usage/sql/03_markers.sql +0 -0
  94. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/usage/sql/04_facts.sql +0 -0
  95. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/usage/sql/05_views.sql +0 -0
  96. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/bmsdna/devtools/worktree.py +0 -0
  97. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/justfile +0 -0
  98. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/conftest.py +0 -0
  99. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_ado_issue.py +0 -0
  100. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_ado_issue_flow.py +0 -0
  101. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_agent_mentions.py +0 -0
  102. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_api_usage.py +0 -0
  103. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_bdt_config.py +0 -0
  104. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_cli_cleanup.py +0 -0
  105. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_cli_commit.py +0 -0
  106. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_cli_issue.py +0 -0
  107. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_cli_pull.py +0 -0
  108. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_cli_tools.py +0 -0
  109. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_commit.py +0 -0
  110. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_env_config.py +0 -0
  111. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_env_files.py +0 -0
  112. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_find_injection.py +0 -0
  113. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_gitrepo.py +0 -0
  114. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_issue_show.py +0 -0
  115. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_issue_watch.py +0 -0
  116. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_lint.py +0 -0
  117. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_lint_models.py +0 -0
  118. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_lint_sql.py +0 -0
  119. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_lint_sql_files.py +0 -0
  120. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_lint_tooling.py +0 -0
  121. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_lint_typescript.py +0 -0
  122. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_pr_build.py +0 -0
  123. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_pr_build_flow.py +0 -0
  124. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_pr_issue_link.py +0 -0
  125. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_pr_labels.py +0 -0
  126. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_pr_markdown.py +0 -0
  127. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_pull.py +0 -0
  128. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_testdb.py +0 -0
  129. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_translate.py +0 -0
  130. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_usage.py +0 -0
  131. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_wait_timeout.py +0 -0
  132. {bmsdna_devtools-0.43.0 → bmsdna_devtools-0.44.0}/tests/test_worktree_clean.py +0 -0
@@ -1,3 +1,21 @@
1
+ Metadata-Version: 2.5
2
+ Name: bmsdna-devtools
3
+ Version: 0.44.0
4
+ Summary: Shared Azure DevOps / GitHub / git / Azure Monitor developer tooling for BMS projects
5
+ Requires-Python: >=3.14
6
+ Requires-Dist: azure-identity>=1.19.0
7
+ Requires-Dist: pgdevkit[db]>=0.7.1
8
+ Requires-Dist: requests>=2.32.0
9
+ Requires-Dist: rich>=13.0.0
10
+ Requires-Dist: tomli-w>=1.0.0
11
+ Requires-Dist: typer>=0.26.7
12
+ Provides-Extra: lint
13
+ Requires-Dist: sqlglot>=27.0.0; extra == 'lint'
14
+ Provides-Extra: usage
15
+ Requires-Dist: duckdb>=1.3.0; extra == 'usage'
16
+ Requires-Dist: plotly>=5.24.0; extra == 'usage'
17
+ Description-Content-Type: text/markdown
18
+
1
19
  # bmsdna-devtools
2
20
 
3
21
  Shared developer tooling for BMS projects: PR build/check status and a
@@ -8,16 +26,24 @@ postgres/psycopg (and arrowbricks) SQL rules, pydantic-model placement, hand-wir
8
26
  TypeScript and baseline tooling, plus `bdt dead-code` for `.sql` files nothing loads and
9
27
  backend routes nothing calls.
10
28
  `bdt pr *` and `bdt issue *` auto-detect whether the current repo's `origin`
11
- remote is Azure DevOps or GitHub and use `az`/`gh` accordingly.
29
+ remote is Azure DevOps or GitHub and call the matching REST API.
12
30
  Consolidates near-duplicate scripts that used to be copy-pasted across
13
31
  OneSales, ccmt2, and MDMApp into one versioned package with a `bdt` CLI.
14
32
 
15
- Requires `git` always, plus `az` (Azure DevOps commands, and all `bdt logs`
16
- commands) and/or `gh` (GitHub commands) on PATH as needed — each is checked
17
- lazily, only when a command actually needs it, with a clear error and an
18
- install link if missing rather than a raw traceback. Works on Windows: CLI
19
- shims (e.g. `az.cmd`) are resolved via `shutil.which` (which honors
20
- `PATHEXT`) rather than shelling out, output is decoded as UTF-8 rather than
33
+ Requires only `git` on PATH (plus `psql` for `bdt testdb`); the `az` and `gh` CLIs are
34
+ **not** needed — everything talks to GitHub (REST + GraphQL), Azure DevOps and Azure
35
+ (ARM / Application Insights) over raw HTTP (`requests`).
36
+
37
+ Auth:
38
+ - **GitHub**: the `GH_TOKEN` / `GITHUB_TOKEN` environment variable, else (if `gh` happens to be
39
+ installed) the output of `gh auth token`. The token needs the `repo` scope, plus `project` for
40
+ boards and `workflow` for reruns.
41
+ - **Azure DevOps**: `--pat` / `AZURE_DEVOPS_EXT_PAT` / `AZURE_DEVOPS_PAT`, else azure-identity's
42
+ `DefaultAzureCredential` (environment variables, managed identity, a cached `az login`, ...).
43
+ - **Azure (`bdt logs *`)**: the same `DefaultAzureCredential`, against ARM / Application Insights;
44
+ `AZURE_SUBSCRIPTION_ID` selects the subscription, else the only enabled one is used.
45
+
46
+ Works on Windows: output is decoded as UTF-8 rather than
21
47
  relying on the console's default codepage, and file arguments accept either
22
48
  slash style.
23
49
 
@@ -76,17 +102,16 @@ polling forever (a failed policy lookup is ignored and the wait continues).
76
102
  **Azure DevOps**: org/project/repo are auto-detected from
77
103
  `git remote get-url origin` (handles SSH, `dev.azure.com` HTTPS, and
78
104
  `*.visualstudio.com` HTTPS forms). Auth is an explicit PAT (`--pat` or
79
- `AZURE_DEVOPS_EXT_PAT`/`AZURE_DEVOPS_PAT` env var), falling back to a short-lived token from the
80
- caller's own `az login` — never embed a PAT literal in a script or CI file.
105
+ `AZURE_DEVOPS_EXT_PAT`/`AZURE_DEVOPS_PAT` env var), falling back to a short-lived token from
106
+ azure-identity's `DefaultAzureCredential` (e.g. the caller's own `az login`) — never embed a PAT literal in a script or CI file.
81
107
  `--target-branch` optionally restricts the lookup to PRs into that branch (default: any).
82
108
 
83
- **GitHub**: uses `gh`'s own auth (`gh auth login`) and resolves the open
84
- PR opened from the current branch (`gh pr list --head`, narrowed by
85
- `--target-branch` to that base branch if given; with none open, `gh pr view`
86
- finds a merged/closed one); the PR's actual base branch
87
- is shown in the output. Check status is computed from
88
- `gh pr view --json statusCheckRollup` rather than `gh pr checks --json`,
89
- since the latter flag isn't available in all `gh` releases.
109
+ **GitHub**: uses the `GH_TOKEN`/`GITHUB_TOKEN` auth above and resolves the open
110
+ PR opened from the current branch (REST pulls filtered by head, narrowed by
111
+ `--target-branch` to that base branch if given; with none open, a merged/closed one
112
+ is looked up); the PR's actual base branch
113
+ is shown in the output. Check status is computed from the PR's
114
+ `statusCheckRollup`, fetched via GraphQL.
90
115
 
91
116
  Pass `--pr-id <number>` to check a specific PR directly instead of resolving
92
117
  it from the current branch — useful when that PR's branch isn't checked out
@@ -135,7 +160,7 @@ anything depending on them) in place — distinct from queuing a brand new
135
160
  build. One retry call per pipeline that has a failed build for the PR.
136
161
  `--target-branch` selects which PR to look at, same as `bdt pr status`.
137
162
 
138
- **GitHub**: uses `gh run rerun <run-id> --failed`, which reruns only the
163
+ **GitHub**: POSTs `actions/runs/<run-id>/rerun-failed-jobs`, which reruns only the
139
164
  failed job(s) (and their dependents) of a workflow run — the run ID(s) are
140
165
  found automatically from the PR's failing checks. Checks that aren't backed
141
166
  by a GitHub Actions run (e.g. a legacy commit status from an external CI)
@@ -166,10 +191,10 @@ skipped rather than blocking or crashing `pr status`.
166
191
  `pr status` uses, just queried by `branchName=refs/heads/<target-branch>`
167
192
  instead of the PR's merge/source ref.
168
193
 
169
- **GitHub**: uses `gh run list --branch <target-branch> --event push`, which
194
+ **GitHub**: lists workflow runs via REST filtered by `branch=<target-branch>&event=push`, which
170
195
  selects workflow runs triggered by a push to that branch — as opposed to a
171
196
  `pull_request`-triggered run for some still-open PR targeting the same
172
- branch. Failed steps are printed via `gh run view <id> --log-failed`.
197
+ branch. Failed steps are printed as the tail of each failed job's log.
173
198
 
174
199
  ## `bdt pr create`
175
200
 
@@ -178,17 +203,34 @@ bdt pr create --target dev # or --target main / test
178
203
  ```
179
204
 
180
205
  Creates a PR from the current branch into `--target`. On Azure DevOps,
181
- a thin wrapper around `az repos pr create` (org/project/repo inferred by
182
- `az` itself from the git remote). On GitHub, `gh pr create --fill` (autofills
183
- title/body from commit info so it never blocks on an interactive prompt).
184
- Extra arguments pass through either way, e.g.
185
- `bdt pr create --target dev -- --reviewers "..."`.
206
+ a REST POST to `pullrequests` (org/project/repo inferred from the git remote). On GitHub,
207
+ a REST POST to `pulls`; title/body default to the branch's commit(s) so it never blocks on an
208
+ interactive prompt.
209
+
210
+ Extra arguments after `--` take the union of `gh pr create` and `az repos pr create` spellings on **both**
211
+ hosts (`--opt value` and `--opt=value`; list flags take every value up to the next option, comma-separated
212
+ values work too; boolean flags take an optional `true`/`false`):
213
+
214
+ | Meaning | Flags |
215
+ |---|---|
216
+ | title / body | `--title`/`-t`, `--body`/`--description`/`-b` (`-d` on Azure DevOps), `--body-file`/`-F` (`-` = stdin) |
217
+ | branches | `--base`/`-B`/`--target-branch`, `--head`/`-H`/`--source-branch`/`-s` |
218
+ | draft | `--draft [true\|false]` (`-d` on GitHub) |
219
+ | labels / reviewers | `--label`/`--labels`/`-l`, `--reviewer`/`--reviewers` (`-r` on GitHub), `--required-reviewers` |
220
+ | work items / issues | `--work-items` (same as `--issue`: linked and tagged `pr-available`) |
221
+ | merge | `--auto-complete`/`--auto-merge`, `--squash`, `--delete-source-branch`, `--merge-commit-message`, `--bypass-policy`, `--bypass-policy-reason`, `--transition-work-items` |
222
+ | other | `--assignee`/`-a`, `--milestone`/`-m`, `--project`/`-p` (GitHub board), `--fill`/`-f`, `--web`/`-w`/`--open`, `--dry-run` |
223
+ | Azure DevOps target | `--organization`/`--org`, `--project`/`-p`, `--repository`/`-r` override the remote-derived values |
224
+ | accepted, ignored | `--detect`, `--verbose`, `--debug`, `--subscription`, `--output`/`-o`, `--query` |
225
+
226
+ Azure DevOps resolves reviewers (GUID, e-mail or display name) through the Identities API, sets
227
+ `--auto-complete` as the current user and applies the merge options in a follow-up update, as `az` does.
228
+ A flag the host has no counterpart for (e.g. `--milestone` on Azure DevOps, `--delete-source-branch` on
229
+ GitHub) is reported on stderr (`Note: --milestone is ignored on Azure DevOps`), never dropped silently.
230
+ Unknown flags fail with the list of supported ones.
186
231
 
187
232
  Pass the text with `--title "..."` and `--description "$(cat pr.md)"` rather than as passthrough
188
- args: it is kept in full. On GitHub the description goes to `gh` on stdin (`--body-file -`); on
189
- Azure DevOps it is set over the REST API right after `az` creates the PR, because
190
- `az repos pr create --description` has been seen cutting long text at ~450 characters (a `--title`/
191
- `--description` given after `--` is intercepted the same way). Azure DevOps itself allows at most
233
+ args: the description is always sent in full. Azure DevOps itself allows at most
192
234
  4000 characters in a PR description: a longer one is rejected up front, and a description the
193
235
  server stored shorter than sent is reported as a warning (`bdt pr update` does the same check).
194
236
 
@@ -198,15 +240,15 @@ it ready for review. Pass `--no-draft` to open it ready for review
198
240
  immediately instead.
199
241
 
200
242
  `--label` (repeatable) applies labels to the PR on either host: on GitHub
201
- these map to `gh pr create --label`, so the label must already exist on the
202
- repo (`gh label create`); on Azure DevOps they map to `az repos pr create
203
- --labels`, which are freeform and get created on the fly. A successful
204
- create prints the PR's web/GUI link (not just the REST API URL Azure DevOps'
205
- `az` output otherwise gives you).
243
+ these are applied to the PR, and unknown labels are created automatically
244
+ by GitHub (no pre-existing label required); on Azure DevOps labels are freeform and get
245
+ created on the fly. A successful
246
+ create prints the PR's web/GUI link (not just the REST API URL Azure DevOps
247
+ returns).
206
248
 
207
249
  `[tool.bdt.pr.required_labels]` in pyproject.toml can require at least one
208
250
  label from each named group before the PR is created — checked locally
209
- (no `gh`/`az` call happens if a group isn't satisfied):
251
+ (no API call happens if a group isn't satisfied):
210
252
 
211
253
  ```toml
212
254
  [tool.bdt.pr.required_labels]
@@ -336,27 +378,26 @@ On `update`, `--board` only moves the item when you pass it explicitly — it
336
378
  never falls back to `pyproject.toml`, so an unrelated field update (e.g.
337
379
  just `--title`) can't silently relocate the item to a different board.
338
380
 
339
- **GitHub**: a thin wrapper around `gh issue create` / `edit` / `delete` /
340
- `comment`. `--label`/`--tag` add a label on `create`, or add/remove one on
381
+ **GitHub**: REST/GraphQL calls for issue create / edit / delete /
382
+ comment. `--label`/`--tag` add a label on `create`, or add/remove one on
341
383
  `update` (paired with `--remove-label`/`--remove-tag`); labels must already
342
384
  exist in the repo. `--screenshot` pushes images to a `pr-assets` branch (same trick `bdt
343
385
  pr create --screenshot` uses, since GitHub has no API for uploading an image
344
386
  into an issue) and appends them to the issue body / comment as Markdown.
345
387
  `issue delete` is **permanent** — GitHub has no recycle bin for issues.
346
- Comment update/delete go through `gh api` directly (`gh issue` has no
347
- subcommand for editing/deleting an arbitrary comment by ID). Extra arguments
348
- to `bdt issue create` pass through to `gh issue create`, e.g.
349
- `bdt issue create --title "..." -- --assignee @me`.
350
-
351
- `--parent` / `--blocked-by` use GitHub's sub-issues and issue dependencies (`gh issue create --parent` /
352
- `--blocked-by`, `gh issue edit --parent` / `--add-blocked-by`), which need a recent `gh`; `issue show` reads
353
- them with `gh issue view --json parent,subIssues,blockedBy,blocking`. `--iteration` isn't supported: a
388
+ Comment update/delete go through REST. Extra arguments
389
+ to `bdt issue create` accept the `gh issue create` flags `--title`/`-t`, `--body`/`-b`, `--body-file`/`-F`
390
+ (`-` = stdin), `--label`/`-l`, `--assignee`/`-a` (`@me` works), `--milestone`/`-m` (title or number),
391
+ `--project`/`-p` (Projects board, repeatable) and `--web`/`-w`; `--template` / `--recover` are rejected. E.g.
392
+ `bdt issue create --title "..." -- --assignee @me --project Roadmap`.
393
+
394
+ `--parent` / `--blocked-by` use GitHub's sub-issues and issue dependencies REST endpoints; `issue show` reads
395
+ the parent, sub-issues, blocked-by and blocking links the same way. `--iteration` isn't supported: a
354
396
  GitHub Projects (v2) iteration is a per-project field, not a property of the issue.
355
397
 
356
398
  `--board <name>` on `create` adds the issue to that GitHub Projects (v2)
357
- board by title (needs a token with the `project` scope — `gh auth refresh -s
358
- project`). On `search`, `--board` (title or number) scopes results to issues
359
- currently on that board; since `gh issue list` has no board filter of its
399
+ board by title (needs a token with the `project` scope). On `search`, `--board` (title or number) scopes results to issues
400
+ currently on that board; since the issue list has no board filter of its
360
401
  own, this fetches a wider raw pool and filters it down client-side, so a
361
402
  board with few matches may return fewer results than `--limit` asks for. A
362
403
  CLI flag beats `[tool.bdt.github].board` in `pyproject.toml`:
@@ -392,7 +433,7 @@ Claims an issue (GitHub) or work item (Azure DevOps): comments `Taken by <you>`
392
433
  running coding agent's session (the same `<Agent> Session: <id>` note every bdt comment gets: a
393
434
  claude.ai link under a bridged Claude session, otherwise the bare session id; nothing outside an
394
435
  agent), so others can see who is on it. `<you>` is the GitHub login
395
- `gh` is authenticated as, else the git `user.name`. Without `NUMBER` it takes the issue the current
436
+ the token authenticates as, else the git `user.name`. Without `NUMBER` it takes the issue the current
396
437
  branch's PR closes (`Fixes #N`, or an issue/work-item URL in the PR body); it refuses to guess if
397
438
  that is none or several. If the issue's newest comment already is a "Taken by ..." claim (by anyone) it
398
439
  says so and posts nothing, so it's safe to run repeatedly -- including before `bdt issue do`.
@@ -413,7 +454,7 @@ addresses the calling agent. It combines with every other `search` filter (keywo
413
454
  the agent that is running `bdt`, or `@Agent`, which addresses every agent. Matching is case-insensitive and
414
455
  on whole words: `@claude`, `@CLAUDE,` and `@Claude.` match, `@Claudette`, `@claude-bot` and `me@claude.ai` do not.
415
456
  Only the newest comment counts, so once someone (including the agent, e.g. with a reply) comments after the
416
- mention, the issue drops out of the list. The newest comment isn't part of the list output of `gh`/Azure DevOps,
457
+ mention, the issue drops out of the list. The newest comment isn't part of the list output of GitHub/Azure DevOps,
417
458
  so this costs one extra request per candidate; candidates are scanned in recently-updated order (up to 10x
418
459
  `--limit` on GitHub, 200 on Azure DevOps) until `--limit` have matched.
419
460
 
@@ -544,10 +585,10 @@ Configuration (flags override env vars):
544
585
  | Local work dir | `--root` | `AZDO_WORK_DIR`, `BMS_WORK_DIR` | `~/projects` (`C:/Projects` on Windows) |
545
586
  | Azure DevOps org | `--org` | `AZDO_ORG`, `BMS_ORG` | none (ADO not searched) |
546
587
  | GitHub org | `--github-org` | `GITHUB_ORG`, `BMS_GITHUB_ORG` | none (GitHub not searched) |
547
- | ADO PAT for cloning | `--pat` | `AZURE_DEVOPS_EXT_PAT`, `AZURE_DEVOPS_PAT` | falls back to `az` login |
588
+ | ADO PAT for cloning | `--pat` | `AZURE_DEVOPS_EXT_PAT`, `AZURE_DEVOPS_PAT` | falls back to `DefaultAzureCredential` |
548
589
 
549
590
  Set the org env vars in your shell profile to make `bdt find-repo <name>` work anywhere. It replaces
550
- the `cross-repo-discovery` skill's `ALL_REPOS.md` sync. Needs `az` (ADO) / `gh` (GitHub) only when
591
+ the `cross-repo-discovery` skill's `ALL_REPOS.md` sync. Needs ADO auth / a GitHub token only when
551
592
  those orgs are searched.
552
593
 
553
594
  ## `bdt cleanup worktrees` / `bdt cleanup orphaned-dbs` / `bdt cleanup db` / `bdt cleanup worktree`
@@ -702,7 +743,7 @@ sandbox mode too.
702
743
  ## `bdt logs roles` / `bdt logs tail`
703
744
 
704
745
  Query Application Insights (KQL over `traces`/`exceptions`) via
705
- `az monitor app-insights query`. No defaults are baked in — pass
746
+ the Application Insights REST API (azure-identity credential). No defaults are baked in — pass
706
747
  `--resource-group`/`--app-insights` explicitly (or set
707
748
  `AZURE_RESOURCE_GROUP`/`AZURE_APP_INSIGHTS`), since which Azure resource
708
749
  "this repo" maps to isn't derivable from the git remote.
@@ -715,7 +756,7 @@ bdt logs tail --resource-group my-rg --app-insights my-app-insights --role my-se
715
756
  ## `bdt logs fetch`
716
757
 
717
758
  Downloads the App Service log archive for a webapp/slot via
718
- `az webapp log download`, unzips it, and writes every line matching a
759
+ the App Service Kudu/ARM log download, unzips it, and writes every line matching a
719
760
  common error/warning marker (`ERROR`, `CRITICAL`, `WARNING`, tracebacks,
720
761
  `4xx`/`5xx`, `FAILED`, `FATAL`) to `<out>/<slot>_errors.log`. Simpler and
721
762
  often preferable to the KQL commands above when you just want "what broke
@@ -723,7 +764,7 @@ recently" rather than a queryable trace stream.
723
764
 
724
765
  The webapp/resource-group/slot come from a named environment configured in
725
766
  the calling repo's `pyproject.toml`. `slot` is optional — omit it for an
726
- app's default/production slot (no `--slot` is passed to `az`); set it for a
767
+ app's default/production slot (no slot is used); set it for a
727
768
  named deployment slot:
728
769
 
729
770
  ```toml
@@ -1,20 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: bmsdna-devtools
3
- Version: 0.43.0
4
- Summary: Shared Azure DevOps / GitHub / git / Azure Monitor developer tooling for BMS projects
5
- Requires-Python: >=3.14
6
- Requires-Dist: pgdevkit[db]>=0.7.1
7
- Requires-Dist: requests>=2.32.0
8
- Requires-Dist: rich>=13.0.0
9
- Requires-Dist: tomli-w>=1.0.0
10
- Requires-Dist: typer>=0.26.7
11
- Provides-Extra: lint
12
- Requires-Dist: sqlglot>=27.0.0; extra == 'lint'
13
- Provides-Extra: usage
14
- Requires-Dist: duckdb>=1.3.0; extra == 'usage'
15
- Requires-Dist: plotly>=5.24.0; extra == 'usage'
16
- Description-Content-Type: text/markdown
17
-
18
1
  # bmsdna-devtools
19
2
 
20
3
  Shared developer tooling for BMS projects: PR build/check status and a
@@ -25,16 +8,24 @@ postgres/psycopg (and arrowbricks) SQL rules, pydantic-model placement, hand-wir
25
8
  TypeScript and baseline tooling, plus `bdt dead-code` for `.sql` files nothing loads and
26
9
  backend routes nothing calls.
27
10
  `bdt pr *` and `bdt issue *` auto-detect whether the current repo's `origin`
28
- remote is Azure DevOps or GitHub and use `az`/`gh` accordingly.
11
+ remote is Azure DevOps or GitHub and call the matching REST API.
29
12
  Consolidates near-duplicate scripts that used to be copy-pasted across
30
13
  OneSales, ccmt2, and MDMApp into one versioned package with a `bdt` CLI.
31
14
 
32
- Requires `git` always, plus `az` (Azure DevOps commands, and all `bdt logs`
33
- commands) and/or `gh` (GitHub commands) on PATH as needed — each is checked
34
- lazily, only when a command actually needs it, with a clear error and an
35
- install link if missing rather than a raw traceback. Works on Windows: CLI
36
- shims (e.g. `az.cmd`) are resolved via `shutil.which` (which honors
37
- `PATHEXT`) rather than shelling out, output is decoded as UTF-8 rather than
15
+ Requires only `git` on PATH (plus `psql` for `bdt testdb`); the `az` and `gh` CLIs are
16
+ **not** needed — everything talks to GitHub (REST + GraphQL), Azure DevOps and Azure
17
+ (ARM / Application Insights) over raw HTTP (`requests`).
18
+
19
+ Auth:
20
+ - **GitHub**: the `GH_TOKEN` / `GITHUB_TOKEN` environment variable, else (if `gh` happens to be
21
+ installed) the output of `gh auth token`. The token needs the `repo` scope, plus `project` for
22
+ boards and `workflow` for reruns.
23
+ - **Azure DevOps**: `--pat` / `AZURE_DEVOPS_EXT_PAT` / `AZURE_DEVOPS_PAT`, else azure-identity's
24
+ `DefaultAzureCredential` (environment variables, managed identity, a cached `az login`, ...).
25
+ - **Azure (`bdt logs *`)**: the same `DefaultAzureCredential`, against ARM / Application Insights;
26
+ `AZURE_SUBSCRIPTION_ID` selects the subscription, else the only enabled one is used.
27
+
28
+ Works on Windows: output is decoded as UTF-8 rather than
38
29
  relying on the console's default codepage, and file arguments accept either
39
30
  slash style.
40
31
 
@@ -93,17 +84,16 @@ polling forever (a failed policy lookup is ignored and the wait continues).
93
84
  **Azure DevOps**: org/project/repo are auto-detected from
94
85
  `git remote get-url origin` (handles SSH, `dev.azure.com` HTTPS, and
95
86
  `*.visualstudio.com` HTTPS forms). Auth is an explicit PAT (`--pat` or
96
- `AZURE_DEVOPS_EXT_PAT`/`AZURE_DEVOPS_PAT` env var), falling back to a short-lived token from the
97
- caller's own `az login` — never embed a PAT literal in a script or CI file.
87
+ `AZURE_DEVOPS_EXT_PAT`/`AZURE_DEVOPS_PAT` env var), falling back to a short-lived token from
88
+ azure-identity's `DefaultAzureCredential` (e.g. the caller's own `az login`) — never embed a PAT literal in a script or CI file.
98
89
  `--target-branch` optionally restricts the lookup to PRs into that branch (default: any).
99
90
 
100
- **GitHub**: uses `gh`'s own auth (`gh auth login`) and resolves the open
101
- PR opened from the current branch (`gh pr list --head`, narrowed by
102
- `--target-branch` to that base branch if given; with none open, `gh pr view`
103
- finds a merged/closed one); the PR's actual base branch
104
- is shown in the output. Check status is computed from
105
- `gh pr view --json statusCheckRollup` rather than `gh pr checks --json`,
106
- since the latter flag isn't available in all `gh` releases.
91
+ **GitHub**: uses the `GH_TOKEN`/`GITHUB_TOKEN` auth above and resolves the open
92
+ PR opened from the current branch (REST pulls filtered by head, narrowed by
93
+ `--target-branch` to that base branch if given; with none open, a merged/closed one
94
+ is looked up); the PR's actual base branch
95
+ is shown in the output. Check status is computed from the PR's
96
+ `statusCheckRollup`, fetched via GraphQL.
107
97
 
108
98
  Pass `--pr-id <number>` to check a specific PR directly instead of resolving
109
99
  it from the current branch — useful when that PR's branch isn't checked out
@@ -152,7 +142,7 @@ anything depending on them) in place — distinct from queuing a brand new
152
142
  build. One retry call per pipeline that has a failed build for the PR.
153
143
  `--target-branch` selects which PR to look at, same as `bdt pr status`.
154
144
 
155
- **GitHub**: uses `gh run rerun <run-id> --failed`, which reruns only the
145
+ **GitHub**: POSTs `actions/runs/<run-id>/rerun-failed-jobs`, which reruns only the
156
146
  failed job(s) (and their dependents) of a workflow run — the run ID(s) are
157
147
  found automatically from the PR's failing checks. Checks that aren't backed
158
148
  by a GitHub Actions run (e.g. a legacy commit status from an external CI)
@@ -183,10 +173,10 @@ skipped rather than blocking or crashing `pr status`.
183
173
  `pr status` uses, just queried by `branchName=refs/heads/<target-branch>`
184
174
  instead of the PR's merge/source ref.
185
175
 
186
- **GitHub**: uses `gh run list --branch <target-branch> --event push`, which
176
+ **GitHub**: lists workflow runs via REST filtered by `branch=<target-branch>&event=push`, which
187
177
  selects workflow runs triggered by a push to that branch — as opposed to a
188
178
  `pull_request`-triggered run for some still-open PR targeting the same
189
- branch. Failed steps are printed via `gh run view <id> --log-failed`.
179
+ branch. Failed steps are printed as the tail of each failed job's log.
190
180
 
191
181
  ## `bdt pr create`
192
182
 
@@ -195,17 +185,34 @@ bdt pr create --target dev # or --target main / test
195
185
  ```
196
186
 
197
187
  Creates a PR from the current branch into `--target`. On Azure DevOps,
198
- a thin wrapper around `az repos pr create` (org/project/repo inferred by
199
- `az` itself from the git remote). On GitHub, `gh pr create --fill` (autofills
200
- title/body from commit info so it never blocks on an interactive prompt).
201
- Extra arguments pass through either way, e.g.
202
- `bdt pr create --target dev -- --reviewers "..."`.
188
+ a REST POST to `pullrequests` (org/project/repo inferred from the git remote). On GitHub,
189
+ a REST POST to `pulls`; title/body default to the branch's commit(s) so it never blocks on an
190
+ interactive prompt.
191
+
192
+ Extra arguments after `--` take the union of `gh pr create` and `az repos pr create` spellings on **both**
193
+ hosts (`--opt value` and `--opt=value`; list flags take every value up to the next option, comma-separated
194
+ values work too; boolean flags take an optional `true`/`false`):
195
+
196
+ | Meaning | Flags |
197
+ |---|---|
198
+ | title / body | `--title`/`-t`, `--body`/`--description`/`-b` (`-d` on Azure DevOps), `--body-file`/`-F` (`-` = stdin) |
199
+ | branches | `--base`/`-B`/`--target-branch`, `--head`/`-H`/`--source-branch`/`-s` |
200
+ | draft | `--draft [true\|false]` (`-d` on GitHub) |
201
+ | labels / reviewers | `--label`/`--labels`/`-l`, `--reviewer`/`--reviewers` (`-r` on GitHub), `--required-reviewers` |
202
+ | work items / issues | `--work-items` (same as `--issue`: linked and tagged `pr-available`) |
203
+ | merge | `--auto-complete`/`--auto-merge`, `--squash`, `--delete-source-branch`, `--merge-commit-message`, `--bypass-policy`, `--bypass-policy-reason`, `--transition-work-items` |
204
+ | other | `--assignee`/`-a`, `--milestone`/`-m`, `--project`/`-p` (GitHub board), `--fill`/`-f`, `--web`/`-w`/`--open`, `--dry-run` |
205
+ | Azure DevOps target | `--organization`/`--org`, `--project`/`-p`, `--repository`/`-r` override the remote-derived values |
206
+ | accepted, ignored | `--detect`, `--verbose`, `--debug`, `--subscription`, `--output`/`-o`, `--query` |
207
+
208
+ Azure DevOps resolves reviewers (GUID, e-mail or display name) through the Identities API, sets
209
+ `--auto-complete` as the current user and applies the merge options in a follow-up update, as `az` does.
210
+ A flag the host has no counterpart for (e.g. `--milestone` on Azure DevOps, `--delete-source-branch` on
211
+ GitHub) is reported on stderr (`Note: --milestone is ignored on Azure DevOps`), never dropped silently.
212
+ Unknown flags fail with the list of supported ones.
203
213
 
204
214
  Pass the text with `--title "..."` and `--description "$(cat pr.md)"` rather than as passthrough
205
- args: it is kept in full. On GitHub the description goes to `gh` on stdin (`--body-file -`); on
206
- Azure DevOps it is set over the REST API right after `az` creates the PR, because
207
- `az repos pr create --description` has been seen cutting long text at ~450 characters (a `--title`/
208
- `--description` given after `--` is intercepted the same way). Azure DevOps itself allows at most
215
+ args: the description is always sent in full. Azure DevOps itself allows at most
209
216
  4000 characters in a PR description: a longer one is rejected up front, and a description the
210
217
  server stored shorter than sent is reported as a warning (`bdt pr update` does the same check).
211
218
 
@@ -215,15 +222,15 @@ it ready for review. Pass `--no-draft` to open it ready for review
215
222
  immediately instead.
216
223
 
217
224
  `--label` (repeatable) applies labels to the PR on either host: on GitHub
218
- these map to `gh pr create --label`, so the label must already exist on the
219
- repo (`gh label create`); on Azure DevOps they map to `az repos pr create
220
- --labels`, which are freeform and get created on the fly. A successful
221
- create prints the PR's web/GUI link (not just the REST API URL Azure DevOps'
222
- `az` output otherwise gives you).
225
+ these are applied to the PR, and unknown labels are created automatically
226
+ by GitHub (no pre-existing label required); on Azure DevOps labels are freeform and get
227
+ created on the fly. A successful
228
+ create prints the PR's web/GUI link (not just the REST API URL Azure DevOps
229
+ returns).
223
230
 
224
231
  `[tool.bdt.pr.required_labels]` in pyproject.toml can require at least one
225
232
  label from each named group before the PR is created — checked locally
226
- (no `gh`/`az` call happens if a group isn't satisfied):
233
+ (no API call happens if a group isn't satisfied):
227
234
 
228
235
  ```toml
229
236
  [tool.bdt.pr.required_labels]
@@ -353,27 +360,26 @@ On `update`, `--board` only moves the item when you pass it explicitly — it
353
360
  never falls back to `pyproject.toml`, so an unrelated field update (e.g.
354
361
  just `--title`) can't silently relocate the item to a different board.
355
362
 
356
- **GitHub**: a thin wrapper around `gh issue create` / `edit` / `delete` /
357
- `comment`. `--label`/`--tag` add a label on `create`, or add/remove one on
363
+ **GitHub**: REST/GraphQL calls for issue create / edit / delete /
364
+ comment. `--label`/`--tag` add a label on `create`, or add/remove one on
358
365
  `update` (paired with `--remove-label`/`--remove-tag`); labels must already
359
366
  exist in the repo. `--screenshot` pushes images to a `pr-assets` branch (same trick `bdt
360
367
  pr create --screenshot` uses, since GitHub has no API for uploading an image
361
368
  into an issue) and appends them to the issue body / comment as Markdown.
362
369
  `issue delete` is **permanent** — GitHub has no recycle bin for issues.
363
- Comment update/delete go through `gh api` directly (`gh issue` has no
364
- subcommand for editing/deleting an arbitrary comment by ID). Extra arguments
365
- to `bdt issue create` pass through to `gh issue create`, e.g.
366
- `bdt issue create --title "..." -- --assignee @me`.
367
-
368
- `--parent` / `--blocked-by` use GitHub's sub-issues and issue dependencies (`gh issue create --parent` /
369
- `--blocked-by`, `gh issue edit --parent` / `--add-blocked-by`), which need a recent `gh`; `issue show` reads
370
- them with `gh issue view --json parent,subIssues,blockedBy,blocking`. `--iteration` isn't supported: a
370
+ Comment update/delete go through REST. Extra arguments
371
+ to `bdt issue create` accept the `gh issue create` flags `--title`/`-t`, `--body`/`-b`, `--body-file`/`-F`
372
+ (`-` = stdin), `--label`/`-l`, `--assignee`/`-a` (`@me` works), `--milestone`/`-m` (title or number),
373
+ `--project`/`-p` (Projects board, repeatable) and `--web`/`-w`; `--template` / `--recover` are rejected. E.g.
374
+ `bdt issue create --title "..." -- --assignee @me --project Roadmap`.
375
+
376
+ `--parent` / `--blocked-by` use GitHub's sub-issues and issue dependencies REST endpoints; `issue show` reads
377
+ the parent, sub-issues, blocked-by and blocking links the same way. `--iteration` isn't supported: a
371
378
  GitHub Projects (v2) iteration is a per-project field, not a property of the issue.
372
379
 
373
380
  `--board <name>` on `create` adds the issue to that GitHub Projects (v2)
374
- board by title (needs a token with the `project` scope — `gh auth refresh -s
375
- project`). On `search`, `--board` (title or number) scopes results to issues
376
- currently on that board; since `gh issue list` has no board filter of its
381
+ board by title (needs a token with the `project` scope). On `search`, `--board` (title or number) scopes results to issues
382
+ currently on that board; since the issue list has no board filter of its
377
383
  own, this fetches a wider raw pool and filters it down client-side, so a
378
384
  board with few matches may return fewer results than `--limit` asks for. A
379
385
  CLI flag beats `[tool.bdt.github].board` in `pyproject.toml`:
@@ -409,7 +415,7 @@ Claims an issue (GitHub) or work item (Azure DevOps): comments `Taken by <you>`
409
415
  running coding agent's session (the same `<Agent> Session: <id>` note every bdt comment gets: a
410
416
  claude.ai link under a bridged Claude session, otherwise the bare session id; nothing outside an
411
417
  agent), so others can see who is on it. `<you>` is the GitHub login
412
- `gh` is authenticated as, else the git `user.name`. Without `NUMBER` it takes the issue the current
418
+ the token authenticates as, else the git `user.name`. Without `NUMBER` it takes the issue the current
413
419
  branch's PR closes (`Fixes #N`, or an issue/work-item URL in the PR body); it refuses to guess if
414
420
  that is none or several. If the issue's newest comment already is a "Taken by ..." claim (by anyone) it
415
421
  says so and posts nothing, so it's safe to run repeatedly -- including before `bdt issue do`.
@@ -430,7 +436,7 @@ addresses the calling agent. It combines with every other `search` filter (keywo
430
436
  the agent that is running `bdt`, or `@Agent`, which addresses every agent. Matching is case-insensitive and
431
437
  on whole words: `@claude`, `@CLAUDE,` and `@Claude.` match, `@Claudette`, `@claude-bot` and `me@claude.ai` do not.
432
438
  Only the newest comment counts, so once someone (including the agent, e.g. with a reply) comments after the
433
- mention, the issue drops out of the list. The newest comment isn't part of the list output of `gh`/Azure DevOps,
439
+ mention, the issue drops out of the list. The newest comment isn't part of the list output of GitHub/Azure DevOps,
434
440
  so this costs one extra request per candidate; candidates are scanned in recently-updated order (up to 10x
435
441
  `--limit` on GitHub, 200 on Azure DevOps) until `--limit` have matched.
436
442
 
@@ -561,10 +567,10 @@ Configuration (flags override env vars):
561
567
  | Local work dir | `--root` | `AZDO_WORK_DIR`, `BMS_WORK_DIR` | `~/projects` (`C:/Projects` on Windows) |
562
568
  | Azure DevOps org | `--org` | `AZDO_ORG`, `BMS_ORG` | none (ADO not searched) |
563
569
  | GitHub org | `--github-org` | `GITHUB_ORG`, `BMS_GITHUB_ORG` | none (GitHub not searched) |
564
- | ADO PAT for cloning | `--pat` | `AZURE_DEVOPS_EXT_PAT`, `AZURE_DEVOPS_PAT` | falls back to `az` login |
570
+ | ADO PAT for cloning | `--pat` | `AZURE_DEVOPS_EXT_PAT`, `AZURE_DEVOPS_PAT` | falls back to `DefaultAzureCredential` |
565
571
 
566
572
  Set the org env vars in your shell profile to make `bdt find-repo <name>` work anywhere. It replaces
567
- the `cross-repo-discovery` skill's `ALL_REPOS.md` sync. Needs `az` (ADO) / `gh` (GitHub) only when
573
+ the `cross-repo-discovery` skill's `ALL_REPOS.md` sync. Needs ADO auth / a GitHub token only when
568
574
  those orgs are searched.
569
575
 
570
576
  ## `bdt cleanup worktrees` / `bdt cleanup orphaned-dbs` / `bdt cleanup db` / `bdt cleanup worktree`
@@ -719,7 +725,7 @@ sandbox mode too.
719
725
  ## `bdt logs roles` / `bdt logs tail`
720
726
 
721
727
  Query Application Insights (KQL over `traces`/`exceptions`) via
722
- `az monitor app-insights query`. No defaults are baked in — pass
728
+ the Application Insights REST API (azure-identity credential). No defaults are baked in — pass
723
729
  `--resource-group`/`--app-insights` explicitly (or set
724
730
  `AZURE_RESOURCE_GROUP`/`AZURE_APP_INSIGHTS`), since which Azure resource
725
731
  "this repo" maps to isn't derivable from the git remote.
@@ -732,7 +738,7 @@ bdt logs tail --resource-group my-rg --app-insights my-app-insights --role my-se
732
738
  ## `bdt logs fetch`
733
739
 
734
740
  Downloads the App Service log archive for a webapp/slot via
735
- `az webapp log download`, unzips it, and writes every line matching a
741
+ the App Service Kudu/ARM log download, unzips it, and writes every line matching a
736
742
  common error/warning marker (`ERROR`, `CRITICAL`, `WARNING`, tracebacks,
737
743
  `4xx`/`5xx`, `FAILED`, `FATAL`) to `<out>/<slot>_errors.log`. Simpler and
738
744
  often preferable to the KQL commands above when you just want "what broke
@@ -740,7 +746,7 @@ recently" rather than a queryable trace stream.
740
746
 
741
747
  The webapp/resource-group/slot come from a named environment configured in
742
748
  the calling repo's `pyproject.toml`. `slot` is optional — omit it for an
743
- app's default/production slot (no `--slot` is passed to `az`); set it for a
749
+ app's default/production slot (no slot is used); set it for a
744
750
  named deployment slot:
745
751
 
746
752
  ```toml
@@ -0,0 +1,28 @@
1
+ """Azure DevOps auth: explicit PAT (`--pat` / AZURE_DEVOPS_EXT_PAT / AZURE_DEVOPS_PAT), or a token from
2
+ `DefaultAzureCredential` (see `azure_auth`)."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import base64
7
+
8
+ from .azure_auth import get_azure_token
9
+
10
+ # Well-known Azure DevOps resource ID; `<id>/.default` is its AAD scope.
11
+ ADO_RESOURCE_ID = "499b84ac-1321-427f-aa17-267ca6975798"
12
+
13
+
14
+ def get_az_devops_token() -> str:
15
+ return get_azure_token(f"{ADO_RESOURCE_ID}/.default")
16
+
17
+
18
+ def auth_header(pat: str | None) -> dict[str, str]:
19
+ """Basic-auth header for an explicit PAT, else Bearer via `DefaultAzureCredential`.
20
+
21
+ Never hardcode a PAT literal in a caller — pass it in from an env var
22
+ (e.g. AZURE_DEVOPS_EXT_PAT) or a CLI flag, or omit it and let azure-identity supply a
23
+ short-lived token from the operator's own login.
24
+ """
25
+ if pat:
26
+ token = base64.b64encode(f":{pat}".encode()).decode()
27
+ return {"Authorization": f"Basic {token}"}
28
+ return {"Authorization": f"Bearer {get_az_devops_token()}"}