Skip to content

vt-d-quickfix

Apply a quick-fix with automatic audit trail. For small, self-contained changes (typos, label corrections, minor UI tweaks) that do not warrant a full spec workflow. Creates an audit file in intake/quick-fixes/, branches, applies the fix, and optionally creates a PR.

Plugin: vt-product-dev
Category: Other
Command: /vt-d-quickfix


/vt-d-quickfix

Quick-Fix Intake Path with Audit Trail (SPEC-133).

Invoke as: /vt-d-quickfix "Fix typo in dashboard heading"


When to Use

  • Small, self-contained changes (typos, label corrections, color tweaks, copy fixes)
  • Change is obvious and unambiguous — no design decision required
  • Fix takes less than 30 minutes to implement
  • No risk of breaking other features

A change that is more than trivial but not spec-worthy runs as class small-change (Step 3a): this skill writes its lane record and branch, then hands over to the build, review and finalize skills.

When NOT to use: if the fix requires architectural decisions, affects multiple systems, or has unknowns → use /vt-d-spec-from-requirements instead.

The boundary between quickfix, small-change and the full workflow is stated once, in constitution Principle II, "Sanctioned lane" (.specify/memory/constitution.md). This skill links to it and does not restate it — reason: two statements of one boundary drift apart (SPEC-164 FR-5).


Execution

Step 0.5: Project Registration Check

Run the shared registration check — single source of truth. Do NOT inline the logic here (duplicating it re-introduces the cross-copy drift SPEC-150 removed):

bash ~/.claude/skills/vt-c-project-register/scripts/check_registration.sh

Act on the emitted status line (stdout); any line other than UNREGISTERED … means continue silently (keeps the gate soft even if the vocabulary grows): - REGISTERED, TOOLKIT_REPO, NOT_GIT, or DEGRADED … → continue silently. - UNREGISTERED skips=N escalate=<bool> → note it and continue (soft gate — never block): - Do NOT ask — reason: registration writes to the toolkit's registry in another repo and is its own interactive flow, so it must not interrupt this workflow. Print one notice line instead: "ℹ This project isn't registered in the toolkit, so its learnings won't reach the intake pipeline. Run /vt-c-project-register when convenient." - If escalate=true, strengthen the wording: note that learnings have not been reaching the toolkit across N sessions. - Only if the user explicitly asks to register → invoke /vt-c-project-register. - Then continue. Do NOT --record-skip here — reason: a skip records that the user DECLINED registration, and this phase does not ask. The counter feeds the session-start escalation ("You have skipped this several times"), so bumping it where nobody was asked makes that sentence false. Recording follows the ASK, not the phase (docs/solutions/patterns/fork-safe-single-source-stub.md).

Step 1: Parse Argument

Read the argument passed to /vt-d-quickfix. This becomes the initial title for the audit entry and the git commit message.

If no argument was provided, ask for a description in Step 2.

Step 2: Collect Context

Derive these from the argument and the conversation — do not ask (reason: a quick fix that opens with a questionnaire is no longer quick). Use AskUserQuestion only when there is no argument and no description anywhere in the conversation: - description — one-line description of what to fix (pre-filled from argument if provided) - trigger — what prompted this fix (e.g., "PM noticed typo during demo", "spotted in QA review") - reason — short human-readable rationale (e.g., "Dashboard heading misspelled") - domain — optional product area tag (e.g., "VisiMatch", "visitrans.de"; blank = omit) - affected files — optional list of files to edit (blank = Claude identifies candidates from description)

trigger and reason default to what the user said (e.g. "user request in session"); domain stays blank unless obvious.

Step 3: Route Decision

Decide the class yourself and print one decision line. Judge it against Principle II, "Sanctioned lane" (.specify/memory/constitution.md):

  • "Fix now" — class quickfix (default) — a trivial request: small and local, a few lines, no new behavior, no data model or API change → continue to Steps 4–9. A trivial request stays class quickfix; do not send it through the lane (EC-1).
  • "Small change" — class small-change — more than trivial, but not spec-worthy: within Principle II's lane bound and not architectural → go to Step 3a. Steps 4–9 do not apply to this class.
  • "Create a Spec instead" — spec-worthy: beyond the lane bound, architectural, or the user asked for a spec → invoke /vt-d-spec-from-requirements and exit

If you route to "Create a Spec", hand off to /vt-d-spec-from-requirements with the description collected in Step 2. EXIT after handoff.

Developer override (EC-3). If the developer names a class, honour it and say so in the decision line. An override up to small-change still gets Step 3a's intake-check and diff-check. An override down to quickfix is not re-checked by anything, because that class never reaches review — Principle II names this as a stated residual, so say it in the decision line too.

Step 3a: The small-change lane

The lane runs this skill (record + branch) → /vt-d-3-build → /vt-d-4-review → /vt-d-5-finalize → /vt-c-session-journal. This skill writes the lane record and the branch, then hands over. It does not build, review or finalize itself — reason: those skills own their gates, and a copy of their steps here would drift from them (FR-3).

