feat(skills): add ticket-scope agent skill - #95
Conversation
Interrogates a work ticket until someone who did not write it could execute it correctly with no follow-up questions. learned-gaps.md is genericized for public use: internal ticket IDs, SDK/platform names, and project names removed, keeping the lessons and generic "Seen on" examples. The repo-local .ticket-scope/learned-gaps.md mechanism lets a consuming repo build its own team-specific gap list on top of this seed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
⊕ Entity-level changestools/agents/skills/ticket-scope/SKILL.md
tools/agents/skills/ticket-scope/learned-gaps.md
Summary: 15 added across 2 files functions and classes, not lines · sem |
| ## Compounding (learned-gaps) | ||
|
|
||
| - **On start:** read `learned-gaps.md` (repo-local `.ticket-scope/learned-gaps.md` if in a repo; otherwise the global file next to this skill) and fold its entries into the fields you check — these are gaps that caused rework on past tickets and should now be caught up front. | ||
| - **After a miss:** if a ticket that went through this skill later needed a redirect or rework because a field was still under-specified, append the field that was missed and the one question that would have caught it. |
There was a problem hiding this comment.
home.file in agent-skills.nix → in default mode the skill is sourced from the read-only Nix store, so the write-back silently fails and the compounding loop never grows. Fix: point the writable target at ~/.claude/… (or XDG data dir); reserve "next to this skill" for read-only seed entries. The read half (L94) is fine.
|
|
||
| Keep going until this is true: | ||
|
|
||
| > Could an engineer skilled in the relevant technology, but new to this codebase and its history, produce the intended result from this ticket alone — without asking anyone? |
There was a problem hiding this comment.
💬 [adversarial + product-lens · conf 75] No lightweight path — the maximal bar + one-question-at-a-time is heavy, so it gets run on big already-scrutinized tickets and skipped on the one-line tickets where a silent blank baseline still bites. Possible: fast-path triage (baseline + acceptance only, escalate on a surfaced gap). Advisory — skill works well as-is.
There was a problem hiding this comment.
Counter-evidence from the SHE-12 trial run (2026-08-15).
The worry here is that the heavy path gets run on big, already-scrutinized tickets and skipped on the one-liners. SHE-12 was the one-liner case: title "pre-work", two-sentence description, two checklist bullets.
It carried four contradictions — "pre-work" meaning one thing in the ticket and another in the build plan; two divergent self-check.sh files in two repos with different required-tool sets; a settled toolchain decision the target repo had silently drifted away from; and a bullet pointing at a days/README.md that exists on no branch. Seven days from a hard external deadline.
If a fast-path triage is added, "the ticket looks small" is the wrong trigger. Small tickets are where ambient context is most likely to be load-bearing and unwritten. Worth settling before designing the escalation heuristic.
| ## Flow | ||
|
|
||
| 1. Read the ticket and everything it links — PRs, related tickets, referenced docs. Identify which of the six fields are missing, vague, or contradictory. Treat an "N/A" in a field that plausibly needs content as missing, not resolved. | ||
| 2. Self-answer whatever the codebase and history can answer; mark those "proposed — confirm." |
There was a problem hiding this comment.
💬 [adversarial + product-lens · conf 75] Self-answered "proposed — confirm" pre-fills can get rubber-stamped: a plausible-but-wrong inferred baseline + confirm-fatigue = a wrong baseline that reads as resolved rather than blank. Possible: for baseline, require restating the value, not a yes/no. Advisory.
There was a problem hiding this comment.
Field-tested on SHE-12 (2026-08-15) — finding confirmed, and the proposed fix is the weaker half.
Ran the skill forward-mode on a two-bullet Linear ticket. My first-pass baseline came from a planning doc asserting "decided 2026-07-01: alycda/aoc-ffi-workshop is the private mirror," and I was about to present exactly that as "proposed — confirm."
The author asked "how did you determine the repo?" — which forced git remote -v and gh repo view. That inverted the picture: the private mirror's main is still Initial commit with all content parked on a feature branch, while the public repo — listed as an unresolved "name TBD" open item in the same doc — was the live surface carrying the actual work.
So a confirm-style question would have laundered a wrong baseline into a resolved field, exactly as predicted. But restating the value would not have caught it either. I'd have restated the wrong repo with full confidence, because the doc was internally consistent and merely stale.
The load-bearing rule sits upstream of question format: a baseline claim sourced from prose is a claim, not a fact — verify against the live system before offering it. Filed as a concrete amendment on L51.
| ## How to ask | ||
|
|
||
| - Use the **AskUserQuestion** tool for every question (in a plain chat interface, use its single-choice question equivalent). Never pose questions as plain prose — use the popup so the author can pick fast or type a custom answer. | ||
| - **One question at a time.** Wait for the answer before the next. Provide 2–4 concrete, realistic options plus the always-available custom field; skip generic Yes/No unless the question is genuinely binary. |
There was a problem hiding this comment.
💬 [adversarial · conf 75] No cap short of the maximal bar — a goal-only ticket blank across all six fields can produce open-ended Q&A and mid-way abandonment. Possible: soft cap + rank gaps by rework cost + allow emitting with low-priority fields marked "unresolved — confirm before assign." Advisory.
|
|
||
| Resolve each of these. The parenthetical is the failure mode leaving it blank produces. | ||
|
|
||
| 1. **Baseline / starting context** *(starting-context gap)* — which branch, release line, environment, or precondition the work targets. This is the field most often left blank, and the most expensive one: "everyone knows it's the release branch" is exactly the ambient context that never reaches someone working from what's written. An acceptance check can pass trivially against the wrong baseline, so the baseline has to be stated, not assumed. Preconditions include **cross-team scheduling**: a dependency someone "has to make sure is scheduled" is not a precondition until it's a tracked blocker with an owner who has agreed. And in a sub-ticket, **"see parent" is not a baseline** — the sub-ticket states its own slice: which platform/branch, and the sub-scope's own end state. |
There was a problem hiding this comment.
💬 [product-lens · conf 75] The "most expensive one" ordering is asserted, not evidenced. If a team's rework clusters on approach/acceptance instead, the interrogation leads with the wrong field. Possible: let learned-gaps own field priority rather than fixing it in prose. Advisory — noting for the record.
| - **One question at a time.** Wait for the answer before the next. Provide 2–4 concrete, realistic options plus the always-available custom field; skip generic Yes/No unless the question is genuinely binary. | ||
| - After each answer, acknowledge the decision in 1–2 sentences, then ask the next. | ||
| - **Resolve in dependency order** — settle the target release before asking about environment-specific behavior, settle the approach before asking about approach-specific acceptance criteria, and so on. | ||
| - **Explore before asking.** If a question can be answered from the codebase, the ticket's links, git history, or existing docs, find the answer yourself and present it for confirmation ("Looks like this targets `releases/stable/sdk-4.14` — correct?"). Only ask the author for what genuinely lives in their head. |
There was a problem hiding this comment.
Proposed change, from the SHE-12 trial run (2026-08-15).
This bullet treats "the codebase, the ticket's links, git history, or existing docs" as one undifferentiated source pool. They are not equally reliable, and baseline is the field where the difference bites: planning docs go stale silently while staying internally consistent, so a confidently-wrong baseline reads as well-sourced.
Concretely: a build plan asserted repo X was canonical. git remote -v + gh repo view showed X's main was Initial commit and the real work lived in repo Y — which the same doc still listed as an open, undecided item.
Suggested amendment:
Explore before asking — and rank your sources. […] For baseline claims specifically, prefer what the system reports (
git remote -v,gh repo view, branch state, file existence) over what a doc asserts — docs go stale while staying internally consistent. Verify before offering, and name the source you used.
Pairs with the L57 thread.
Adds the
ticket-scopeagent skill undertools/agents/skills/.What it does — Interrogates a work ticket until someone who didn't write it could execute it with no follow-up questions: resolves baseline/starting context, acceptance criteria, approach, out-of-scope, assumptions, and prior decisions. Forward (interactive) and retro (audit) modes.
Provenance — Ported from my global
~/.claude/skills/ticket-scope.learned-gaps.mdgenericized for public use: internal ticket IDs, SDK/platform names, and project names stripped; the lessons and generic "Seen on" examples kept. The repo-local.ticket-scope/learned-gaps.mdmechanism lets a consuming repo grow its own team-specific gap list on top of this seed.Review notes — Ran
ce-doc-review(coherence, feasibility, adversarial, product-lens) againstSKILL.md. Onesafe_autofix is already folded in: thedescriptionnow lists all six fields, not five. The skill has worked very well in practice. One finding is worth acting on and I've left it inline — the compounding write-back can't happen under this repo's read-only Nix-store deployment. The remaining reviewer findings are left inline for the record; I may or may not address them.FYI-tier (not acted on): retro cost-mapping can read as blame; the bar certifies "executable" not "worth doing"; the activation
descriptionis broad enough to over-trigger.Draft — not ready to merge.
🤖 Generated with Claude Code