Compare commits

...
Author SHA1 Message Date
Matt PocockandClaude Opus 4.8 97dca07b64 Duplication pass: collapse restated out-of-scope mechanics
- Out of scope section: drop embedded 'however sharply you can see it'
  duplicate; the 'Scope, not sharpness, lands it here' punchline carries
  the claim once.
- Work-through step 5: delegate close+one-line mechanics to the Out of
  scope section via the 'rule it out of scope' leading phrase instead of
  restating them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:48:56 +01:00
Matt PocockandClaude Opus 4.8 7d34a8dc38 Split fog and out-of-scope into separate explanatory sections
QA follow-up: the Fog of war section was teaching both concepts, which
muddies the leading word — fog should point only *toward* the
destination. Now Fog of war teaches only the not-yet-specified bucket
(with the two-way fog-or-ticket sharpness test restored), and a new
Out of scope section owns the scope axis: beyond the destination,
closed not graduating, kept out of Decisions so far.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:44:13 +01:00
Matt PocockandClaude Opus 4.8 3ea01314c1 Rename the two buckets to plain map headings: Not yet specified / Out of scope
Following review: the map-body headings a cold reader sees should be
self-evident without having read the skill, so drop the metaphor from
the headings — `## Not yet specified` (in-scope, unripe) and
`## Out of scope` (beyond the destination). Keep **fog of war** as the
leading word: it still titles the explanatory section and drives the
"clears the fog" / "graduate the fog" behavior in the prose.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:35:50 +01:00
Matt PocockandClaude Opus 4.8 299eb0c017 Give wayfinder a distinct Deferred section for beyond-destination work
Fog is the undiscovered frontier *toward* the destination — in scope,
just not yet sharp — so it graduates into tickets as the frontier
advances. Work ruled out of *this* effort's scope was being parked in
Fog too, where it read as takeable frontier (an unblocked, unclaimed
ticket is indistinguishable from a live one).

Add a dedicated `## Deferred` section for beyond-the-destination work:
it never graduates (the frontier stops at the destination) and returns
only as a fresh effort if the destination is redrawn. The "Fog or
ticket?" test becomes "Fog, ticket, or deferred?", gated on scope as
well as sharpness, and both charting and working-the-map now defer a
mis-scoped ticket (close it, one line in Deferred) rather than leaving
it on the frontier or logging it in Decisions so far.

Builds on #455 (destination as the leading word / destination fixes
scope).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 15:08:48 +01:00
Matt PocockandGitHub 4691566bbb Merge pull request #455 from mattpocock/wayfinder-destination-purpose
Reframe wayfinder around "destination" as the leading word
2026-07-05 15:03:18 +01:00
Matt PocockandClaude Opus 4.8 913a105881 Split charting triage into two grilling steps: destination, then frontier
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 15:02:39 +01:00
Matt PocockandClaude Opus 4.8 c808f64f92 Make triage step 1 breadth-first: fix scope, then find the frontier
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 15:00:34 +01:00
Matt PocockandClaude Opus 4.8 53c6219cd2 Reframe wayfinder around "destination" as the leading word
Wayfinding finds the way to a destination rather than building it. The
destination varies per effort and is named first, since it shapes every
ticket. Adds a Destination map-body field, makes it triage step 1, and
unifies the stray "goal" mentions onto "destination".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 14:57:57 +01:00
Matt PocockandGitHub 272f99b225 Merge pull request #436 from mattpocock/wayfinder-assign-to-claim
wayfinder: claim by assigning the ticket, not a label
2026-07-03 11:37:11 +01:00
Matt PocockandClaude Opus 4.8 6f9e9956fd wayfinder: claim by assigning the ticket, not a label
A session now claims a ticket by assigning it to the dev driving the
map, rather than setting a `wayfinder:claimed` label. The assignee is
the claim; an open, unassigned ticket is unclaimed. Reads more naturally
in the tracker's own UI and frees the label vocabulary to
`wayfinder:<type>` alone.