Calling the lane helper. Every lane-detect.sh call below is written in the cross-plugin form "$CLAUDE_PLUGIN_ROOT/../vt-base/bin/lane-detect.sh": the helper lives in the base plugin, while $CLAUDE_PLUGIN_ROOT names this skill's plugin. If $CLAUDE_PLUGIN_ROOT is unset or that file does not exist, run the copy relative to this skill file's real directory, ../../../vt-base/bin/lane-detect.sh. If neither exists, stop and report both paths — never decide the lane from memory, because the helper is the one place the rule is applied (review and finalize call it too).

  1. Read the bound. Run "$CLAUDE_PLUGIN_ROOT/../vt-base/bin/lane-detect.sh" bound. Exit 2 → stop and report that Principle II's bound marker is missing or broken. If the output ends in recalibrate=due, tell the developer in one line that Principle II's provisional bound is due for re-evaluation (D11), then continue.
  2. Write the lane record at intake/quick-fixes/{filename}, using Step 4's slug, filename and fixed_by rules and its path sanitizing:
---
id: YYYY-MM-DD-HH-MM-{slug}
title: {title from Step 2}
date: {ISO 8601 timestamp}
trigger: {trigger from Step 2}
reason: {reason from Step 2}
class: small-change
goal: {what the change achieves, in one line}
intended_files:
  - {repo-relative path}
bound: {the output of step 1, verbatim}
architectural: {true|false}
commit: ""
files: []
fixed_by: {git config user.name}
branch: fix/qf-YYYY-MM-DD-{slug}
---

intended_files is written now, before the build: the diff re-check compares the real diff with it. 3. Check the record before any branch exists. Run "$CLAUDE_PLUGIN_ROOT/../vt-base/bin/lane-detect.sh" intake-check intake/quick-fixes/{filename}. On exit 3, delete the record, print the helper's reason, and hand the collected context over to /vt-d-spec-from-requirements — EXIT (EC-2, FR-4). On exit 2, stop and report what the helper printed. 4. A bug goal? If the goal fixes a defect whose cause is not yet known, point the developer first to /vt-d-systematic-debugging or /vt-d-root-cause-tracing, or to /vt-d-6-operate for a production incident. This skill does not investigate — reason: those skills own the method (FR-6). 5. Create the branch.

git checkout -b fix/qf-YYYY-MM-DD-{slug}
6. Commit the record as the branch's first commit.
git add intake/quick-fixes/{filename}
git commit -m "chore: lane record for {title}" -m "Quick-Fix-Ref: intake/quick-fixes/{filename}"
Only a committed record added on this branch makes it a lane run; an uncommitted one does not count. 7. Build. Run /vt-d-3-build for the change described by goal. 8. Re-check the diff before review. Run "$CLAUDE_PLUGIN_ROOT/../vt-base/bin/lane-detect.sh" diff-check intake/quick-fixes/{filename}. - Exit 3 → stop: the diff outgrew the lane, or the record is architectural. Hand over to /vt-d-spec-from-requirements; do not review it as a lane run (EC-4). - Exit 4 → there is no base the remote confirms. Apply the fix the helper prints on stderr (for example git fetch origin) and re-run; never treat it as zero files. - Paths listed after outside= were not in intended_files: name them to the developer. They do not escalate on their own. 9. Review, finalize, journal. Run /vt-d-4-review, then /vt-d-5-finalize, then /vt-c-session-journal. Review runs spec compliance with this record as the stand-in spec, and finalize fails a spec-less branch that has no lane record.

Step 4: Generate Slug and Create Audit File

Derive slug from title: 1. Lowercase the title 2. Replace all non-alphanumeric characters with - 3. Collapse consecutive - to single - 4. Strip leading and trailing - 5. Truncate to 40 characters max 6. Example: "Fix typo in Dashboard Heading" → fix-typo-in-dashboard-heading

Derive filename from current date/time (local time):

YYYY-MM-DD-HH-MM-{slug}.md
Example: 2026-05-14-22-05-fix-typo-in-dashboard-heading.md

Derive fixed_by automatically (no user prompt):

git config user.name

If intake/quick-fixes/ does not exist, create it:

mkdir -p intake/quick-fixes
touch intake/quick-fixes/.gitkeep

Write audit file at intake/quick-fixes/{filename}:

---
id: YYYY-MM-DD-HH-MM-{slug}
title: {title from Step 2}
date: {ISO 8601 timestamp}
trigger: {trigger from Step 2}
reason: {reason from Step 2}
domain: {domain from Step 2, or omit field if blank}
commit: ""  # see Step 8 — stays empty on no-upstream path; populated on upstream path
files: []
fixed_by: {git config user.name}
branch: fix/qf-YYYY-MM-DD-{slug}
---

<!-- Auto-generated by /vt-d-quickfix. Do not edit. -->

Security note: Sanitize the files: field values — strip any path traversal patterns (../, ~/, absolute paths). Only relative paths within the repo are valid.

Step 5: Create Quickfix Branch

git checkout -b fix/qf-YYYY-MM-DD-{slug}

