scoped-planning
A per-stage scope-check overlay across the planning lifecycle (/brainstorming -> /grill-plan -> /writing-plans). Each stage is invoked separately as scoped-planning /<wrapped-skill>, wrapping that stage's base skill as a black box. The wrapped skill runs to its natural checkpoint (brainstorming's user-review gate, grill-plan's handoff, writing-plans' execution-handoff); scope-check guardrails are then applied post-hoc, before the artifact is handed off. The deliverable is the wrapped skill's normal output (spec / grilled plan / task plan), kept scope-clean - not a separate verdict report.
All scope-routing logic - scope inventory, IN/OWNED/GAP classification, gap rule, gap-ticket template - is defined here. The only external reference is to create-scoped-tickets, which is used to seed the ticket universe when it is empty; scope-routing itself is self-contained.
Inputs
--tickets <N,M,...>- one or more target ticket numbers in the label universe that this planning effort implements (e.g.4343,4380). Comma-separated. Required; omitting--ticketsis always an error.--label <label>- the issue label defining the ticket universe. Default:agent-work-ticket.<wrapped-skill>- the base skill to wrap: one of/brainstorming,/grill-plan,/writing-plans. This token selects the overlay mode; no separate--modeflag.- The wrapped skill's own inputs (idea/spec, plan draft, etc.) are passed through unchanged.
--repo is required when --tickets are bare numbers; inferred from full ticket URLs when --tickets are given as full URLs.
If the label universe is empty (no tickets exist yet), use the create-scoped-tickets skill first to seed it from a spec, then invoke /scoped-planning with the seeded ticket numbers.
Built-in defaults (never restated by the user)
- No-duplication check against every open ticket carrying
--labelin the repo. - Gap routing: every overscoped artifact point must map to a target ticket's owned scope, another open ticket's owned scope, or a proposed new ticket - never silently introduced as new scope.
- No-new-scope guard: an artifact point that fits neither a target ticket nor an existing open ticket is a gap, not a license to expand the plan.
- Post-hoc guardrails: the wrapped skill runs uninterrupted to its natural checkpoint; scope-checks apply after, before handoff.
Scope inventory
Run:
gh issue list -R <REPO> --label <LABEL> --state open --limit 200 --json number,title,body
For every ticket, extract what it OWNS ("The owner may" / "Scope of Work" or equivalent) and EXCLUDES ("The owner must not" / "Not In Scope" or equivalent). Produce a scope map: ticket number -> one-line ownership statement. Treat every ticket's ownership as exclusive. If work appears under another ticket's "owner may", it is out of scope here, full stop.
The authoritative set of recognized scope-section headings is: "The owner may", "The owner must not", "Not In Scope", "Scope of Work", "Definition of Done". If a ticket uses none of these, see Error handling.
IN/OWNED/GAP classification
Applied to every point of the wrapped skill's artifact (a spec section, an unresolved decision, a task line) at the stage's natural checkpoint:
- IN SCOPE - the point touches the union of the target tickets' owned scope. Silent pass; emit the artifact as the base skill would.
- OWNED BY ANOTHER TICKET - the point touches an area owned by a different open ticket in the scope map. Flag inline at the point of collision; name the owning ticket. Ask the user to narrow the artifact, reassign, or explicitly accept the cross-ticket work before handoff.
- GENUINE GAP - the point touches an area no open ticket owns. Flag inline at the unowned work. Stop and ask: (i) create a gap ticket now (embedded template) or (ii) narrow the artifact to drop the gap.
- Uncertain mapping - if you cannot map a point to a scope-map entry with confidence, default to GENUINE GAP and flag the uncertainty inline. The approval gate for gap tickets lets the user filter noise; do not interrupt the stage to ask per-point about the uncertainty.
Stubs, interfaces, TODOs, and hardcoded placeholders standing in for other tickets' unimplemented work are CORRECT by design - only verify the stub matches the agreed interface; do not flag them as overscope.
Gap rule
Draft each GENUINE GAP as a new ticket using the self-contained Agent Work Ticket template below. Present drafts under "Proposed new tickets". Request approval before filing each gap ticket via gh issue create -R <REPO> --label <LABEL> -t "<title>" -b "<body>". Do not file any gap ticket unless approved. Capture the real number/URL of each filed ticket and surface it back to the user.
Self-contained gap-ticket template
Title: <Area> - <Concrete outcome>. Do NOT include Status, Status Flow, Required Receipt, or Due Date in the body.
## Agent Work Ticket
### Request / Outcome
What needs to happen?
* <Concrete outcomes, not a vague theme. Concrete verbs + named components.>
### Background / Context
* <Why this exists: prior PRs, reviews, design constraints.>
* <Dependencies on other tickets: "Depends on #N (consumed via <interface/stub>); blocked by #M landing first.">
### Source Materials
* <Specs, PRs, commits, adjacent tickets. Prefer repo-relative paths.>
### Scope of Work
The owner may:
* <Allowed work. Every bullet traces to the Request / Outcome.>
The owner must not:
* <Out-of-scope work item> (ticket #N)
* <Out-of-scope work item> (ticket #M)
* <Change shared interfaces/contracts owned elsewhere>
* <Invent semantics not in the spec/source materials>
* Take any action requiring human approval without asking first
### Definition of Done
This ticket is complete when:
* <Verifiable checks only: tests, invariants, docs.>
### Stop Conditions
Stop and ask for input if:
* Ambiguity or missing information in the spec or interfaces
* A decision requires product/protocol judgment
* An action would affect another team or repo
* There is risk of overstepping the ticket scope
### Blocking Questions
* <Pre-seed only if already known; otherwise leave empty.>
The "owner must not" list cites adjacent ticket numbers from the scope map so every boundary is unambiguous.
Architecture
scoped-planning is a per-stage scope-check overlay with three entry points, each wrapping one lifecycle skill as a black box and bolting a scope guard onto its output:
| Mode | Wraps | Scope-checks |
|------|-------|--------------|
| scoped-planning /brainstorming | brainstorming | The emerging spec/design - flag scope creep vs the ticket universe. |
| scoped-planning /grill-plan | grill-plan | The grilled plan - flag unresolved decisions that drift outside ticket scope. |
| scoped-planning /writing-plans | writing-plans | The task plan - flag any task that touches work owned by another ticket or outside the universe. |
Each stage keeps its own human review gate intact. scoped-planning is invoked once per stage, not as a single end-to-end run.
Universe seeding is out of scope for this skill. If the label universe is empty, use create-scoped-tickets to seed it from a spec before invoking /scoped-planning.
Workflow
Each mode follows the same shape: build scope map -> run wrapped skill (black box) to its natural checkpoint -> apply scope-check guardrails post-hoc -> emit the artifact scope-cleaned.
Phase 0 - Scope inventory
Run once at the start of every invocation.
- List all open GitHub issues carrying
--labelviagh issue list --label <label> --state open --json number,title,body. - For each issue, parse the recognized scope-section headings to extract owned scope ("The owner may" / "Scope of Work") and excluded scope ("The owner must not" / "Not In Scope").
- Build the scope map:
{ticketNumber -> {owned, excluded}}. - Fetch each target ticket in
--ticketsviagh issue view <N> -R <REPO>and confirm it is OPEN and carries--label. If any target ticket is closed, missing, or not in the label universe: Error - unknown target ticket. Stop. - Mark the union of the target tickets' owned scope as the in-scope region.
- If the scope map is empty, stop and tell the user to seed the universe via
create-scoped-ticketsfirst.
Mode: /scoped-planning /brainstorming <idea>
- Run
brainstormingto its user-review gate. Then apply scope-check guardrails post-hoc, before the gate is passed:- IN SCOPE: design stays within the union of target tickets' owned scope. Silent pass; emit the spec as
brainstormingwould. - OWNED BY ANOTHER TICKET: flag inline at the design point that collides. Name the owning ticket. Ask the user to narrow the design, reassign, or explicitly accept the cross-ticket work before the artifact is handed off.
- GENUINE GAP: flag inline at the unowned design point. Stop and ask: (i) create a gap ticket now (embedded template) or (ii) narrow the design to drop the gap.
- IN SCOPE: design stays within the union of target tickets' owned scope. Silent pass; emit the spec as
Mode: /scoped-planning /grill-plan <plan>
- Run
grill-planto its handoff. Then apply scope-check guardrails post-hoc to the unresolved-decision list and the plan's scope:- IN SCOPE: unresolved decisions stay within the union of target tickets' owned scope. Silent pass; emit the grilled plan as
grill-planwould. - OWNED BY ANOTHER TICKET: flag inline which unresolved decisions would commit to another ticket's owned work. Ask the user to narrow or reassign.
- GENUINE GAP: flag inline decisions that require unowned work. Stop and ask: create a gap ticket or narrow.
- IN SCOPE: unresolved decisions stay within the union of target tickets' owned scope. Silent pass; emit the grilled plan as
Mode: /scoped-planning /writing-plans <spec>
- Run
writing-plansto its execution-handoff. Then apply scope-check guardrails post-hoc to every task:- IN SCOPE: every task touches only the union of target tickets' owned scope. Silent pass; emit the plan as
writing-planswould, through to its execution-handoff. - OWNED BY ANOTHER TICKET: flag inline at the offending tasks and which ticket owns the work they touch. Ask the user to drop/split/reassign the tasks.
- GENUINE GAP: flag inline tasks needing unowned work. Stop and ask: create a gap ticket or drop the tasks.
- IN SCOPE: every task touches only the union of target tickets' owned scope. Silent pass; emit the plan as
Guardrail behavior (all modes)
The scope-check runs as a post-hoc guardrail at the wrapped skill's natural checkpoint, not alongside its dialogue and not as a separate report. The deliverable is the wrapped skill's normal artifact, scope-cleaned before handoff:
- IN SCOPE - silent pass. No annotation; the artifact is emitted as the base skill would emit it.
- OWNED BY ANOTHER TICKET - flag inline at the point of collision (a spec section, an unresolved decision, a task line). Name the owning ticket. Ask the user how to resolve before the artifact is handed off.
- GENUINE GAP - flag inline at the unowned work. Stop and ask: create a gap ticket (embedded template) or narrow the artifact to drop the gap.
The final output is indistinguishable from the base skill's own output, except overscoped content has been flagged and resolved before the artifact is handed off.
Error handling and edge cases
Input errors
- A target ticket is not in the
--labeluniverse -> stop, report which ticket(s), ask the user to confirm the label or ticket numbers. REPOcannot be inferred and was not supplied -> stop, ask forREPO.gh issue listreturns zero open tickets carrying--label-> stop, tell the user the universe is empty and to seed it viacreate-scoped-ticketsfirst. Scope routing has no boundary to enforce against an empty universe.- A target ticket has no recognizable scope section -> stop, ask the user for the scope statement. Never infer scope from a ticket title alone.
--ticketsomitted -> stop, ask for--tickets. Target tickets are explicit; the skill does not infer them.- The wrapped skill token is not one of
/brainstorming,/grill-plan,/writing-plans-> stop, report the unrecognized token, list the three valid modes.
Scope-map ambiguity
- An artifact point touches an area that two tickets both appear to own -> flag the overlap inline with both ticket numbers; ask the user to resolve the boundary. Do not silently pick one.
- The scope map cannot be built because tickets use non-standard headings -> stop, report which tickets are unparseable, ask the user to confirm the heading convention or supply scope statements.
Gap-ticket creation
- User declines to create a gap ticket -> leave the gap flagged inline; do not file. Ask whether to narrow the artifact to drop the gap instead.
gh issue createfails -> report the error, stop, do not file. Tickets already filed in this run remain filed; report their numbers.- A drafted gap ticket duplicates an existing open ticket the scope map missed -> do not file; note the possible duplicate URL and ask the user to confirm.
No mutations beyond gap tickets
- The skill never edits existing issues, changes labels, closes issues, or mutates the wrapped skill's artifact beyond inline scope flags. The only GitHub mutation is
gh issue createfor approved gap tickets.
Invocation
/scoped-planning /brainstorming <idea> --tickets 4343,4380 --label synchronous-composability-demo
/scoped-planning /grill-plan <plan> --tickets 4343,4380
/scoped-planning /writing-plans <spec> --tickets 4343,4380
If the label universe is empty, seed it via create-scoped-tickets first.
--repo is optional when tickets are given as full URLs (inferred); required when --tickets are bare numbers. --label defaults to agent-work-ticket. All scope-routing guarantees become built-in defaults, never restated by the user.
Verification
Since this is a Markdown skill wrapping vendor skills, verification is static checks plus scenario exercises against this SKILL.md.
Static checks
# All three wrapped skills are referenced
rg -n 'brainstorming|grill-plan|writing-plans' skills/scoped-planning/SKILL.md
# Expected: matches in mode descriptions and workflow
# Self-contained scope-routing concepts are all defined
rg -n 'Scope inventory|IN SCOPE|OWNED BY ANOTHER TICKET|GENUINE GAP|Gap rule|gap-ticket template|Guardrail behavior' skills/scoped-planning/SKILL.md
# Expected: matches in workflow and classification sections
# No lingering bootstrap references
rg -n -i 'bootstrap' skills/scoped-planning/SKILL.md
# Expected: no output
# No em-dash (per workspace rule)
rg -n $'\u2014' skills/scoped-planning/SKILL.md
# Expected: no output
Scenario exercises
- Empty universe -
gh issue listreturns zero. Output: stop, tell the user to seed the universe viacreate-scoped-ticketsfirst. - Brainstorm, all in scope - Design stays within
#4343and#4380's owned scope. Output:brainstorming's normal spec, no inline flags. - Brainstorm, owned by another ticket - A spec section touches
#4350's owned scope. Output: inline flag at the section, name#4350, ask user to narrow/reassign/accept. - Brainstorm, genuine gap - A design point needs unowned work. Output: inline flag, stop and ask - create gap ticket or narrow.
- Grill, unresolved decision drifts out of scope - A grilled unresolved decision would commit to
#4350's work. Output: inline flag at the decision, ask user to narrow/reassign. - Writing-plans, task touches another ticket's scope - A task line implements
#4350's scope. Output: inline flag at the task, ask user to drop/split/reassign. - Writing-plans, genuine gap task - A task needs unowned work. Output: inline flag, stop and ask - create gap ticket or drop the task.
- Gap ticket created - User approves gap creation. Output:
gh issue create, capture real number, surface it back. - Gap ticket declined - User declines. Output: gap stays flagged inline, no filing; ask whether to narrow.
- Two target tickets both appear to own the same area - Overlap flagged inline with both numbers; ask user to resolve boundary.
- Unrecognized wrapped skill - User invokes
/scoped-planning /foo. Output: stop, report unrecognized token, list three valid modes. - Target ticket not in label universe -
--tickets 4343but#4343lacks--label. Output: stop, report, ask to confirm.