bmsdna-devtools 0.28.0__tar.gz → 0.28.1__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.
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/PKG-INFO +40 -1
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/README.md +39 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/cli.py +26 -0
- bmsdna_devtools-0.28.1/bmsdna/devtools/issue_do.py +71 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/pyproject.toml +1 -1
- bmsdna_devtools-0.28.1/tests/test_issue_do.py +20 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/uv.lock +1 -1
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/.github/workflows/auto-release.yml +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/.github/workflows/python-publish.yml +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/.github/workflows/python-test.yml +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/.gitignore +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/.python-version +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/__init__.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/ado_auth.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/ado_issue.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/app_service_logs.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/bdt_config.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/cli_tools.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/commit.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/env_config.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/find_injection.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/find_repo.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/gh_issue.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/gh_pr.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/gitrepo.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/injection_frontend.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/injection_python.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/lint.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/lint_findings.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/lint_models.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/lint_sql.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/lint_tooling.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/lint_typescript.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/logs.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/pr_build.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/pr_issue_link.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/pr_labels.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/pr_markdown.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/pull.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/testdb.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/bmsdna/devtools/worktree.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/justfile +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/skills/bmsdna-devtools/SKILL.md +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/conftest.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_ado_auth.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_ado_issue.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_ado_issue_flow.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_app_service_logs.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_bdt_config.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_cli_cleanup.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_cli_commit.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_cli_issue.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_cli_pr_create.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_cli_pr_id.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_cli_pull.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_cli_tools.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_commit.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_env_config.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_find_injection.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_find_repo.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_gh_issue.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_gh_pr.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_gitrepo.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_lint.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_lint_models.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_lint_sql.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_lint_tooling.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_lint_typescript.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_pr_build.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_pr_build_flow.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_pr_issue_link.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_pr_labels.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_pr_markdown.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_pull.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_testdb.py +0 -0
- {bmsdna_devtools-0.28.0 → bmsdna_devtools-0.28.1}/tests/test_worktree_clean.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: bmsdna-devtools
|
|
3
|
-
Version: 0.28.
|
|
3
|
+
Version: 0.28.1
|
|
4
4
|
Summary: Shared Azure DevOps / GitHub / git / Azure Monitor developer tooling for BMS projects
|
|
5
5
|
Requires-Python: >=3.14
|
|
6
6
|
Requires-Dist: pgdevkit[db]>=0.7.1
|
|
@@ -287,6 +287,17 @@ CLI flag beats `[tool.bdt.github].board` in `pyproject.toml`:
|
|
|
287
287
|
board = "Roadmap"
|
|
288
288
|
```
|
|
289
289
|
|
|
290
|
+
## `bdt issue do`
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
bdt issue do 60 [--agent claude] [--dry-run] [extra agent args...]
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Hands an issue (GitHub) or work item (Azure DevOps) to a coding agent. Fetches the title and
|
|
297
|
+
description and runs `claude -p "<prompt>" --name "60: <title>"`, so the session is easy to find
|
|
298
|
+
with `claude --resume`. `--agent` picks another executable (it then gets just the prompt as its
|
|
299
|
+
last argument); extra args are passed through; `--dry-run` prints the command.
|
|
300
|
+
|
|
290
301
|
## `bdt worktree`
|
|
291
302
|
|
|
292
303
|
```bash
|
|
@@ -297,6 +308,34 @@ Creates `.worktrees/<name>` branched from `--base`, initializes submodules
|
|
|
297
308
|
(unless `--no-submodules`), and copies an env file into the new worktree as
|
|
298
309
|
`.env` (auto-detects `.local_env` then `.env` if `--env-file` isn't given).
|
|
299
310
|
|
|
311
|
+
## `bdt find-repo`
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
bdt find-repo my-repo [--root ~/projects] [--org MYORG] [--github-org MYORG] [--yes] [--pat PAT]
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Finds a repo by name: first among the local clones under the work dir, then -- only if there is
|
|
318
|
+
no local match -- in an Azure DevOps org and/or a GitHub org. Names match exactly
|
|
319
|
+
(case-insensitive) if any repo has that name, otherwise by substring. Local matches are printed
|
|
320
|
+
as paths. A single remote-only match is offered for cloning (`--yes` skips the prompt; without a
|
|
321
|
+
TTY it never prompts and just tells you to pass `--yes`); several matches are listed and nothing
|
|
322
|
+
is cloned.
|
|
323
|
+
|
|
324
|
+
Clone destinations: `<root>/<project>/<repo>` for Azure DevOps, `<root>/github/<repo>` for GitHub.
|
|
325
|
+
|
|
326
|
+
Configuration (flags override env vars):
|
|
327
|
+
|
|
328
|
+
| Setting | Flag | Env vars | Default |
|
|
329
|
+
| --- | --- | --- | --- |
|
|
330
|
+
| Local work dir | `--root` | `AZDO_WORK_DIR`, `BMS_WORK_DIR` | `~/projects` (`C:/Projects` on Windows) |
|
|
331
|
+
| Azure DevOps org | `--org` | `AZDO_ORG`, `BMS_ORG` | none (ADO not searched) |
|
|
332
|
+
| GitHub org | `--github-org` | `GITHUB_ORG`, `BMS_GITHUB_ORG` | none (GitHub not searched) |
|
|
333
|
+
| ADO PAT for cloning | `--pat` | `AZURE_DEVOPS_EXT_PAT`, `AZURE_DEVOPS_PAT` | falls back to `az` login |
|
|
334
|
+
|
|
335
|
+
Set the org env vars in your shell profile to make `bdt find-repo <name>` work anywhere. It replaces
|
|
336
|
+
the `cross-repo-discovery` skill's `ALL_REPOS.md` sync. Needs `az` (ADO) / `gh` (GitHub) only when
|
|
337
|
+
those orgs are searched.
|
|
338
|
+
|
|
300
339
|
## `bdt cleanup worktrees` / `bdt cleanup orphaned-dbs` / `bdt cleanup db` / `bdt cleanup worktree`
|
|
301
340
|
|
|
302
341
|
```bash
|
|
@@ -274,6 +274,17 @@ CLI flag beats `[tool.bdt.github].board` in `pyproject.toml`:
|
|
|
274
274
|
board = "Roadmap"
|
|
275
275
|
```
|
|
276
276
|
|
|
277
|
+
## `bdt issue do`
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
bdt issue do 60 [--agent claude] [--dry-run] [extra agent args...]
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Hands an issue (GitHub) or work item (Azure DevOps) to a coding agent. Fetches the title and
|
|
284
|
+
description and runs `claude -p "<prompt>" --name "60: <title>"`, so the session is easy to find
|
|
285
|
+
with `claude --resume`. `--agent` picks another executable (it then gets just the prompt as its
|
|
286
|
+
last argument); extra args are passed through; `--dry-run` prints the command.
|
|
287
|
+
|
|
277
288
|
## `bdt worktree`
|
|
278
289
|
|
|
279
290
|
```bash
|
|
@@ -284,6 +295,34 @@ Creates `.worktrees/<name>` branched from `--base`, initializes submodules
|
|
|
284
295
|
(unless `--no-submodules`), and copies an env file into the new worktree as
|
|
285
296
|
`.env` (auto-detects `.local_env` then `.env` if `--env-file` isn't given).
|
|
286
297
|
|
|
298
|
+
## `bdt find-repo`
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
bdt find-repo my-repo [--root ~/projects] [--org MYORG] [--github-org MYORG] [--yes] [--pat PAT]
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Finds a repo by name: first among the local clones under the work dir, then -- only if there is
|
|
305
|
+
no local match -- in an Azure DevOps org and/or a GitHub org. Names match exactly
|
|
306
|
+
(case-insensitive) if any repo has that name, otherwise by substring. Local matches are printed
|
|
307
|
+
as paths. A single remote-only match is offered for cloning (`--yes` skips the prompt; without a
|
|
308
|
+
TTY it never prompts and just tells you to pass `--yes`); several matches are listed and nothing
|
|
309
|
+
is cloned.
|
|
310
|
+
|
|
311
|
+
Clone destinations: `<root>/<project>/<repo>` for Azure DevOps, `<root>/github/<repo>` for GitHub.
|
|
312
|
+
|
|
313
|
+
Configuration (flags override env vars):
|
|
314
|
+
|
|
315
|
+
| Setting | Flag | Env vars | Default |
|
|
316
|
+
| --- | --- | --- | --- |
|
|
317
|
+
| Local work dir | `--root` | `AZDO_WORK_DIR`, `BMS_WORK_DIR` | `~/projects` (`C:/Projects` on Windows) |
|
|
318
|
+
| Azure DevOps org | `--org` | `AZDO_ORG`, `BMS_ORG` | none (ADO not searched) |
|
|
319
|
+
| GitHub org | `--github-org` | `GITHUB_ORG`, `BMS_GITHUB_ORG` | none (GitHub not searched) |
|
|
320
|
+
| ADO PAT for cloning | `--pat` | `AZURE_DEVOPS_EXT_PAT`, `AZURE_DEVOPS_PAT` | falls back to `az` login |
|
|
321
|
+
|
|
322
|
+
Set the org env vars in your shell profile to make `bdt find-repo <name>` work anywhere. It replaces
|
|
323
|
+
the `cross-repo-discovery` skill's `ALL_REPOS.md` sync. Needs `az` (ADO) / `gh` (GitHub) only when
|
|
324
|
+
those orgs are searched.
|
|
325
|
+
|
|
287
326
|
## `bdt cleanup worktrees` / `bdt cleanup orphaned-dbs` / `bdt cleanup db` / `bdt cleanup worktree`
|
|
288
327
|
|
|
289
328
|
```bash
|
|
@@ -15,6 +15,7 @@ from pgdevkit.testdb import constants as pgdevkit_constants
|
|
|
15
15
|
from . import ado_issue, app_service_logs, commit as commit_mod
|
|
16
16
|
from . import env_config
|
|
17
17
|
from . import find_repo as find_repo_mod
|
|
18
|
+
from . import issue_do as issue_do_mod
|
|
18
19
|
from . import gh_issue, gh_pr
|
|
19
20
|
from . import find_injection as find_injection_mod
|
|
20
21
|
from . import lint as lint_mod
|
|
@@ -694,6 +695,31 @@ def issue_search(
|
|
|
694
695
|
)
|
|
695
696
|
|
|
696
697
|
|
|
698
|
+
@issue_app.command("do", context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
|
|
699
|
+
def issue_do(
|
|
700
|
+
ctx: typer.Context,
|
|
701
|
+
number: int = typer.Argument(..., help="Issue number (GitHub) or work item ID (Azure DevOps)"),
|
|
702
|
+
agent: str = typer.Option("claude", "--agent", help="Agent executable to start (claude runs headless: `claude -p ... --name '<number>: <title>'`)"),
|
|
703
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Print the agent command instead of running it"),
|
|
704
|
+
pat: str | None = typer.Option(
|
|
705
|
+
None,
|
|
706
|
+
"--pat",
|
|
707
|
+
envvar=["AZURE_DEVOPS_EXT_PAT", "AZURE_DEVOPS_PAT"],
|
|
708
|
+
help="Azure DevOps PAT (else falls back to `az` login)",
|
|
709
|
+
),
|
|
710
|
+
) -> None:
|
|
711
|
+
"""Start a coding agent on an issue / work item, with the session named '<number>: <title>'.
|
|
712
|
+
Extra args after the number are passed through to the agent."""
|
|
713
|
+
remote = current_remote()
|
|
714
|
+
if isinstance(remote, GitHubRemote):
|
|
715
|
+
title, body = issue_do_mod.fetch_github(require_gh(), number)
|
|
716
|
+
else:
|
|
717
|
+
session = requests.Session()
|
|
718
|
+
session.headers.update(auth_header(pat))
|
|
719
|
+
title, body = issue_do_mod.fetch_ado(session, remote, number)
|
|
720
|
+
issue_do_mod.run(number, title, body, agent=agent, extra=list(ctx.args), dry_run=dry_run)
|
|
721
|
+
|
|
722
|
+
|
|
697
723
|
@issue_app.command("update")
|
|
698
724
|
def issue_update(
|
|
699
725
|
number: int = typer.Argument(..., help="Issue number (GitHub) or work item ID (Azure DevOps)"),
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""`bdt issue do <number>`: hand an issue / work item to a coding agent.
|
|
2
|
+
|
|
3
|
+
Fetches the issue's title and description (GitHub via `gh`, Azure DevOps via the REST API)
|
|
4
|
+
and starts the agent headless (`claude -p`), naming the session `<number>: <title>` so it is
|
|
5
|
+
easy to find again with `claude --resume`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
import requests
|
|
17
|
+
|
|
18
|
+
from .ado_issue import _base_url
|
|
19
|
+
from .gitrepo import AdoRemote
|
|
20
|
+
|
|
21
|
+
_HTML_TAG_RE = re.compile(r"<[^>]+>")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def fetch_github(gh: str, number: int) -> tuple[str, str]:
|
|
25
|
+
result = subprocess.run([gh, "issue", "view", str(number), "--json", "title,body"], capture_output=True, text=True, check=False)
|
|
26
|
+
if result.returncode != 0:
|
|
27
|
+
sys.exit(f"gh issue view {number} failed:\n{result.stderr.strip()}")
|
|
28
|
+
data = json.loads(result.stdout)
|
|
29
|
+
return data["title"], data.get("body") or ""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def fetch_ado(session: requests.Session, remote: AdoRemote, number: int) -> tuple[str, str]:
|
|
33
|
+
r = session.get(
|
|
34
|
+
f"{_base_url(remote)}/_apis/wit/workitems/{number}",
|
|
35
|
+
params={"fields": "System.Title,System.Description", "api-version": "7.1"},
|
|
36
|
+
)
|
|
37
|
+
r.raise_for_status()
|
|
38
|
+
fields = r.json()["fields"]
|
|
39
|
+
# ADO descriptions are HTML; the agent only needs the text.
|
|
40
|
+
description = _HTML_TAG_RE.sub("", (fields.get("System.Description") or "").replace("<br>", "\n").replace("</div>", "\n"))
|
|
41
|
+
return fields["System.Title"], description.strip()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def session_name(number: int, title: str) -> str:
|
|
45
|
+
return f"{number}: {title}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def build_prompt(number: int, title: str, body: str) -> str:
|
|
49
|
+
return (
|
|
50
|
+
f"Work on issue {number}: {title}\n\n{body.strip() or '(no description)'}\n\n"
|
|
51
|
+
"Follow the dev-workflow skill: worktree, draft PR early, implement, test, publish the PR "
|
|
52
|
+
f"and reference issue {number} in it. Do not merge."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build_command(agent: str, number: int, title: str, body: str, extra: list[str]) -> list[str]:
|
|
57
|
+
prompt = build_prompt(number, title, body)
|
|
58
|
+
if agent == "claude":
|
|
59
|
+
return [agent, "-p", prompt, "--name", session_name(number, title), *extra]
|
|
60
|
+
# Other agents: no known naming flag, so just hand over the prompt.
|
|
61
|
+
return [agent, *extra, prompt]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def run(number: int, title: str, body: str, *, agent: str, extra: list[str], dry_run: bool) -> None:
|
|
65
|
+
cmd = build_command(agent, number, title, body, extra)
|
|
66
|
+
if dry_run:
|
|
67
|
+
print(" ".join(json.dumps(c) if " " in c or "\n" in c else c for c in cmd))
|
|
68
|
+
return
|
|
69
|
+
if shutil.which(agent) is None:
|
|
70
|
+
sys.exit(f"'{agent}' is required for this command but wasn't found on PATH.")
|
|
71
|
+
raise SystemExit(subprocess.run(cmd, check=False).returncode)
|
|
@@ -10,7 +10,7 @@ packages = ["bmsdna"]
|
|
|
10
10
|
|
|
11
11
|
[project]
|
|
12
12
|
name = "bmsdna-devtools"
|
|
13
|
-
version = "0.28.
|
|
13
|
+
version = "0.28.1"
|
|
14
14
|
description = "Shared Azure DevOps / GitHub / git / Azure Monitor developer tooling for BMS projects"
|
|
15
15
|
readme = "README.md"
|
|
16
16
|
requires-python = ">=3.14" # pgdevkit>=0.7.1 requires 3.14; was >=3.11 before adding it as a dependency
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from bmsdna.devtools import issue_do
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_claude_command_names_session_and_is_headless() -> None:
|
|
5
|
+
cmd = issue_do.build_command("claude", 60, "add command", "body text", ["--model", "opus"])
|
|
6
|
+
assert cmd[:2] == ["claude", "-p"]
|
|
7
|
+
assert "body text" in cmd[2]
|
|
8
|
+
assert cmd[3:5] == ["--name", "60: add command"]
|
|
9
|
+
assert cmd[-2:] == ["--model", "opus"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_other_agent_gets_prompt_last() -> None:
|
|
13
|
+
cmd = issue_do.build_command("codex", 7, "t", "", ["exec"])
|
|
14
|
+
assert cmd[:2] == ["codex", "exec"]
|
|
15
|
+
assert "(no description)" in cmd[2]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_dry_run_prints(capsys) -> None:
|
|
19
|
+
issue_do.run(1, "x", "y", agent="claude", extra=[], dry_run=True)
|
|
20
|
+
assert '"1: x"' in capsys.readouterr().out
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|