Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 76 additions & 14 deletions extensions/bug/README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
# Bug Triage Workflow Extension

A three-step bug triage workflow for Spec Kit: assess, fix, and validate. Each bug lives in its own directory under `.specify/bugs/<slug>/`, with one Markdown report per stage.
An end-to-end bug triage workflow for Spec Kit: assess, report (GitHub issue), fix, open a PR, and validate. Each bug lives in its own directory under `.specify/bugs/<slug>/`, with one Markdown report per stage.

## Overview

This extension delivers an opinionated, repeatable bug workflow that any AI coding agent can drive:

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.
2. **Load** (alternative entry point) — `speckit.bug.fetch` pulls an *existing* GitHub issue (by number, URL, or `owner/repo#n`) via `gh`, records it as `issue.md`, and seeds an `assessment.md` draft. Use this when the bug is already tracked on GitHub and you want to triage and fix it here — instead of starting from a pasted report.
3. **Report** (optional) — `speckit.bug.issue` turns an assessment into a tracked GitHub issue via `gh`, recording the issue link. `assess` can auto-trigger this with `--issue` or the `auto_create_issue` config. `fetch` already produces `issue.md`, so `issue` is normally skipped after a load.
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.

The three stages communicate through three Markdown files in a single per-bug directory:
The stages communicate through Markdown files in a single per-bug directory:

```
.specify/bugs/<slug>/
├── assessment.md # written by speckit.bug.assess
├── fix.md # written by speckit.bug.fix
└── test.md # written by speckit.bug.test
├── assessment.md # written by speckit.bug.assess
├── issue.md # written by speckit.bug.issue or speckit.bug.fetch (issue number + URL)
├── issue-body.md # issue body draft used by speckit.bug.issue
├── issue-draft.md # fallback when gh/GitHub is unavailable
├── fix.md # written by speckit.bug.fix
├── pr.md # written by speckit.bug.pr (PR number + URL)
├── pr-body.md # PR body draft used by speckit.bug.pr
├── pr-draft.md # fallback when gh/GitHub is unavailable
└── test.md # written by speckit.bug.test
```

## Commands

| Command | Description | Output |
|---------|-------------|--------|
| `speckit.bug.assess` | Triages a bug report (pasted text or URL) against the codebase. | `.specify/bugs/<slug>/assessment.md` |
| `speckit.bug.fix` | Applies the remediation from the assessment. | `.specify/bugs/<slug>/fix.md` |
| `speckit.bug.issue` | Files a GitHub issue from the assessment (the "report" phase). | `.specify/bugs/<slug>/issue.md` |
| `speckit.bug.fetch` | Loads an existing GitHub issue (`issue.md`) and seeds a triage draft. | `.specify/bugs/<slug>/issue.md` + `assessment.md` |
| `speckit.bug.fix` | Applies the remediation from the assessment (`--branch`/`--worktree` to isolate). | `.specify/bugs/<slug>/fix.md` |
| `speckit.bug.pr` | Opens a PR for the fix, linking the issue. | `.specify/bugs/<slug>/pr.md` |
| `speckit.bug.test` | Validates the fix and records the verification report. | `.specify/bugs/<slug>/test.md` |

## Slug Conventions
Expand Down Expand Up @@ -55,26 +67,76 @@ specify extension enable bug
## Typical Flow

```bash
# 1. Triage a bug from a pasted stack trace
# 1. Triage a bug from a pasted stack trace (or pass --issue to file the GitHub issue now)
/speckit.bug.assess "TypeError: cannot read properties of undefined (reading 'token') at /auth/callback"

# 2. Triage a bug from a GitHub issue URL
/speckit.bug.assess https://github.com/example/repo/issues/1234 slug=callback-token

# 3. Apply the proposed fix
/speckit.bug.fix slug=callback-token
# 3. File the GitHub issue (the "report" phase) — skipped if assess ran with --issue
/speckit.bug.issue slug=callback-token

# 4. Validate the fix
# 4. Apply the proposed fix on its own branch (or pass --worktree for a separate worktree)
/speckit.bug.fix slug=callback-token --branch

# 5. Open a PR from the fix branch, linking the issue
/speckit.bug.pr slug=callback-token

# 6. Validate the fix
/speckit.bug.test slug=callback-token

# --- Alternative entry point: load an issue that already exists on GitHub ---
# Load issue #1234 (from the current repo) and seed a triage draft
/speckit.bug.fetch 1234

# Load by URL or owner/repo#n
/speckit.bug.fetch https://github.com/example/repo/issues/1234
/speckit.bug.fetch example/repo#1234

# Then proceed straight to the fix on its own branch
/speckit.bug.fix slug=callback-token --branch
/speckit.bug.pr slug=callback-token
```

