feat(bug): add issue, PR, and fetch commands plus branch isolation - #4281
feat(bug): add issue, PR, and fetch commands plus branch isolation#4281arrrrny wants to merge 2 commits into
Conversation
Extend the bundled Bug Triage Workflow extension: - speckit.bug.issue: file a GitHub issue from an assessment (the "report" phase) and record the issue link, degrading to issue-draft.md offline. - speckit.bug.pr: open a PR from the fix branch, linking the tracked issue, degrading to pr-draft.md offline. - speckit.bug.fetch: load an existing GitHub issue by number/URL/owner#n via gh, record issue.md, and seed an assessment.md draft so fix/test can run. - speckit.bug.assess: auto-trigger the issue via --issue or the auto_create_issue config, and clarify that assess != report. - speckit.bug.fix: --branch/--worktree isolation mirroring `specify spec`. Also updates extension.yml (new commands + gh tool), config-template.yml, README, catalog.json, and the extension test (EXPECTED_COMMANDS -> 6). Assisted-by: Kimi Code (autonomous)
There was a problem hiding this comment.
Pull request overview
Extends the bundled bug workflow with GitHub issue/PR operations, issue fetching, configuration, and branch isolation.
Changes:
- Adds issue, fetch, and PR commands.
- Adds configurable issue creation and branch isolation.
- Updates documentation, manifest, catalog, and tests.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
extensions/bug/commands/speckit.bug.assess.md |
Adds optional issue reporting. |
extensions/bug/commands/speckit.bug.fetch.md |
Adds existing-issue ingestion. |
extensions/bug/commands/speckit.bug.fix.md |
Adds branch/worktree isolation. |
extensions/bug/commands/speckit.bug.issue.md |
Adds GitHub issue creation. |
extensions/bug/commands/speckit.bug.pr.md |
Adds pull-request creation. |
extensions/bug/config-template.yml |
Defines workflow defaults. |
extensions/bug/extension.yml |
Registers commands, configuration, and gh. |
extensions/bug/README.md |
Documents the expanded workflow. |
extensions/catalog.json |
Updates the bundled extension description. |
tests/extensions/bug/test_bug_extension.py |
Updates command and configuration checks. |
Suppressed comments (2)
extensions/bug/commands/speckit.bug.fetch.md:37
- A URL or
owner/repo#nalready supplies the repository, but this prerequisite still refuses a live fetch unless the current directory has a GitHub origin. That unnecessarily degrades valid cross-repository fetches (and fetches outside a Git worktree) to a draft; require the remote only for bare issue numbers.
- Detect GitHub context (same as `__SPECKIT_COMMAND_BUG_ISSUE__`):
- `git rev-parse --is-inside-work-tree 2>/dev/null` to confirm a repository.
- `git config --get remote.origin.url` to read the remote; parse `owner`/`repo` (HTTPS `https://github.com/<owner>/<repo>.git` or SSH `git@github.com:<owner>/<repo>.git`). Only proceed with a live fetch when the remote points to `github.com`.
- `command -v gh >/dev/null 2>&1` and `gh auth status` to confirm the CLI and auth. If `gh`/GitHub remote/auth is unavailable, skip the live fetch and write a draft (see Graceful Degradation).
extensions/bug/README.md:115
- This again states that assess “never touches GitHub,” which is false when
--issueorauto_create_issueis enabled. Describe the local-only behavior as the default and disclose that opt-in reporting delegates tobug.issue.
- **Assess** means *triage a report into a local `assessment.md`* — it never touches GitHub. Use it for a bug described in pasted text or a URL.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| 3. **Open the PR (live path)** | ||
| - Determine the base branch: prefer the repository default (usually `main`/`master`); allow the user to override with `base=<branch>` in `$ARGUMENTS`. | ||
| - Run: | ||
| ```bash | ||
| gh pr create --base <base> --title "<title>" --body-file BUG_DIR/pr-body.md --json number,url,title | ||
| ``` | ||
| - Capture the JSON. If the push of the current branch fails, run `git push -u origin <current-branch>` and retry. |
| - Run: | ||
| ```bash | ||
| gh issue create --title "<title>" --body-file BUG_DIR/issue-body.md --label "bug" --label "severity:<level>" --json number,url,title | ||
| ``` | ||
| - Capture the JSON. **If a label is rejected** (the repo does not have it), retry without the `severity:<level>` label, then without any labels — a tracked issue is better than none. Record the final outcome either way. |
| - **Branch mode** (`--branch`): run `git checkout -b <prefix>/<BUG_SLUG>` from the current branch (assumed clean or committed). | ||
| - **Worktree mode** (`--worktree`): run `git worktree add ../<repo>-<BUG_SLUG> -b <prefix>/<BUG_SLUG>` so the fix lives in a separate working directory; then continue operations there. |
|
|
||
| ## Slug Resolution | ||
|
|
||
| Each bug gets its own directory under `.specify/bugs/<slug>/`. If the user passed a slug, use it verbatim after normalization (lowercase, hyphen-separated, no spaces, no special characters other than `-` and digits). Otherwise derive a 2–4 word kebab-case slug from the issue **title**. Ensure the directory is unique — if `.specify/bugs/<slug>/` already exists, append the shortest disambiguating suffix (`-2`, `-3`, …) or `-<issue-number>`. Never overwrite an existing bug directory. |
| - `__SPECKIT_COMMAND_BUG_ASSESS__ slug=<BUG_SLUG>` (refine the triage draft into a full assessment) — optional. | ||
| - `__SPECKIT_COMMAND_BUG_FIX__ slug=<BUG_SLUG>` (apply the fix; add `--branch` or `--worktree` to isolate). |
| - Parse the user input for `branch` / `--branch` / `worktree` / `--worktree` (or `branch=true` / `worktree=true`). These are mutually exclusive; prefer `--branch` unless the user explicitly asks for a worktree. | ||
| - Determine the branch name `<prefix>/<BUG_SLUG>`, where `<prefix>` comes from `.specify/extensions/bug/bug-config.yml` (`branch_prefix`, default `fix`). Example: `fix/login-timeout`. If a branch with that name already exists, stop and ask the user how to proceed (reuse it, choose another name, or skip isolation). | ||
| - **Branch mode** (`--branch`): run `git checkout -b <prefix>/<BUG_SLUG>` from the current branch (assumed clean or committed). | ||
| - **Worktree mode** (`--worktree`): run `git worktree add ../<repo>-<BUG_SLUG> -b <prefix>/<BUG_SLUG>` so the fix lives in a separate working directory; then continue operations there. |
| - **Explicit opt-in**: if the user passed a truthy `issue` / `--issue` flag, file the issue now. | ||
| - **Config opt-in**: if `.specify/extensions/bug/bug-config.yml` exists and sets `auto_create_issue: true`, file the issue now. |
| - `BUG_DIR/fix.md` MUST exist. If it does not, stop and instruct the user to run `__SPECKIT_COMMAND_BUG_FIX__` first. | ||
| - Confirm the current branch is the fix branch (created by `bug.fix --branch`, or whatever branch holds the change). If the working tree is on `main`/`master` with uncommitted changes, warn the user and ask which branch to open the PR from before continuing. |
| 1. **Assess** — read a bug report (pasted text or a URL), judge whether it is a real bug, locate suspected code paths, and propose a remediation. | ||
| 2. **Fix** — apply the proposed remediation and record exactly what changed. | ||
| 3. **Test** — re-run the reproduction and any added tests, then record the verification result. | ||
| 1. **Assess** — read a bug report (pasted text or a URL), judge whether it is a real bug, locate suspected code paths, and propose a remediation. `assess` writes a *local* assessment only; it does **not** file a GitHub issue. |
| 3. **Fix** — apply the proposed remediation and record exactly what changed. Pass `--branch` (or `--worktree`) to isolate the fix on its own git branch. | ||
| 4. **Open PR** (optional) — `speckit.bug.pr` opens a pull request from the fix branch, linking the issue. | ||
| 5. **Test** — re-run the reproduction and any added tests, then record the verification result. |
- bug.issue / bug.pr: stop using `gh ... create --json`, which older gh
versions reject ("unknown flag: --json"). Capture the issue/PR URL from
stdout instead and parse the number from it.
- bug.fetch: same treatment — use plain `gh issue view` and read the
rendered output rather than `--json`.
- bug.assess: make the auto_create_issue config check explicit and
mandatory (read bug-config.yml; if auto_create_issue is true, file the
issue without further confirmation) so the opt-in actually fires.
Assisted-by: Kimi Code (autonomous)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Suppressed comments (11)
Previously missed (9) — in code that hasn't changed since the last review.
extensions/bug/extension.yml:7
- The extension still reports version
1.0.0, sospecify extension updatewill consider existing 1.0.0 installations up to date (_commands.py:1660-1671) and they will never receive these new commands. Bump the manifest and catalog entry together (for example to1.1.0) and refresh the catalog timestamp.
description: "Assess, load or report (GitHub issue), fix, and validate bug reports against the codebase, with optional branch isolation and PR creation; per-bug reports stored under .specify/bugs/<slug>/"
extensions/bug/extension.yml:16
- The new branch/worktree and PR paths invoke Git, but the manifest declares only
gh. Add optionalgitmetadata as the bundled Git extension does atextensions/git/extension.yml:14-16, otherwise prerequisite/tool reporting omits a tool required by these features.
tools:
- name: gh
required: false
extensions/bug/commands/speckit.bug.fetch.md:37
- Live fetches by URL or
owner/repo#nare unnecessarily blocked unless the current directory is a Git repository with a GitHub origin. Those input forms already supply the repository, so only a bare issue number should depend on the current remote.
- Detect GitHub context (same as `__SPECKIT_COMMAND_BUG_ISSUE__`):
- `git rev-parse --is-inside-work-tree 2>/dev/null` to confirm a repository.
- `git config --get remote.origin.url` to read the remote; parse `owner`/`repo` (HTTPS `https://github.com/<owner>/<repo>.git` or SSH `git@github.com:<owner>/<repo>.git`). Only proceed with a live fetch when the remote points to `github.com`.
- `command -v gh >/dev/null 2>&1` and `gh auth status` to confirm the CLI and auth. If `gh`/GitHub remote/auth is unavailable, skip the live fetch and write a draft (see Graceful Degradation).
extensions/bug/commands/speckit.bug.pr.md:39
bug.fixonly edits files and never commits them, butgh pr createcannot include uncommitted changes. Require the intended fix to be committed (and the working tree clean) before creating the PR, or explicitly obtain consent to create a commit here; pushing an unchanged branch and retrying will not solve this failure.
- `BUG_DIR/fix.md` MUST exist. If it does not, stop and instruct the user to run `__SPECKIT_COMMAND_BUG_FIX__` first.
- Confirm the current branch is the fix branch (created by `bug.fix --branch`, or whatever branch holds the change). If the working tree is on `main`/`master` with uncommitted changes, warn the user and ask which branch to open the PR from before continuing.
extensions/bug/commands/speckit.bug.fix.md:54
- Creating the branch without checking the working tree carries all pre-existing edits onto the fix branch, so the advertised isolation can mix unrelated work into the eventual commit/PR. Inspect Git status first and ask the user to commit, stash, or cancel when changes predate this fix.
- **Branch mode** (`--branch`): run `git checkout -b <prefix>/<BUG_SLUG>` from the current branch (assumed clean or committed).
extensions/bug/README.md:9
- The overview says
assessdoes not file an issue, but two lines later documents its--issue/config auto-create behavior. Qualify this as the default behavior so the workflow description is internally consistent.
This issue also appears on line 115 of the same file.
1. **Assess** — read a bug report (pasted text or a URL), judge whether it is a real bug, locate suspected code paths, and propose a remediation. `assess` writes a *local* assessment only; it does **not** file a GitHub issue.
extensions/bug/README.md:14
- The ordered workflow repeats step 3 and leaves the final stage numbered 5 even though six stages are listed. Renumber these entries to preserve the documented sequence.
3. **Fix** — apply the proposed remediation and record exactly what changed. Pass `--branch` (or `--worktree`) to isolate the fix on its own git branch.
4. **Open PR** (optional) — `speckit.bug.pr` opens a pull request from the fix branch, linking the issue.
5. **Test** — re-run the reproduction and any added tests, then record the verification result.
extensions/bug/README.md:98
- Without an explicit slug,
fetchderives one from the issue title, so it is not guaranteed to becallback-token. Using that hard-coded slug in the next commands can target a different bug or fail; tell users to reuse the slug reported byfetch.
# Then proceed straight to the fix on its own branch
/speckit.bug.fix slug=callback-token --branch
/speckit.bug.pr slug=callback-token
tests/extensions/bug/test_bug_extension.py:64
- This only proves the source template exists; it does not verify the new manifest entry or the promised deployment to
.specify/extensions/bug/bug-config.yml. Extend the install test to invoke config scaffolding and assert the deployed file/content, so a brokenprovides.configdeclaration is caught.
def test_config_template_exists(self):
assert (EXT_DIR / "config-template.yml").is_file()
extensions/bug/commands/speckit.bug.fetch.md:28
- Slug resolution requires the fetched issue title before the fetch occurs, and when
ghis unavailable there is no title at all, so the promised offlineissue-draft.mdpath cannot determineBUG_DIR. Parse the reference and fetch first, then derive the title-based slug; define a fallback such asissue-<number>for degraded fetches.
Each bug gets its own directory under `.specify/bugs/<slug>/`. If the user passed a slug, use it verbatim after normalization (lowercase, hyphen-separated, no spaces, no special characters other than `-` and digits). Otherwise derive a 2–4 word kebab-case slug from the issue **title**. Ensure the directory is unique — if `.specify/bugs/<slug>/` already exists, append the shortest disambiguating suffix (`-2`, `-3`, …) or `-<issue-number>`. Never overwrite an existing bug directory.
After resolution, set `BUG_SLUG` and `BUG_DIR = .specify/bugs/<BUG_SLUG>`.
extensions/bug/README.md:115
- This absolute statement conflicts with the documented
--issueandauto_create_issuemodes, where assess does touch GitHub. State that assess is local-only by default and identify those explicit exceptions.
- **Assess** means *triage a report into a local `assessment.md`* — it never touches GitHub. Use it for a bug described in pasted text or a URL.
| 1. **Explicit opt-in**: if the user passed a truthy `issue` / `--issue` flag (or `issue=true`), file the issue now. | ||
| 2. **Config opt-in**: read `.specify/extensions/bug/bug-config.yml` (scaffolded at install). If it exists and `auto_create_issue` is `true` (or `1` / `yes` / `on`), file the issue now — the user enabled this explicitly, so no further confirmation is required. | ||
| 3. **Otherwise**: do not file it; only **suggest** the issue step in the report-back below. |
| 2. **Fetch the issue (live path)** | ||
| - Run (no `--json` — it is unsupported on older `gh`; read the rendered output instead): | ||
| ```bash | ||
| gh issue view <number> --repo <owner>/<repo> |
Extend the bundled Bug Triage Workflow extension:
specify spec.Also updates extension.yml (new commands + gh tool), config-template.yml, README, catalog.json, and the extension test (EXPECTED_COMMANDS -> 6).
Assisted-by: Kimi Code (autonomous)
Description
Testing
uv run specify --helpuv sync && uv run pytestAI Disclosure