Skip to content

vt-d-triage-bugs

Triage pending bug reports — promote to bugs/, defer, or mark as duplicate

Plugin: vt-product-dev
Category: Other
Command: /vt-d-triage-bugs


/vt-d-triage-bugs — Bug Triage Skill

Process pending bug reports in intake/pending/bugs/. Bulk-archive any whose status is already terminal (resolved/fixed/duplicate/wontfix), then for each remaining open bug decide: promote to the tracked bugs backlog, defer for later, or close as duplicate.

Invocation

/vt-d-triage-bugs

Execution

Step 1: Scan Pending Bugs

Glob intake/pending/bugs/BUG-*.md.

For each file, read YAML frontmatter and partition by status::

status: value Handling
deferred Skip silently — already triaged as "later".
resolved, fixed, duplicate, wontfix Terminal. Do not offer Promote/Defer/Duplicate — the work is finished. Collect these and archive them in bulk (Step 1b).
new, reported, pending, triaged, or absent Triage normally (Step 2).
anything else Triage normally, but print ⚠ unknown status "{value}" in BUG-NNN — add it to the table in Step 1. Never silently treat an unrecognised status as open.

Why this partition exists. A bug fixed directly — without ever being promoted to bugs/NNN-slug/ — keeps its intake file with status: resolved. Nothing retires it, so it counts as open work in every triage run and every dashboard. A 2026-08-12 triage found 7 of 15 pending bugs already resolved this way. Offering Promote/Defer/Duplicate for those forces a wrong answer: Promote creates a backlog entry to investigate solved work, and Duplicate is false.

If no files at all:

No pending bugs to triage.
Exit.

Step 1b: Archive Terminal Bugs

If the partition above found any terminal-status bugs, list them and confirm once for the whole set (not per bug) via AskUserQuestion:

Already resolved — not open work (N):
  BUG-023  medium  resolved
  BUG-029  high    resolved 2026-08-05
  ...
  • Archive all — for each: mkdir -p intake/processed/bugs/, then git mv intake/pending/bugs/BUG-NNN.md intake/processed/bugs/BUG-NNN.md. Preserve the existing status — do not rewrite resolved to triaged; the archived record must stay truthful.
  • Leave pending — change nothing; they will resurface on the next run.

Report per file: ⤓ BUG-NNN archived (status: resolved).

If a terminal bug does have a tracked bugs/NNN-*/ directory, archive it the same way — the tracked directory is the live record and the intake file is the duplicate.

Then continue to Step 2 with the normal-triage set only.

Step 2: Process Each Bug

For each pending bug (sorted by filename):

Display summary:

───────────────────────────────────────────────────────
BUG-NNN: [title]
Severity: [severity]
Reported: [reported_at]
───────────────────────────────────────────────────────
Steps: [steps to reproduce, truncated to 3 lines]
Expected: [expected behavior]
Actual: [actual behavior]
───────────────────────────────────────────────────────

Ask via AskUserQuestion: "What should we do with BUG-NNN?" Options: - Promote — move to bugs/ backlog for investigation - Defer — keep in intake, mark deferred (skip in future triage) - Duplicate — close as duplicate of an existing bug

If Promote:

  1. ID-collision guard — run this BEFORE creating anything. Check whether the ID is already taken by a different bug:
    ls -d bugs/${NNN}-* 2>/dev/null
    
    If a directory exists, compare its report.md H1 against this bug's title. If they describe different bugs, STOP and ask the user via AskUserQuestion — do not mkdir a second bugs/${NNN}-* directory, and do not write a bugchen_status: BUG-NNN entry, which would overwrite the existing bug's record.

When renumbering, the next free ID is the max across every branch, not just the current worktree:

{ git ls-tree -r --name-only --full-tree $(git for-each-ref --format='%(refname)' refs/heads/) \
    -- bugs/ intake/pending/bugs/ intake/processed/bugs/ 2>/dev/null
  ls -d bugs/*/ intake/*/bugs/* 2>/dev/null
} | grep -oE 'bugs/[0-9]{3}-|BUG-[0-9]{3}' | grep -oE '[0-9]{3}' | sort -n | tail -1
The bugs/[0-9]{3}- and BUG-[0-9]{3} anchors are load-bearing. A bare [0-9]{3} also matches the 202 in date-named files such as intake/processed/bugs/2026-05-18-pre-commit-hook-allow-merge-commits.md, which floors the max to 202 and hands out an absurd ID. Requiring the trailing - after three digits rejects bugs/2026-… while still matching bugs/063-…. This is the same defect that poisoned allocate_spec_id.py (it returned SPEC-2031 against a real max of 170); do not "simplify" the pattern. Allocating the lowest ID free on main is exactly how the collision recurs: an unmerged branch may already hold that ID and will collide when it merges. A real 2026-08-12 case — intake/pending/bugs/BUG-050.md and bugs/050-install-exits-zero-without-registering/ were unrelated bugs sharing an ID; main's max was 056 but feature/spec-170 already held 057–062, so the safe allocation was 063, not 057.

This is a workaround, not a fix. The root cause is BUG-047 — bug IDs have no allocator, so parallel sessions collide. Until that lands, every promote is a chance to collide.

  1. Build slug from title: lowercase, spaces → hyphens, max 30 chars, strip special chars Example: "Login fails on Safari" → login-fails-on-safari
  2. Run mkdir -p bugs/NNN-slug/ (NNN = zero-padded from id: field)
  3. Copy intake file content → bugs/NNN-slug/report.md
  4. Resolve the bug's domain (SPEC-122):
  5. Read domain: from the source frontmatter
  6. If present, preserve as-is in bugs/NNN-slug/report.md (the copy in step 3 already carries it; no edit needed)
  7. If absent (legacy bug authored before SPEC-122), prompt the user via AskUserQuestion. Build the option list dynamically from intake/projects.yaml (unique domain: values across projects: entries), and append cross-cutting as a fixed final choice
  8. Write the chosen value into the domain: frontmatter field of bugs/NNN-slug/report.md (and into the intake copy at step 7 below so the archived record stays consistent)
  9. Write bugs/NNN-slug/state.yaml:
    status: triaged
    id: BUG-NNN
    promoted_at: "[ISO-8601 now]"
    
  10. Update .design-state.yaml: add entry under bugchen_status::
    bugchen_status:
      BUG-NNN:
        dir: bugs/NNN-slug/
        title: [title]
        severity: [severity]
        status: triaged
        domain: [domain from step 4]
        reported_at: [reported_at from source frontmatter]
    
    If bugchen_status: key does not exist in .design-state.yaml, add it. The status:, domain:, and reported_at: fields are required by SPEC-122 parse_bugchen_status for dashboard filtering and rendering (matches data-model.md Entity 3 schema).
  11. Update intake file frontmatter: set status: triaged (so the archived copy reflects final disposition). Also write the resolved domain: value from step 4 if the intake file lacked it.
  12. Ensure intake/processed/bugs/ directory exists (run mkdir -p intake/processed/bugs/ if needed)
  13. Run git mv intake/pending/bugs/BUG-NNN.md intake/processed/bugs/BUG-NNN.md
  14. Report: ✓ BUG-NNN promoted to bugs/NNN-slug/

If Defer:

  1. Edit intake/pending/bugs/BUG-NNN.md frontmatter: set status: deferred
  2. Report: → BUG-NNN deferred

If Duplicate:

  1. Ask via AskUserQuestion: "Duplicate of which bug? (e.g. BUG-005)"
  2. Edit intake file frontmatter: set status: duplicate, add duplicate_of: [answer]
  3. Ensure intake/processed/bugs/ directory exists (run mkdir -p intake/processed/bugs/ if needed)
  4. Run git mv intake/pending/bugs/BUG-NNN.md intake/processed/bugs/BUG-NNN.md
  5. Report: ✗ BUG-NNN closed as duplicate of [answer]

Step 3: Summary

After all bugs are processed, display:

Triage complete
───────────────────────────────────────────────────────
Archived:  N  (already resolved → intake/processed/bugs/)
Promoted:  N  (ready for /vt-d-investigate-bug)
Deferred:  N  (will skip on next triage)
Duplicate: N  (moved to intake/processed/bugs/)
Renumbered: N (ID collision — list old → new)
───────────────────────────────────────────────────────

Promoted bugs ready for investigation:
  BUG-NNN  [title]  →  bugs/NNN-slug/
  ...

Next: /vt-d-investigate-bug BUG-NNN  to investigate a promoted bug