## Configuration

The extension reads `.specify/extensions/bug/bug-config.yml` (copied from `config-template.yml` on install). Options:

- `auto_create_issue` (`false`) — when `true`, `speckit.bug.assess` files the GitHub issue automatically after writing the assessment. The `--issue` flag overrides this per run.
- `branch_prefix` (`"fix"`) — prefix for the fix branch created by `speckit.bug.fix --branch` / `--worktree` (branch is `<prefix>/<slug>`, e.g. `fix/login-timeout`).
- `default_host` (`"github"`) — Git host used when creating issues/PRs.

## Branch Isolation

`speckit.bug.fix --branch` creates `<prefix>/<slug>` and checks it out before editing, so the fix is isolated like feature work from `specify spec`. `--worktree` instead runs `git worktree add` into a sibling directory. If Git is unavailable, the fix is applied to the current branch with a warning. `speckit.bug.pr` then opens a PR from that branch.

## Assess vs Load vs Report

- **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.
- **Load** (`speckit.bug.fetch`) means *pull an issue that already exists on GitHub* into `issue.md` and seed an `assessment.md` draft. Use it when the bug is already tracked and you want to work on it here. It is the read-only complement of "Report".
- **Report** (`speckit.bug.issue`) means *file the bug as a new GitHub issue* from an assessment. After a `fetch`, the issue is already loaded, so "Report" is normally skipped — `fetch` and `issue` both produce `issue.md`, and `bug.issue` refuses to create a duplicate when one already exists.

This separation keeps triage read-only and lets you decide per bug whether it is worth tracking.

## Guardrails

- `speckit.bug.assess` and `speckit.bug.test` **never modify source code**. They read the repository and write only inside `.specify/bugs/<slug>/`.
- `speckit.bug.issue` and `speckit.bug.pr` are opt-in **external** actions (they call the `gh` CLI). They never edit repository source; when `gh`/GitHub is unavailable they write a local draft (`issue-draft.md` / `pr-draft.md`) instead of erroring.
- `speckit.bug.fix` is the only command that edits source code, and it stays within the files listed in the assessment unless new evidence requires expanding scope (which is logged in `fix.md` under **Deviations from Assessment**).
- None of the commands overwrite an existing report file without explicit confirmation; in automated mode they refuse and pick a new unique slug instead.
- Verdicts and verification results are never over-claimed: a reproduction that was not actually performed is reported as `partial` or `not-run`, not `verified`.

## Hooks

This extension registers no hooks. The three commands are always invoked explicitly by the user.
This extension registers one hook:

- **`after_bug_assess`** → `speckit.bug.issue` — a **mandatory** (Automatic) hook that files the
GitHub issue after `speckit.bug.assess` writes the assessment. It is bound as `optional: false` but
gated by the `auto_create_issue` config condition (`config.auto_create_issue == 'true'`), so it only
executes when that setting is enabled. With `auto_create_issue: false` (the default) the condition
fails and the hook is skipped, so `assess` simply *suggests* the issue step as before.

This is how the `auto_create_issue` setting takes effect: it is not re-read inline by `assess`. Instead,
when `auto_create_issue: true`, the installed hook is an Automatic Hook (`optional: false`) that runs
`speckit.bug.issue` automatically after each assessment. Toggle the setting in
`.specify/extensions/bug/bug-config.yml` — no reinstall is required.
21 changes: 20 additions & 1 deletion extensions/bug/commands/speckit.bug.assess.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The user input contains the bug description and (optionally) a slug. Treat it as
1. **Pasted text** — a copy of an issue, a stack trace, an error message, or a freeform description.
2. **A URL** — a link to a GitHub/GitLab issue, a discussion, a Sentry/log link, a forum thread, or any web page describing the bug. Fetch and read the page content before proceeding.
3. **A mix** — text plus a URL for additional context.
4. **An `issue` flag** — `issue` / `--issue` (or `issue=true` / `issue=false`). When present and truthy, this command also files a GitHub issue for the bug after writing the assessment (the "report" phase). See **Optional — file the GitHub issue** below.