Updates the GitHub and GitLab tracker-operations docs to the assignee
mechanism (`--add-assignee @me` / `--assignee @me`). The local-markdown
tracker keeps its `Status: claimed` line — a file store has no assignee.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 11:34:42 +01:00
Matt PocockandGitHub efa058a349 Merge pull request #435 from mattpocock/wayfinder-native-blocking-rationale
wayfinder: prefer native blocking, and say why
2026-07-03 10:44:51 +01:00
6 changed files with 66 additions and 23 deletions
+7
View File
@@ -0,0 +1,7 @@
---
"mattpocock-skills": minor
---
Change **`wayfinder`**'s claim mechanism from a label to an assignee.
A session now **claims** a ticket by assigning it to the dev driving the map, rather than setting a `wayfinder:claimed` label. The assignee _is_ the claim — an open, unassigned ticket is unclaimed — which reads more naturally in GitHub's own UI and frees the label vocabulary to `wayfinder:<type>` alone. The *claim* leading word and its "first, before any work" rationale are unchanged; only the physical expression moved.
+11
View File
@@ -0,0 +1,11 @@
---
"mattpocock-skills": minor
---
Give **`wayfinder`** a first-class notion of **out of scope**, separate from fog.
Fog and out-of-scope were conflated under one `## Fog` map section, gated by different things: fog by *knowledge* (can't specify it yet — in scope, unripe, graduates as the frontier advances), out-of-scope by *scope* (beyond the destination — never graduates). Cramming both under "Fog" made out-of-scope work read as takeable frontier (an unblocked, unclaimed item is indistinguishable from a live ticket).
Now the map body has two plainly-named sections — **`## Not yet specified`** and **`## Out of scope`** — and the skill's prose splits to match: the **Fog of war** section teaches only the not-yet-specified bucket and keeps the two-way *fog-or-ticket* sharpness test, while a new **Out of scope** section owns the scope axis (beyond the destination, closed not graduating, returns only as a fresh effort if the destination is redrawn). Charting and working-the-map now rule a beyond-destination ticket out of scope — close it, one line in *Out of scope* — rather than leaving it on the frontier or logging it in *Decisions so far*.
The **fog of war** leading word is retained: it names the concept and drives the graduate-the-fog behavior in the prose; only the human-facing map headings go to plain language.
@@ -0,0 +1,7 @@
---
"mattpocock-skills": minor
---
Sharpen **`wayfinder`**'s top-level purpose around **destination** as the leading word: wayfinding finds the *way* to a destination, it doesn't charge at building it.
The opening now states that the destination varies per effort — a spec to hand off and iterate, a decision to lock before planning, or a change made in place like a data-structure migration — and that naming it is the first act of charting, because it shapes every ticket. The map body gains a `## Destination` field that every session orients to before choosing a ticket, and triage's first step now pins the destination before any ticket exists. The stray `goal` mentions in the description and Fog section are unified onto `destination`.
@@ -38,8 +38,8 @@ Run `gh issue view <number> --comments`.
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `gh issue create --label wayfinder:map`.
- **Child ticket**: an issue linked to the map as a GitHub sub-issue (`gh api` on the sub-issues endpoint). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #<map>` at the top of the child body. Labels: `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`), plus `wayfinder:claimed` once claimed.
- **Child ticket**: an issue linked to the map as a GitHub sub-issue (`gh api` on the sub-issues endpoint). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #<map>` at the top of the child body. Labels: `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
- **Blocking**: GitHub's **native issue dependencies** — the canonical, UI-visible representation. Add an edge with `gh api --method POST repos/<owner>/<repo>/issues/<child>/dependencies/blocked_by -F issue_id=<blocker-db-id>`, where `<blocker-db-id>` is the blocker's numeric **database id** (`gh api repos/<owner>/<repo>/issues/<n> --jq .id`, _not_ the `#number` or `node_id`). GitHub reports `issue_dependencies_summary.blocked_by` (open blockers only — the live gate). Where dependencies aren't available, fall back to a `Blocked by: #<n>, #<n>` line at the top of the child body. A ticket is unblocked when every blocker is closed.
- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0`, or an open issue in the `Blocked by` line) or the `wayfinder:claimed` label; first in map order wins.
- **Claim**: `gh issue edit <n> --add-label wayfinder:claimed` — the session's first write.
- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0`, or an open issue in the `Blocked by` line) or an assignee; first in map order wins.
- **Claim**: `gh issue edit <n> --add-assignee @me` — the session's first write.
- **Resolve**: `gh issue comment <n> --body "<answer>"`, then `gh issue close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far.
@@ -39,8 +39,8 @@ Run `glab issue view <number> --comments`.
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `glab issue create --label wayfinder:map`. (On GitLab tiers with native epics, an epic may hold the map instead; a labelled issue works everywhere.)
- **Child ticket**: an issue carrying `Part of #<map>` at the top of its description and labels `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`), plus `wayfinder:claimed` once claimed.
- **Child ticket**: an issue carrying `Part of #<map>` at the top of its description and labels `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
- **Blocking**: GitLab's **native blocking link** — the canonical, UI-visible representation. Add it with the `/blocked_by #<n>` quick action, posted as a note (`glab issue note <child> --message "/blocked_by #<blocker>"`). Native blocking links are a Premium/Ultimate feature; on the free tier (or where unavailable) fall back to a `Blocked by: #<n>, #<n>` line at the top of the description. A ticket is unblocked when every blocker is closed.
- **Frontier query**: `glab issue list -F json` scoped to the map's children, drop any with an open blocker — a native `blocked_by` link to an open issue (`glab api projects/:id/issues/:iid/links`), or an open issue in the `Blocked by` line — or the `wayfinder:claimed` label; first in map order wins.
- **Claim**: `glab issue update <n> --label wayfinder:claimed` — the session's first write.
- **Frontier query**: `glab issue list -F json` scoped to the map's children, drop any with an open blocker — a native `blocked_by` link to an open issue (`glab api projects/:id/issues/:iid/links`), or an open issue in the `Blocked by` line — or an assignee; first in map order wins.
- **Claim**: `glab issue update <n> --assignee @me` — the session's first write.
- **Resolve**: `glab issue note <n> --message "<answer>"`, then `glab issue close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far.
+35 -17
View File
@@ -1,9 +1,11 @@
---
name: wayfinder
description: Plan a huge chunk of work — more than one agent session can hold — as a shared map of investigation tickets on your issue tracker, and resolve them one at a time until the way to the goal is clear.
description: Plan a huge chunk of work — more than one agent session can hold — as a shared map of investigation tickets on your issue tracker, and resolve them one at a time until the way to the destination is clear.
---
A loose idea has arrived — too big for one agent session, and wrapped in fog: the route from here to a plan isn't visible yet. This skill charts it as a **shared map** on the repo's issue tracker, then works its tickets one at a time. The map is domain-agnostic — engineering work, course content, whatever fits the shape.
A loose idea has arrived — too big for one agent session, and wrapped in fog: the way from here to the **destination** isn't visible yet. Wayfinding is about finding that way, not charging at the destination. This skill charts the way as a **shared map** on the repo's issue tracker, then works its tickets one at a time until the route is clear.
The destination varies per effort, and naming it is the first act of charting — it shapes every ticket. It might be a spec to hand off and iterate on, a decision to lock before planning starts, or a change made in place like a data-structure migration. The map is domain-agnostic — engineering work, course content, whatever fits the shape.
## Refer by name
@@ -22,6 +24,10 @@ The map is an **index**, not a store. It lists the decisions made and points at
The whole map at low resolution, loaded once per session. Open tickets are **not** listed — they are open child issues, found by query.
```markdown
## Destination
<what reaching the end of this map looks like — the spec, decision, or change this effort is finding its way to. One or two lines; every session orients to it before choosing a ticket.>
## Notes
<domain; skills every session should consult; standing preferences for this effort>
@@ -32,9 +38,13 @@ The whole map at low resolution, loaded once per session. Open tickets are **not
- [<closed ticket title>](link) — <one-line gist of the answer>
## Fog
## Not yet specified
<!-- see "Fog of war" for what belongs here -->
<!-- see "Fog of war": in-scope fog you can't ticket yet; graduates as the frontier advances -->
## Out of scope
<!-- see "Out of scope": work ruled beyond the destination; closed, never graduates -->
```
### Tickets
@@ -47,10 +57,9 @@ Each ticket is a **child issue** of the map; the tracker's issue id is its ident
<the decision or investigation this ticket resolves>
```
Two label families:
Each ticket carries a `wayfinder:<type>` label — one of `research`, `prototype`, `grilling`, `task` (see [Ticket Types](#ticket-types)).
- `wayfinder:<type>` — one of `research`, `prototype`, `grilling`, `task` (see [Ticket Types](#ticket-types)).
- `wayfinder:claimed` — a session sets this **first**, before any work, so concurrent sessions skip it.
A session **claims** a ticket by assigning it to the dev driving the map, **first**, before any work, so concurrent sessions skip it. That assignee _is_ the claim: an open, unassigned ticket is unclaimed.
Blocking uses the tracker's **native** dependency relationship — essential because it renders the frontier _visually_ in the tracker's own UI, so the human sees what's takeable without opening the map. Only a tracker that lacks native blocking falls back to a body convention. A ticket is **unblocked** when every ticket blocking it is closed; the **frontier** is the open, unblocked, unclaimed children — the edge of the known.
@@ -65,16 +74,24 @@ The answer isn't part of the body — it's recorded on resolution (see [Work thr
## Fog of war
The map is _deliberately_ incomplete: don't chart what you can't yet see. Beyond the tickets lies fog — the dim view of decisions and investigations you can tell are coming but can't yet pin down, because they hang on questions still open. Resolving a ticket clears the fog ahead of it, graduating whatever's now specifiable into fresh tickets — one at a time, until the way to the goal is clear and no tickets remain.
The map is _deliberately_ incomplete: don't chart what you can't yet see. Beyond the live tickets lies the **fog of war** — the dim view of decisions and investigations you can tell are coming but can't yet pin down, because they hang on questions still open. Resolving a ticket clears the fog ahead of it, graduating whatever's now specifiable into fresh tickets — one at a time, until the way to the destination is clear and no tickets remain.
The map's **Fog** section is where that dim view is written down: the suspected question, the area to revisit later, the risk you're deferring. Write as loosely or as fully as the view allows; it doubles as a signpost for collaborators reading where the effort is headed.
The map's **Not yet specified** section is where that dim view is written down: the suspected question, the area to revisit later. It's the undiscovered frontier _toward_ the destination — everything here is in scope, just not sharp enough to ticket. Write as loosely or as fully as the view allows; it doubles as a signpost for collaborators reading where the effort is headed.
**Fog or ticket?** The test is whether you can state the question precisely now — _not_ whether you can answer it now.
- **Ticket when** the question is already sharp — even if it's blocked and you can't act on it yet.
- **Fog when** you can't yet phrase it that sharply. Don't pre-slice fog into ticket-sized pieces: it's coarser than a ticket, and one patch may graduate into several tickets, or none, once the frontier reaches it.
- **Not yet specified when** you can't yet phrase it that sharply. Don't pre-slice the fog into ticket-sized pieces: it's coarser than a ticket, and one patch may graduate into several tickets, or none, once the frontier reaches it.
Fog excludes only what's already decided (that's Decisions so far) and what's already a ticket.
**Not yet specified** excludes what's already decided (Decisions so far), what's already a live ticket, and what's out of scope (the next section).
## Out of scope
Fog only ever gathers _toward_ the destination. The destination fixes the scope, so work beyond it is **out of scope** — it isn't fog, and it doesn't belong in **Not yet specified**. It gets its own **Out of scope** section on the map: work you've consciously ruled out of _this_ effort. Scope, not sharpness, lands it here.
Out-of-scope work never graduates — the frontier stops at the destination — so it returns only if the destination is redrawn, and then as a fresh effort, not a resumption.
Ruling something out of scope is a scoping act, not a step on the route. When a ticket that already exists turns out to sit past the destination — mis-scoped in while charting, or exposed by a resolution — **close it** (a closed ticket is unambiguously off the frontier) and leave one line in the **Out of scope** section: the gist plus why it's out of scope, linking the closed ticket. It stays out of **Decisions so far**, which records the route actually walked — a scope boundary isn't a step on it.
## Invocation
@@ -84,19 +101,20 @@ Two modes. Either way, **never resolve more than one ticket per session.**
User invokes with a loose idea.
1. Run a `/grilling` and `/domain-modeling` session to surface the open decisions.
2. **Create the map** (label `wayfinder:map`): Notes filled in, Decisions-so-far empty, Fog sketched.
3. **Create the tickets you can specify now** as child issues of the map — then wire blocking edges in a **second pass** (issues need ids before they can reference each other). Wiring sorts them into the frontier and the blocked; everything you can't yet specify stays in the Fog.
4. Stop — charting the map is one session's work; do not also resolve tickets.
1. **Name the destination.** Run a `/grilling` and `/domain-modeling` session to pin down what this map is finding its way to — the spec, decision, or change. The destination fixes the scope, so it's settled first.
2. **Map the frontier.** Grill again, **breadth-first** this time: fan out across the whole space rather than deep on any one thread, surfacing the open decisions and the first steps takeable now.
3. **Create the map** (label `wayfinder:map`): Destination and Notes filled in, Decisions-so-far empty, the fog sketched into **Not yet specified**.
4. **Create the tickets you can specify now** as child issues of the map — then wire blocking edges in a **second pass** (issues need ids before they can reference each other). Wiring sorts them into the frontier and the blocked; everything you can't yet specify stays in the fog — the **Not yet specified** section.
5. Stop — charting the map is one session's work; do not also resolve tickets.
### Work through the map
User invokes with a map (URL or number). A ticket is **optional** — without one, you pick the next decision, not the user.
1. Load the **map** — the low-res view, not every ticket body.
2. Choose the ticket. If the user named one, use it. Otherwise take the first frontier ticket in order. **Claim it**: set `wayfinder:claimed` and save before any work.
2. Choose the ticket. If the user named one, use it. Otherwise take the first frontier ticket in order. **Claim it**: assign it to yourself before any work.
3. Resolve it — **zoom as needed**: fetch the full body of any related or closed ticket on demand; invoke the skills the `## Notes` block names. If in doubt, use `/grilling` and `/domain-modeling`.
4. Record the resolution: post the answer as a **resolution comment**, **close** the issue, and **append a context pointer** to the map's Decisions-so-far.
5. Add newly-surfaced tickets (create-then-wire); graduate any fog the answer has made specifiable, clearing each graduated patch from the Fog so it lives only as its new ticket. If the decision invalidates other parts of the map, update or delete those tickets.
5. Add newly-surfaced tickets (create-then-wire); graduate any fog the answer has made specifiable, clearing each graduated patch from **Not yet specified** so it lives only as its new ticket. If the answer reveals a ticket — this one or another — sits beyond the destination, **rule it out of scope** rather than resolving it on the route. If the decision invalidates other parts of the map, update or delete those tickets.
The user may run unblocked tickets in parallel, so expect other sessions to be editing the tracker concurrently.