If already on main or master, note: "Creating quick-fix branch from main." (advisory only, not blocking).

Step 6: Apply the Fix

Claude applies the fix directly using its edit tools.

If files were specified in Step 2: edit those files directly.

If no files were specified: identify candidate files from the description and edit them — no confirmation step (reason: the fix lives on its own branch and is reviewed in the PR).

After edits, present a diff summary and continue to Step 7. If the user objects later, re-examine the description and try again. After 3 rejected attempts, hand off to a spec.

Step 7: Stage and Commit

Stage the audit file and all changed fix files:

git add intake/quick-fixes/{filename} {...changed files...}

Commit with trailer:

fix: {title}

Quick-Fix-Ref: intake/quick-fixes/{filename}

The Quick-Fix-Ref: trailer makes this fix discoverable via:

git log --grep="Quick-Fix-Ref:"

Step 8: Update Audit File With File List

Patch the audit file: - files: [] → files: list of actually changed files (from git diff --name-only HEAD~1)

Leave commit: "" as-is — see Why no commit hash in the audit file below.

Amend safety check — check if branch has a remote upstream:

git rev-parse --abbrev-ref @{u} 2>/dev/null

  • If no upstream (exit code non-zero): branch is local only → safe to amend the audit file's files: list into the fix commit:
    git add intake/quick-fixes/{filename}
    git commit --amend --no-edit
    
  • If upstream exists: branch was already pushed → do NOT amend (rewrites published history). Create a second commit. In this path the fix-commit hash is already final, so it can be recorded:
    git rev-parse HEAD  # capture the fix-commit hash BEFORE adding the audit update
    # then patch audit file's commit: field with that hash, plus files: list
    git add intake/quick-fixes/{filename}
    git commit -m "chore: record commit hash in QF audit [no-qa]"
    

Why no commit hash in the audit file (no-upstream path)

A commit cannot contain its own hash. After git commit --amend, the hash changes, so any hash patched in before the amend points to a dead commit. There is no fixpoint — amending again just produces another dead hash. The skill historically tried to record this hash, which never worked cleanly.

Discovery of the canonical commit instead uses the Quick-Fix-Ref: trailer that Step 7 writes into the commit message:

git log --grep="Quick-Fix-Ref: intake/quick-fixes/{filename}"
This grep returns exactly one commit (the fix-commit, or after merge the merge-commit-of-record). No paradox.

Recovery: If amend fails for any reason, the audit file simply retains commit: "" and files: []. The user can re-run git add intake/quick-fixes/{filename} && git commit --amend --no-edit once the files list is patched in.

Step 9: Offer PR Creation

Do not ask — take Option A (pushing the fix/qf- branch and opening a PR is reversible and is how the fix gets reviewed). Use Option B or C only if the user said so, or if gh is not available/authenticated (then fall back to B, and to C if the push fails).

Option A: "Create PR now" (default)

gh pr create \
  --title "fix: {title}" \
  --body "$(cat <<'PRBODY'
## Summary

Quick-fix applied via `/vt-d-quickfix`.

## Quick-Fix Audit Entries

This PR contains 1 quick fix. See full context in the audit file below.

| Date | Title | Reason | Audit File |
|------|-------|--------|------------|
| {date} | {title} | {reason} | intake/quick-fixes/{filename} |

---
🔧 Applied via [/vt-d-quickfix](plugins/vt-product-dev/skills/quickfix/SKILL.md)
PRBODY
)"

Option B: "Push branch, I'll create PR manually"

git push -u origin fix/qf-{date}-{slug}
Print: "Branch pushed. Create PR at your convenience."

Option C: "Leave local — I'll handle it" Print: "Reminder: run git push -u origin fix/qf-{date}-{slug} when ready."


Edge Cases

Slug collision (two fixes in the same minute): append -2, -3 etc. to the filename if it already exists.

On main/master branch: warn "You're on main. A quickfix branch will be created from main." — advisory only, not blocking.

Audit file accumulation: all files in intake/quick-fixes/ are permanent. No cut/archive mechanism — files are small and git history is the ledger.

Escalation: if the fix turns out to be more complex than expected at any step, hand off to /vt-d-spec-from-requirements with the already-collected context and say why in one line.


Success Criteria

For class small-change (Step 3a):

  • the lane record carries class: small-change, goal, intended_files, bound and architectural, and is the branch's first commit
  • intake-check ran before the branch was created, and diff-check ran after the build and before review
  • the run handed over to /vt-d-3-build, /vt-d-4-review, /vt-d-5-finalize and /vt-c-session-journal instead of doing their work here

For class quickfix (Steps 4–9):

  • intake/quick-fixes/{filename}.md exists with all required frontmatter fields
  • files: field is populated (not []) after Step 8
  • commit: field is populated only on the upstream-exists path (no-upstream path leaves it empty — see Step 8 "Why no commit hash")
  • Quick-Fix-Ref: trailer present in the commit message — this is the canonical discovery path, independent of the commit: field
  • fix/qf- branch exists with the fix committed
  • PR body (if created) contains ## Quick-Fix Audit Entries section