If both a URL and text are present, fetch the URL and merge its content with the pasted text when forming the bug summary.

Expand Down Expand Up @@ -159,15 +160,33 @@ Do not attempt to validate the URL by issuing a preflight `HEAD` (or any other)
- [NEEDS CLARIFICATION: …]
```

### Optional — file the GitHub issue (report phase)

By default, `assess` only writes a **local** assessment; it does **not** file a GitHub issue. "Assess" means *triage*, not *report*. File the issue in one of two ways:

1. **Explicit opt-in (per run)**: if the user passed a truthy `issue` / `--issue` flag (or `issue=true`), file the issue now by performing the same procedure as `__SPECKIT_COMMAND_BUG_ISSUE__` for this slug: read the assessment you just wrote, create the GitHub issue via `gh`, and record `BUG_DIR/issue.md`.
2. **Automatic opt-in (config-driven hook)**: after writing the assessment, check the project's extension hooks for the `after_bug_assess` event (via the extension hook executor / `.specify/extensions.yml`). If an **Automatic Hook** is returned, execute it — the bug extension registers one (`optional: false`, gated by the `auto_create_issue` config) that runs `__SPECKIT_COMMAND_BUG_ISSUE__` for this slug. With `auto_create_issue` set to `true` in `.specify/extensions/bug/bug-config.yml` the hook condition is satisfied, so the issue is filed **automatically** (no further confirmation). If no hook is returned — e.g. the setting is `false` or absent — do not file the issue.

**Otherwise** (neither path triggered): do not file the issue; only **suggest** the issue step in the report-back below.

When filing, if `gh` / GitHub remote / auth is unavailable, write `BUG_DIR/issue-draft.md` and note it — do not error.

> The `auto_create_issue` config is honored through the `after_bug_assess` hook, not by re-reading the config inline here. That keeps create-issue out of the agent's "optional" path: with the setting enabled the hook is registered as `optional: false` and fires automatically.

7. **Report back** with:
- The slug used and whether it was user-provided, asked-for, or auto-generated. State it on its own line (e.g. `Slug: <BUG_SLUG>`) so it is easy to spot — downstream commands in the same session may reuse it from context without re-prompting.
- The path `.specify/bugs/<BUG_SLUG>/assessment.md`.
- The verdict and severity.
- The next suggested step: `__SPECKIT_COMMAND_BUG_FIX__ slug=<BUG_SLUG>`.
- A one-line clarification: `assess` = local triage (this file); "report" = the GitHub issue created by `__SPECKIT_COMMAND_BUG_ISSUE__`.
- A note that if the bug is **already** tracked as a GitHub issue you want to work on, you can skip pasting it here and instead load it with `__SPECKIT_COMMAND_BUG_FETCH__` (by issue number / URL / `owner/repo#n`), which records `issue.md` and seeds this assessment for you.
- The next suggested steps, in order:
- If the issue was NOT yet filed: `__SPECKIT_COMMAND_BUG_ISSUE__ slug=<BUG_SLUG>` (file the GitHub issue).
- Then: `__SPECKIT_COMMAND_BUG_FIX__ slug=<BUG_SLUG>` (apply the remediation; add `--branch` or `--worktree` to isolate the fix on its own branch).

## Guardrails

- Never modify source files during assessment — this command only reads and writes inside `.specify/bugs/<slug>/`.
- Never invent reproduction steps or file paths that are not supported by either the report or the codebase.
- Never overwrite an existing `assessment.md` without confirmation.
- If the bug report cannot be understood at all (empty, unrelated, spam), set verdict to `invalid` with a clear reason and stop.
- Filing a GitHub issue (only when the `issue` flag or `auto_create_issue` config is set) is an opt-in external action. It never modifies repository source and degrades to a local `issue-draft.md` when `gh` / GitHub is unavailable.
Loading