mirror of
https://github.com/mattpocock/skills.git
synced 2026-07-29 02:52:42 +07:00
Compare commits
19
Commits
@@ -0,0 +1,10 @@
|
||||
---
|
||||
"mattpocock-skills": patch
|
||||
---
|
||||
|
||||
Make `/ask-matt` clued-up about `/wayfinder` — the heaviest, most cognitively demanding flow.
|
||||
|
||||
The router now sharpens the two routing mistakes people most often make with wayfinder:
|
||||
|
||||
- **Over-reaching for it.** It's slower and denser than a single grill, so it's flagged as the heaviest flow and reserved for the idea that genuinely won't fit one session — a well-scoped feature belongs on `/grill-with-docs`, not here.
|
||||
- **Losing the way at the handoff.** When the map clears, wayfinder hands off, it doesn't build: merge onto the main flow at `/to-spec` (which collapses the map's linked decisions into a buildable plan) rather than looping the map straight into `/implement`. Straight-to-`/implement` is only for efforts that turned out genuinely small.
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
"mattpocock-skills": patch
|
||||
---
|
||||
|
||||
Make `/setup-matt-pocock-skills` friendlier and align the local-markdown tracker with the current spec.
|
||||
|
||||
- **Triage labels** are now asked about only when the `triage` skill is installed, and then as a single recommended-yes question ("keep the default triage labels?") instead of an override interrogation. When `triage` isn't installed, the section — and `docs/agents/triage-labels.md` — are skipped.
|
||||
- **External PRs as a request surface** is no longer a setup question. The GitHub/GitLab templates still carry the flag, defaulted off; a user can flip it in `docs/agents/issue-tracker.md` later.
|
||||
- **Domain docs** default to single-context without asking; multi-context is only offered when the repo shows monorepo signals.
|
||||
- **Local-markdown tickets** are now one file per ticket under `.scratch/<feature>/issues/<NN>-<slug>.md` — never a single combined `tickets.md`. `/to-tickets` and the local issue-tracker template now agree, and the spec file is `spec.md` (not `PRD.md`) to match `/to-spec`.
|
||||
|
||||
Docs pages for `setup-matt-pocock-skills` and `to-tickets` re-synced.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"mattpocock-skills": minor
|
||||
---
|
||||
|
||||
Reword how the **`prototype`** skill handles its artifacts around a single idea: **the prototype is a primary source**. Rather than being deleted once it's answered its question, the prototype is captured as runnable evidence on a throwaway branch (`prototype/<name>`) out of main, with a context pointer to it left on the implementation issue — so the main branch keeps only the validated decision while the exploration stays findable. The answer (verdict + question) is still captured durably in an issue/ADR/commit.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"mattpocock-skills": patch
|
||||
---
|
||||
|
||||
Name the `/wayfinder` unit a **decision ticket**.
|
||||
|
||||
People kept reading a wayfinder ticket as an ordinary *implementation* ticket — a slice of a build to execute — when wayfinder uses them as **decision tickets**: questions whose resolution is a decision. The skill description and its opening line now introduce "decision ticket" (and say what makes it one), and the `ask-matt` / engineering README wayfinder blurbs and the docs page match — while "ticket" stays the everyday word once the term is established. `CONTEXT.md` records **Decision ticket** as a domain term so the "avoid: ticket" guidance no longer contradicts wayfinder's deliberate use of the word.
|
||||
+5
-1
@@ -10,7 +10,10 @@ _Avoid_: backlog manager, backlog backend, issue host
|
||||
|
||||
**Issue**:
|
||||
A single tracked unit of work inside an **Issue tracker** — a bug, task, spec, or slice produced by `to-tickets`.
|
||||
_Avoid_: ticket (use only when quoting external systems that call them tickets)
|
||||
_Avoid_: ticket (use only when quoting external systems that call them tickets, or for a **Decision ticket** — see below)
|
||||
|
||||
**Decision ticket**:
|
||||
A `wayfinder` unit — a child **Issue** of a `wayfinder:map` holding a *question* whose resolution is a decision, not a slice of a build to execute. The **decision** qualifier is what keeps it distinct from an implementation ticket; `wayfinder` introduces the term, then uses "ticket".
|
||||
|
||||
**Triage role**:
|
||||
A canonical state-machine label applied to an **Issue** during triage (e.g. `needs-triage`, `ready-for-afk`). Each role maps to a real label string in the **Issue tracker** via `docs/agents/triage-labels.md`.
|
||||
@@ -19,6 +22,7 @@ A canonical state-machine label applied to an **Issue** during triage (e.g. `nee
|
||||
|
||||
- An **Issue tracker** holds many **Issues**
|
||||
- An **Issue** carries one **Triage role** at a time
|
||||
- A **Decision ticket** is an **Issue** (a child of a `wayfinder:map`)
|
||||
|
||||
## Flagged ambiguities
|
||||
|
||||
|
||||
@@ -31,9 +31,11 @@ The question decides the shape, and there are two shapes:
|
||||
|
||||
Picking the wrong branch wastes the whole prototype, so the question comes first. Both branches keep state in memory, run from one command, and surface the full state on every step.
|
||||
|
||||
## The answer is the artifact
|
||||
## Keep the prototype as a primary source
|
||||
|
||||
The code is disposable; the **answer** is the only thing worth keeping. When the prototype has settled its question, capture the verdict somewhere durable — a commit message, an ADR, an issue, or a `NOTES.md` next to it — alongside the question it answered, then delete or absorb the code. A prototype left rotting in the repo has outlived its purpose.
|
||||
A finished prototype leaves two things. The **answer** — the verdict plus the question it settled — is what you capture durably (a commit message, an ADR, an issue). The **prototype itself is a primary source** — the runnable evidence the answer came from.
|
||||
|
||||
The prototype doesn't belong in the main branch: no tests, no error handling, nothing to maintain. But that's not a reason to destroy it. Once the answer is captured, fold any validated decision into the real code, then capture the prototype on a throwaway branch — out of main, never merged — and leave a context pointer to it on the implementation issue. The main branch stays clean; the raw exploration stays one click away for anyone who wants to re-run it. A prototype left rotting in the main branch has outlived its purpose — a prototype captured as a primary source on a side branch hasn't.
|
||||
|
||||
## Where it fits
|
||||
|
||||
|
||||
@@ -24,17 +24,17 @@ Reach for it **once per repo, before the first use of any other engineering skil
|
||||
|
||||
## The three decisions
|
||||
|
||||
It walks you through three choices, one at a time, each with a plain-language explainer (it assumes you don't already know the terms):
|
||||
It leads each with a recommended answer you can accept in a word, and skips whatever it can already infer — so most runs are a couple of quick confirmations:
|
||||
|
||||
- **Issue tracker** — where work is tracked, so `triage`/`to-spec`/`to-tickets` know whether to call `gh`, `glab`, write markdown under `.scratch/`, or follow a workflow you describe. GitHub, GitLab, local markdown, or other.
|
||||
- **Triage labels** — the strings behind the five canonical roles (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`), mapped to labels you've actually configured so `triage` applies real ones instead of creating duplicates.
|
||||
- **Domain docs** — whether the repo has one `CONTEXT.md` or a multi-context map, so skills that read domain language look in the right place.
|
||||
- **Issue tracker** — where work is tracked, so `triage`/`to-spec`/`to-tickets` know whether to call `gh`, `glab`, write markdown under `.scratch/`, or follow a workflow you describe. GitHub, GitLab, local markdown, or other. (It proposes the one that matches your `git remote`.)
|
||||
- **Triage labels** — asked only if the `triage` skill is installed, and then just: keep the default labels (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`)? Say no only if your tracker already uses other names, so `triage` applies real ones instead of creating duplicates.
|
||||
- **Domain docs** — assumed single-context (one `CONTEXT.md` + `docs/adr/` at the root), which fits almost every repo; it only raises a multi-context map when it spots monorepo signals.
|
||||
|
||||
The output is three files — `docs/agents/issue-tracker.md`, `docs/agents/triage-labels.md`, `docs/agents/domain.md` — plus an `## Agent skills` block pointing to them in whichever of `CLAUDE.md` / `AGENTS.md` the repo already uses. Those files are the shared substrate the rest of the toolkit stands on.
|
||||
The output is a set of files under `docs/agents/` — `issue-tracker.md`, `domain.md`, and `triage-labels.md` when `triage` is installed — plus an `## Agent skills` block pointing to them in whichever of `CLAUDE.md` / `AGENTS.md` the repo already uses. Those files are the shared substrate the rest of the toolkit stands on.
|
||||
|
||||
## It's working if
|
||||
|
||||
- Three files land under `docs/agents/`, and an `## Agent skills` section appears in your `CLAUDE.md` or `AGENTS.md`.
|
||||
- `issue-tracker.md` and `domain.md` land under `docs/agents/` (plus `triage-labels.md` when `triage` is installed), and an `## Agent skills` section appears in your `CLAUDE.md` or `AGENTS.md`.
|
||||
- The tracker it proposes matches your real `git remote`, and the labels match strings that already exist in your repo.
|
||||
- Afterwards, `triage` and `to-tickets` act on the right place with the right labels instead of asking or guessing.
|
||||
|
||||
|
||||
@@ -57,4 +57,3 @@ grill-with-docs → to-spec → to-tickets → implement → code-review
|
||||
```
|
||||
|
||||
Reach for it after the plan and domain language are resolved, and before you break the work into implementation tickets. Its key neighbours are [grill-with-docs](https://aihero.dev/skills-grill-with-docs), which sharpens the context so the spec is precise, and [to-tickets](https://aihero.dev/skills-to-tickets), which turns the spec into a set of tickets for [implement](https://aihero.dev/skills-implement) to build. When you're unsure which skill or flow fits, [ask-matt](https://aihero.dev/skills-ask-matt) routes you.
|
||||
</content>
|
||||
|
||||
@@ -30,7 +30,7 @@ Reach for it once you have an agreed plan or a written spec and you want it spli
|
||||
|
||||
The blocking edges are the whole point. They make one set of tickets read two ways, depending on the tracker:
|
||||
|
||||
- **Local files** → a single `tickets.md` in the repo root, the edges written as text. You work it top-to-bottom, by hand, staying in the loop.
|
||||
- **Local files** → one file per ticket under `.scratch/<feature>/issues/`, numbered blockers-first, the edges written as text. You work them top-to-bottom, by hand, staying in the loop.
|
||||
- **A real tracker (GitHub, Linear)** → one issue per ticket, the edges as native blocking links (or sub-issues). Any ticket whose blockers are all done is on the **frontier** and can be grabbed — so several agents can run at once.
|
||||
|
||||
The edges live in the ticket regardless of medium; the medium only decides whether anything acts on them in parallel. `to-tickets` produces the artifact — how you run it (sequential by hand, or a parallel fleet) is up to you.
|
||||
@@ -54,4 +54,3 @@ grill-with-docs → to-spec → to-tickets → implement → code-review
|
||||
```
|
||||
|
||||
It sits between [to-spec](https://aihero.dev/skills-to-spec), which hands it a settled spec with user stories to slice against, and [implement](https://aihero.dev/skills-implement), which builds each ticket, driving [tdd](https://aihero.dev/skills-tdd) internally to write the tests test-first, before its [code-review](https://aihero.dev/skills-code-review) pass. Work the frontier one ticket per fresh context, clearing between them. When you're unsure which skill or flow fits, [ask-matt](https://aihero.dev/skills-ask-matt) routes you.
|
||||
</content>
|
||||
|
||||
@@ -12,7 +12,7 @@ npx skills update wayfinder
|
||||
|
||||
## What it does
|
||||
|
||||
`wayfinder` takes an effort too big for one agent session — wrapped in fog, where the way from here to the goal isn't visible yet — and charts it as a **shared map** of investigation tickets on your issue tracker, then resolves them one at a time until the way is clear. It **plans, it doesn't do**: every ticket resolves a decision, and the map is done when nothing is left to decide before someone goes and builds the thing — so it produces decisions, not deliverables.
|
||||
`wayfinder` takes an effort too big for one agent session — wrapped in fog, where the way from here to the goal isn't visible yet — and charts it as a **shared map** of **decision tickets** on your issue tracker, then resolves them one at a time until the way is clear. It **plans, it doesn't do**: every ticket resolves a decision — a question to settle, not a slice of a build to execute — and the map is done when nothing is left to decide before someone goes and builds the thing — so it produces decisions, not deliverables.
|
||||
|
||||
## When to reach for it
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ Reachable only when you type them (`disable-model-invocation: true`).
|
||||
- **[setup-matt-pocock-skills](./setup-matt-pocock-skills/SKILL.md)** — Configure this repo for the engineering skills (issue tracker, triage labels, domain doc layout). Run once per repo.
|
||||
- **[to-spec](./to-spec/SKILL.md)** — Turn the current conversation into a spec and publish it to the issue tracker.
|
||||
- **[to-tickets](./to-tickets/SKILL.md)** — Break any plan, spec, or conversation into a set of tracer-bullet tickets, each declaring its blocking edges — text in a local file, or native blocking links on a real tracker.
|
||||
- **[wayfinder](./wayfinder/SKILL.md)** — Plan a huge chunk of work — more than one agent session can hold — as a shared map of investigation tickets on the issue tracker, resolved one at a time until the way to the destination is clear.
|
||||
- **[wayfinder](./wayfinder/SKILL.md)** — Plan a huge chunk of work — more than one agent session can hold — as a shared map of decision tickets on the issue tracker, resolved one at a time until the way to the destination is clear.
|
||||
|
||||
## Model-invoked
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ The route most work travels. You have an idea and want it built.
|
||||
- **`/prototype`** to answer the question with throwaway code,
|
||||
- **`/handoff`** back what you learned, and reference it from the original idea thread.
|
||||
3. **Branch — is this a multi-session build?**
|
||||
- **Yes** → **`/to-spec`** (turn the thread into a spec), then **`/to-tickets`** to split it into tracer-bullet tickets, each declaring its **blocking edges**. On a local tracker that's an ordered `tickets.md` you work by hand; on a real tracker the edges become native blocking links, so any ticket whose blockers are done can be grabbed — kick off **`/implement`** per ticket, **clearing context between each one**.
|
||||
- **Yes** → **`/to-spec`** (turn the thread into a spec), then **`/to-tickets`** to split it into tracer-bullet tickets, each declaring its **blocking edges**. On a local tracker that's one file per ticket under `.scratch/<feature>/issues/`, worked blockers-first by hand; on a real tracker the edges become native blocking links, so any ticket whose blockers are done can be grabbed — kick off **`/implement`** per ticket, **clearing context between each one**.
|
||||
- **No** → **`/implement`** right here, in the same context window.
|
||||
|
||||
Either way, **`/implement`** builds each issue by driving **`/tdd`** internally — one red-green slice at a time — then closes out by running **`/code-review`**, a two-axis review (Standards + Spec) of the diff, before committing. Reach for **`/tdd`** on its own when you just want to build a concrete behaviour test-first without a full spec, and **`/code-review`** on its own whenever you want to review a branch or PR against a fixed point.
|
||||
@@ -41,7 +41,9 @@ A starting situation that generates work, then merges onto the main flow.
|
||||
|
||||
- **Something's broken** → **`/diagnosing-bugs`**. For the hard ones: the bug that resists a first glance, the intermittent flake, the regression that crept in between two known-good states. It refuses to theorise until it has a **tight feedback loop** — one command that already goes red on *this* bug — then fixes with a regression test. Its post-mortem hands off to **`/improve-codebase-architecture`** when the real finding is that there's no good seam to lock the bug down.
|
||||
|
||||
- **A huge, foggy effort — a greenfield project or a huge feature build, too big for one session** → **`/wayfinder`**. When the way from here to the destination isn't visible yet, it charts a **shared map** of investigation tickets on the issue tracker and resolves them one at a time — producing **decisions, not deliverables** — until the fog is pushed back and the way is clear. Then it merges onto the main flow at **`/to-spec`** (or, if the effort turned out small enough, straight to **`/implement`**). Where **`/grill-with-docs`** sharpens an idea you can hold in one session, wayfinder is for the idea you can't.
|
||||
- **A huge, foggy effort — a greenfield project or a huge feature build, too big for one session** → **`/wayfinder`**, the most cognitively demanding flow here. When the way from here to the destination isn't visible yet, it charts a **shared map** of **decision tickets** on the issue tracker and resolves them one at a time — producing **decisions, not deliverables** — until the fog is pushed back and the way is clear. Where **`/grill-with-docs`** sharpens an idea you can hold in one session, wayfinder is for the idea you can't — and it's slower and denser, so save it for exactly that, never a well-scoped feature.
|
||||
|
||||
When the map clears, **it hands off, it doesn't build**: merge onto the main flow at **`/to-spec`**, which collapses the map's linked decisions into a buildable plan, then `/to-tickets` and `/implement` as usual. Looping the map straight into `/implement` skips that collapse and throws the linked detail away — go straight to `/implement` only when the effort turned out genuinely small.
|
||||
|
||||
## Codebase health
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ The right shape depends on the question:
|
||||
|
||||
Pick whichever shape best fits the question being asked, *not* whichever is easiest to wire to a TUI. Keep it pure: no I/O, no terminal code, no `console.log` for control flow. The TUI imports it and calls into it; nothing flows the other direction.
|
||||
|
||||
This is what makes the prototype useful past its own lifetime. When the question's been answered, the validated reducer / machine / function set can be lifted into the real module — the TUI shell gets deleted.
|
||||
This is what makes the prototype useful past its own lifetime: when the question's been answered, the validated reducer / machine / function set can be lifted into the real module on its own.
|
||||
|
||||
### 4. Build the smallest TUI that exposes the state
|
||||
|
||||
@@ -66,9 +66,9 @@ If the host project has no task runner, just put the command at the top of the p
|
||||
|
||||
Give the user the run command. They'll drive it themselves; the interesting moments are when they say "wait, that shouldn't be possible" or "huh, I assumed X would be different" — those are the bugs in the _idea_, which is the whole point. If they want new actions added, add them. Prototypes evolve.
|
||||
|
||||
### 7. Capture the answer
|
||||
### 7. Capture the answer and the prototype
|
||||
|
||||
When the prototype has done its job, the answer to the question is the only thing worth keeping. If the user is around, ask what it taught them. If not, leave a `NOTES.md` next to the prototype so the answer can be filled in (or filled in by you, if you've watched the session) before the prototype gets deleted.
|
||||
Once the prototype has answered its question, capture the answer, then capture the prototype the way the [SKILL](SKILL.md) describes. The logic-specific mapping: the validated reducer / machine / function set lifts into the real module (the decision, absorbed); the TUI shell rides along to the throwaway branch that keeps the prototype as a primary source.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
|
||||
@@ -21,10 +21,6 @@ The two branches produce very different artifacts — getting this wrong wastes
|
||||
1. **Throwaway from day one, and clearly marked as such.** Locate the prototype code close to where it will actually be used (next to the module or page it's prototyping for) so context is obvious — but name it so a casual reader can see it's a prototype, not production. For throwaway UI routes, obey whatever routing convention the project already uses; don't invent a new top-level structure.
|
||||
2. **One command to run.** Whatever the project's existing task runner supports — `pnpm <name>`, `python <path>`, `bun <path>`, etc. The user must be able to start it without thinking.
|
||||
3. **No persistence by default.** State lives in memory. Persistence is the thing the prototype is _checking_, not something it should depend on. If the question explicitly involves a database, hit a scratch DB or a local file with a clear "PROTOTYPE — wipe me" name.
|
||||
4. **Skip the polish.** No tests, no error handling beyond what makes the prototype _runnable_, no abstractions. The point is to learn something fast and then delete it.
|
||||
4. **Skip the polish.** No tests, no error handling beyond what makes the prototype _runnable_, no abstractions. The point is to learn something fast.
|
||||
5. **Surface the state.** After every action (logic) or on every variant switch (UI), print or render the full relevant state so the user can see what changed.
|
||||
6. **Delete or absorb when done.** When the prototype has answered its question, either delete it or fold the validated decision into the real code — don't leave it rotting in the repo.
|
||||
|
||||
## When done
|
||||
|
||||
The _answer_ is the only thing worth keeping from a prototype. Capture it somewhere durable (commit message, ADR, issue, or a `NOTES.md` next to the prototype) along with the question it was answering. If the user is around, that capture is a quick conversation; if not, leave the placeholder so they (or you, on the next pass) can fill in the verdict before deleting the prototype.
|
||||
6. **Capture it when done.** Fold any validated decision into the real code, then capture the prototype itself as a **primary source**: commit it to a throwaway branch, out of main, and leave a context pointer to that branch on the implementation issue. Capture the answer too — the verdict and the question it settled — in the issue or a commit. The main branch keeps only the validated decision.
|
||||
|
||||
@@ -97,12 +97,12 @@ Surface the URL (and the `?variant=` keys). The user will flip through whenever
|
||||
|
||||
### 6. Capture the answer and clean up
|
||||
|
||||
Once a variant has won, write down which one and why (commit message, ADR, issue, or a `NOTES.md` next to the prototype if running AFK and the user hasn't responded yet). Then:
|
||||
Once a variant has won, capture the answer — which variant and why — then capture the prototype the way the [SKILL](SKILL.md) describes. Fold the winner into the real code and move the rest onto the throwaway branch, not into main:
|
||||
|
||||
- **Sub-shape A** — delete the losing variants and the switcher; fold the winner into the existing page.
|
||||
- **Sub-shape B** — promote the winning variant to a real route, delete the throwaway route and the switcher.
|
||||
- **Sub-shape A** — fold the winner into the existing page; drop the losing variants and the switcher from main.
|
||||
- **Sub-shape B** — promote the winning variant to a real route; drop the throwaway route and the switcher from main.
|
||||
|
||||
Don't leave variant components or the switcher lying around. They rot fast and confuse the next reader.
|
||||
The full set of variants is the primary source, so it lands on the throwaway branch, not the bin — variant components and the switcher left in the main branch rot fast and confuse the next reader.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
|
||||
@@ -26,12 +26,14 @@ Look at the current repo to understand its starting state. Read whatever exists;
|
||||
- `docs/adr/` and any `src/*/docs/adr/` directories
|
||||
- `docs/agents/` — does this skill's prior output already exist?
|
||||
- `.scratch/` — sign that a local-markdown issue tracker convention is already in use
|
||||
- Is the `triage` skill installed? (a `triage` skill folder alongside this one, or `triage` in your available skills.) This decides whether Section B runs at all.
|
||||
- Monorepo signals — a `pnpm-workspace.yaml`, a `workspaces` field in `package.json`, or a populated `packages/*` with its own `src/`. Present only in a genuinely large multi-package repo; their absence means single-context, which is almost every repo.
|
||||
|
||||
### 2. Present findings and ask
|
||||
|
||||
Summarise what's present and what's missing. Then walk the user through the three decisions **one at a time** — present a section, get the user's answer, then move to the next. Don't dump all three at once.
|
||||
Summarise what's present and what's missing. Then take the sections in order — one section, one answer, then the next.
|
||||
|
||||
Assume the user does not know what these terms mean. Each section starts with a short explainer (what it is, why these skills need it, what changes if they pick differently). Then show the choices and the default.
|
||||
Lead each section with the recommended answer so the user can accept it in a word. Give a one-line explainer only when the choice genuinely branches; skip the section entirely when exploration already settled it (Section B when `triage` isn't installed, Section C when there's no monorepo).
|
||||
|
||||
**Section A — Issue tracker.**
|
||||
|
||||
@@ -44,41 +46,26 @@ Default posture: these skills were designed for GitHub. If a `git remote` points
|
||||
- **Local markdown** — issues live as files under `.scratch/<feature>/` in this repo (good for solo projects or repos without a remote)
|
||||
- **Other** (Jira, Linear, etc.) — ask the user to describe the workflow in one paragraph; the skill will record it as freeform prose
|
||||
|
||||
If — and only if — the user picked **GitHub** or **GitLab**, ask one follow-up:
|
||||
Record the choice in `docs/agents/issue-tracker.md`. The GitHub and GitLab templates carry a "PRs as a request surface" flag, defaulted **off** — leave it off and don't raise it; a user who wants external PRs in the triage queue can flip the flag in the file later.
|
||||
|
||||
> Explainer: Open-source repos often receive feature requests as pull requests, not just issues — a PR is an issue with attached code. If you turn this on, `/triage` pulls *external* PRs into the same queue and runs them through the same labels and states as issues (collaborators' in-flight PRs are left alone). Leave it off if PRs aren't a request surface for you.
|
||||
**Section B — Triage label vocabulary.** Skip this section entirely if the `triage` skill isn't installed (exploration told you) — an uninstalled skill needs no labels.
|
||||
|
||||
- **PRs as a request surface** — yes / no (default: no). Record the answer in `docs/agents/issue-tracker.md`. For local-markdown and other trackers, skip this question — there are no PRs.
|
||||
If it is installed, ask exactly one question:
|
||||
|
||||
**Section B — Triage label vocabulary.**
|
||||
> Do you want to keep the default triage labels? (recommended: **yes**)
|
||||
|
||||
> Explainer: When the `triage` skill processes an incoming issue, it moves it through a state machine — needs evaluation, waiting on reporter, ready for an AFK agent to pick up, ready for a human, or won't fix. To do that, it needs to apply labels (or the equivalent in your issue tracker) that match strings *you've actually configured*. If your repo already uses different label names (e.g. `bug:triage` instead of `needs-triage`), map them here so the skill applies the right ones instead of creating duplicates.
|
||||
The defaults are the five canonical roles, each label string equal to its name: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. On **yes**, write them as-is. Only if the user says no — usually because their tracker already uses other names (e.g. `bug:triage` for `needs-triage`) — collect the overrides so `triage` applies existing labels instead of creating duplicates.
|
||||
|
||||
The five canonical roles:
|
||||
**Section C — Domain docs.** Default to **single-context** — one `CONTEXT.md` + `docs/adr/` at the repo root. This fits almost every repo; write it without asking.
|
||||
|
||||
- `needs-triage` — maintainer needs to evaluate
|
||||
- `needs-info` — waiting on reporter
|
||||
- `ready-for-agent` — fully specified, AFK-ready (an agent can pick it up with no human context)
|
||||
- `ready-for-human` — needs human implementation
|
||||
- `wontfix` — will not be actioned
|
||||
|
||||
Default: each role's string equals its name. Ask the user if they want to override any. If their issue tracker has no existing labels, the defaults are fine.
|
||||
|
||||
**Section C — Domain docs.**
|
||||
|
||||
> Explainer: Some skills (`improve-codebase-architecture`, `diagnosing-bugs`, `tdd`) read a `CONTEXT.md` file to learn the project's domain language, and `docs/adr/` for past architectural decisions. They need to know whether the repo has one global context or multiple (e.g. a monorepo with separate frontend/backend contexts) so they look in the right place.
|
||||
|
||||
Confirm the layout:
|
||||
|
||||
- **Single-context** — one `CONTEXT.md` + `docs/adr/` at the repo root. Most repos are this.
|
||||
- **Multi-context** — `CONTEXT-MAP.md` at the root pointing to per-context `CONTEXT.md` files (typically a monorepo).
|
||||
Offer **multi-context** — a root `CONTEXT-MAP.md` pointing to per-context `CONTEXT.md` files — only when exploration found monorepo signals. Then confirm which layout they want.
|
||||
|
||||
### 3. Confirm and edit
|
||||
|
||||
Show the user a draft of:
|
||||
|
||||
- The `## Agent skills` block to add to whichever of `CLAUDE.md` / `AGENTS.md` is being edited (see step 4 for selection rules)
|
||||
- The contents of `docs/agents/issue-tracker.md`, `docs/agents/triage-labels.md`, `docs/agents/domain.md`
|
||||
- The contents of `docs/agents/issue-tracker.md`, `docs/agents/domain.md`, and `docs/agents/triage-labels.md` (the last only when `triage` is installed)
|
||||
|
||||
Let them edit before writing.
|
||||
|
||||
@@ -101,7 +88,7 @@ The block:
|
||||
|
||||
### Issue tracker
|
||||
|
||||
[one-line summary of where issues are tracked, plus whether external PRs are a triage surface]. See `docs/agents/issue-tracker.md`.
|
||||
[one-line summary of where issues are tracked]. See `docs/agents/issue-tracker.md`.
|
||||
|
||||
### Triage labels
|
||||
|
||||
@@ -112,12 +99,14 @@ The block:
|
||||
[one-line summary of layout — "single-context" or "multi-context"]. See `docs/agents/domain.md`.
|
||||
```
|
||||
|
||||
Then write the three docs files using the seed templates in this skill folder as a starting point:
|
||||
Include the `### Triage labels` sub-block, and write `docs/agents/triage-labels.md`, only when `triage` is installed and Section B ran. When it isn't, both are omitted.
|
||||
|
||||
Then write the docs files using the seed templates in this skill folder as a starting point:
|
||||
|
||||
- [issue-tracker-github.md](./issue-tracker-github.md) — GitHub issue tracker
|
||||
- [issue-tracker-gitlab.md](./issue-tracker-gitlab.md) — GitLab issue tracker
|
||||
- [issue-tracker-local.md](./issue-tracker-local.md) — local-markdown issue tracker
|
||||
- [triage-labels.md](./triage-labels.md) — label mapping
|
||||
- [triage-labels.md](./triage-labels.md) — label mapping (only if `triage` is installed)
|
||||
- [domain.md](./domain.md) — domain doc consumer rules + layout
|
||||
|
||||
For "other" issue trackers, write `docs/agents/issue-tracker.md` from scratch using the user's description.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Issue tracker: Local Markdown
|
||||
|
||||
Issues and PRDs for this repo live as markdown files in `.scratch/`.
|
||||
Issues and specs (you may know a spec as a PRD) for this repo live as markdown files in `.scratch/`.
|
||||
|
||||
## Conventions
|
||||
|
||||
- One feature per directory: `.scratch/<feature-slug>/`
|
||||
- The PRD is `.scratch/<feature-slug>/PRD.md`
|
||||
- Implementation issues are `.scratch/<feature-slug>/issues/<NN>-<slug>.md`, numbered from `01`
|
||||
- The spec is `.scratch/<feature-slug>/spec.md`
|
||||
- Implementation issues are one file per ticket at `.scratch/<feature-slug>/issues/<NN>-<slug>.md`, numbered from `01` — never a single combined tickets file
|
||||
- Triage state is recorded as a `Status:` line near the top of each issue file (see `triage-labels.md` for the role strings)
|
||||
- Comments and conversation history append to the bottom of the file under a `## Comments` heading
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: to-tickets
|
||||
description: Break a plan, spec, or the current conversation into a set of tracer-bullet tickets, each declaring its blocking edges, published to the configured tracker — edges as text in a local file, or native blocking links on a real tracker.
|
||||
description: Break a plan, spec, or the current conversation into a set of tracer-bullet tickets, each declaring its blocking edges, published to the configured tracker — edges as text in one file per ticket locally, or native blocking links on a real tracker.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
@@ -59,33 +59,27 @@ Iterate until the user approves the breakdown.
|
||||
|
||||
Publish the approved tickets. **How** depends on the tracker `/setup-matt-pocock-skills` configured — the tickets are the same either way, only the shape of the blocking edges changes:
|
||||
|
||||
- **Local files** → write one `tickets.md` in the repo root, all tickets in dependency order (blockers first), each with its "Blocked by" listing the titles it depends on. Use the file template below.
|
||||
- **Local files** → write one file per ticket under `.scratch/<feature-slug>/issues/<NN>-<slug>.md`, numbered from `01` in dependency order (blockers first). Each file's "Blocked by" lists the numbers/titles it depends on. Use the per-ticket file template below — one ticket per file, never a single combined file.
|
||||
- **A real issue tracker (GitHub, Linear, …)** → publish one issue per ticket in dependency order (blockers first) so each ticket's blocking edges can reference real identifiers. Use the platform's native blocking / sub-issue relationship where it has one; otherwise set each ticket's "Blocked by" to the blocking issues. Apply the `ready-for-agent` triage label unless instructed otherwise — the tickets are agent-grabbable by construction.
|
||||
|
||||
Do NOT close or modify any parent issue.
|
||||
|
||||
<tickets-file-template>
|
||||
|
||||
# Tickets: <short name of the work>
|
||||
|
||||
A one-line summary of what these tickets build. Reference the source spec if there is one.
|
||||
|
||||
Work the **frontier**: any ticket whose blockers are all done. For a purely linear chain that means top to bottom.
|
||||
|
||||
## <Ticket title>
|
||||
Do NOT close or modify any parent issue.
|
||||
|
||||
<local-ticket-template>
|
||||
|
||||
# <NN> — <Ticket title>
|
||||
|
||||
**What to build:** the end-to-end behaviour this ticket makes work, from the user's perspective — not a layer-by-layer implementation list.
|
||||
|
||||
**Blocked by:** the titles of the tickets that gate this one, or "None — can start immediately".
|
||||
**Blocked by:** the numbers/titles of the tickets that gate this one, or "None — can start immediately".
|
||||
|
||||
**Status:** ready-for-agent
|
||||
|
||||
- [ ] Acceptance criterion 1
|
||||
- [ ] Acceptance criterion 2
|
||||
|
||||
## <Ticket title>
|
||||
|
||||
...
|
||||
|
||||
</tickets-file-template>
|
||||
</local-ticket-template>
|
||||
|
||||
<issue-template>
|
||||
|
||||
@@ -111,4 +105,3 @@ The end-to-end behaviour this ticket makes work, from the user's perspective —
|
||||
In either form, avoid specific file paths or code snippets — they go stale fast. Exception: if a prototype produced a snippet that encodes a decision more precisely than prose can (state machine, reducer, schema, type shape), inline it and note briefly that it came from a prototype. Trim to the decision-rich parts — not a working demo, just the important bits.
|
||||
|
||||
Work the frontier one ticket at a time with `/implement`, clearing context between tickets.
|
||||
</content>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
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 destination is clear.
|
||||
description: Plan a huge chunk of work — more than one agent session can hold — as a shared map of decision tickets on your issue tracker, and resolve them one at a time until the way to the destination is clear.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
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.
|
||||
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 **decision tickets** — questions whose resolution is a decision, not slices of a build to execute — 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.
|
||||
|
||||
|
||||
@@ -8,3 +8,4 @@ Skills that are still being developed. They're not ready to ship — expect roug
|
||||
- **[writing-fragments](./writing-fragments/SKILL.md)** — Grilling session that mines you for fragments — heterogeneous nuggets of writing — and appends them to a single document as raw material for a future article.
|
||||
- **[writing-shape](./writing-shape/SKILL.md)** — Take a markdown file of raw material and shape it into an article paragraph by paragraph, arguing format choices at each step.
|
||||
- **[claude-handoff](./claude-handoff/SKILL.md)** — Hand the current conversation off to a fresh background agent that picks up the work immediately, seeded with a handoff summary via `claude --bg`. User-invoked.
|
||||
- **[setup-ts-deep-modules](./setup-ts-deep-modules/SKILL.md)** — Wire dependency-cruiser into a TypeScript repo so each package is a deep module — implementation hidden in subfolders, reachable only through its entry-point files, tests exercising it through those. User-invoked.
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
name: setup-ts-deep-modules
|
||||
description: Wire dependency-cruiser into a TypeScript repo so each package is a deep module — implementation hidden in subfolders, reachable only through its entry-point files. User-invoked.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# Setup TS Deep Modules
|
||||
|
||||
Make every package in this repo a **deep module**: a lot of behaviour behind a small interface. A package's public surface is its **entry points** — the files at the package root — and everything in its subfolders is hidden. This skill installs [dependency-cruiser](https://github.com/sverweij/dependency-cruiser) and the rules that make the entry points the only way in, then proves the rules bite.
|
||||
|
||||
For the vocabulary (deep module, interface, seam, depth), run the `/codebase-design` skill — use its language throughout.
|
||||
|
||||
## The shape this enforces
|
||||
|
||||
```
|
||||
src/packages/
|
||||
<name>/
|
||||
index.ts ← an entry point (public). Import this from outside.
|
||||
client.ts ← another entry point. Packages may expose SEVERAL.
|
||||
lib/ ← implementation: hidden from outside, free to import each other.
|
||||
tests/ ← co-located tests + fixtures (a subfolder, so private).
|
||||
```
|
||||
|
||||
The public surface is the package's **root files** — not one designated `index.ts`. By convention implementation lives in `lib/` and tests in `tests/`, giving every package the same two-folder shape. The rule itself is general, though: *anything* in *any* subfolder is private, so you never extend the config to add a folder.
|
||||
|
||||
Four rules, all `error`:
|
||||
|
||||
1. **Entry-point boundary** — code outside a package (app code or another package) may import only that package's entry points (its root files), never anything in its subfolders.
|
||||
2. **Intra-package freedom** — a package's own files import each other freely.
|
||||
3. **Tests through the entry points** — files under `<pkg>/tests/` may import any package's entry points and their own `tests/` fixtures, but never any package's subfolder internals (not even their own). Integration tests across packages are fine; deep imports are not.
|
||||
4. **No cycles** — no dependency cycles.
|
||||
|
||||
**Entry points, not a barrel.** Because the public surface is *every* root file, a package can expose several small entry points (`index.ts`, `client.ts`, `server.ts`) instead of funnelling everything through one giant `index.ts`. Barrel files that re-export a whole subtree are discouraged — keep entry points small and hide implementation in subfolders.
|
||||
|
||||
Layering (which packages may depend on which) is a *different* concern and is left as a commented stub in the config for this repo to fill in.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Detect the environment
|
||||
|
||||
- **Package manager** — `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `bun.lockb` → bun, else npm. Use it for every command below (`pnpm`/`yarn`/`npm run`/`bunx`).
|
||||
- **Packages root** — if `src/` exists use `src/packages`, else `packages`. Confirm the choice with the user if the repo already has a different obvious convention.
|
||||
- **Existing config** — check for a `.dependency-cruiser.*` file. If one exists, do **not** overwrite it: merge the four rules and the options in, and tell the user what you added.
|
||||
|
||||
**Done when:** package manager, packages root, and existing-config status are all known.
|
||||
|
||||
### 2. Install dependency-cruiser
|
||||
|
||||
Install `dependency-cruiser` as a devDependency with the detected package manager.
|
||||
|
||||
**Done when:** `dependency-cruiser` is in `devDependencies`.
|
||||
|
||||
### 3. Write the config
|
||||
|
||||
Copy [`dependency-cruiser.config.cjs`](./dependency-cruiser.config.cjs) to the repo root as `.dependency-cruiser.cjs`. Set `PACKAGES_ROOT` to the root detected in step 1. The rules are path-depth based and extension-agnostic, so nothing else needs adapting.
|
||||
|
||||
**Done when:** `.dependency-cruiser.cjs` exists with the correct `PACKAGES_ROOT`, and the four forbidden rules are present.
|
||||
|
||||
### 4. Wire it into the checks
|
||||
|
||||
- Add a `lint:boundaries` script: `depcruise <packages-root>` (or `depcruise src`).
|
||||
- Fold it into the repo's umbrella check command — the one that already runs typecheck (e.g. a `check` / `ci` / `validate` script). Do **not** touch `tsconfig` or add path aliases.
|
||||
- If there is no umbrella script, add `lint:boundaries` and tell the user to include it in CI.
|
||||
|
||||
**Done when:** `lint:boundaries` exists and runs as part of the same command as typecheck.
|
||||
|
||||
### 5. Scaffold the example package
|
||||
|
||||
Create a committed `<packages-root>/example/` as a copy-me template:
|
||||
|
||||
- `index.ts` — an entry point. Export one function that delegates to an internal file (so the package is visibly *deep*, not a pass-through).
|
||||
- `lib/impl.ts` — an internal file in a **subfolder**, imported by `index.ts`, not reachable from outside.
|
||||
- `tests/example.test.ts` — imports **only** `../index` (an entry point), and asserts against the public function.
|
||||
|
||||
Tell the user this is a starter template to copy or delete.
|
||||
|
||||
**Done when:** the example package exists, exposes its behaviour through a root entry point, and hides `impl` in a subfolder.
|
||||
|
||||
### 6. Prove the rules bite
|
||||
|
||||
This is the completion criterion for the whole skill — a config that doesn't fail on a violation is worthless.
|
||||
|
||||
1. Run `lint:boundaries`. It must **pass** on the clean example.
|
||||
2. Temporarily add a deep import to `tests/example.test.ts` (e.g. `import { thing } from "../lib/impl"`). Run `lint:boundaries` again — it must **fail** with `tests-through-entrypoints`.
|
||||
3. Revert the deep import. Run once more — it must **pass**.
|
||||
|
||||
**Done when:** you have observed a pass, then a fail on the deep import, then a pass again. If step 2 does not fail, the rules are not wired correctly — fix before finishing.
|
||||
|
||||
### 7. Document the convention
|
||||
|
||||
Write a `README.md` **in the packages folder** (`<packages-root>/README.md`) — next to the packages it governs — covering: the `src/packages/<name>/` layout (entry points at the root, `lib/` for implementation, `tests/` for tests), "import only through a package's entry points (its root files)", and how to run `lint:boundaries`. **Discourage barrel files** explicitly — expose several small entry points instead of re-exporting a whole subtree through one index. Keep it to the copy-me snippet plus the four rules in one paragraph each.
|
||||
|
||||
Then add a **context pointer** to it from the repo's agent-instructions file — `CLAUDE.md` if present, else `AGENTS.md` (create `AGENTS.md` if neither exists). One line is enough, e.g. `Packages are deep modules — see [src/packages/README.md](./src/packages/README.md) before adding or importing one.` This is what makes an agent discover the boundary rule instead of tripping over it.
|
||||
|
||||
**Done when:** `<packages-root>/README.md` exists and discourages barrels, and the repo's `CLAUDE.md`/`AGENTS.md` links to it.
|
||||
|
||||
## Notes
|
||||
|
||||
- The config's `$1` back-references (dependency-cruiser's group matching) are what let a package reach its own internals while outsiders can't — don't flatten them into separate per-package rules.
|
||||
- Public vs private is decided by **depth**: a package's root files are entry points; anything in a subfolder is private. The conventional subfolders are `lib/` (implementation) and `tests/`, but the rule doesn't hardcode them — any subfolder is private, so a new folder never needs a config change. Adding an entry point is just adding a root file — no barrel.
|
||||
- Packages are **flat**: one tier of immediate children under the root. A package's internals may nest as deep as you like; a package may not contain another package.
|
||||
- Use `.cjs` (not `.js`) so the config's `module.exports` works even in `"type": "module"` repos.
|
||||
@@ -0,0 +1,95 @@
|
||||
// @ts-check
|
||||
// Deep-module enforcement for dependency-cruiser.
|
||||
//
|
||||
// Each package under the packages root is a DEEP MODULE: a lot of behaviour
|
||||
// behind a small interface. A package's PUBLIC SURFACE is its ENTRY POINTS —
|
||||
// the files at the package root. Implementation lives in SUBFOLDERS and is
|
||||
// private — by convention `lib/` for implementation and `tests/` for tests,
|
||||
// though any subfolder is private. A package may expose several small entry
|
||||
// points (index.ts, client.ts, server.ts, …) — prefer that over one giant
|
||||
// barrel index.
|
||||
//
|
||||
// The only thing you should ever need to edit here is PACKAGES_ROOT.
|
||||
|
||||
/** Where packages live. One immediate child dir per package (flat, no nesting). */
|
||||
const PACKAGES_ROOT = "src/packages";
|
||||
|
||||
// --- derived patterns (no need to edit) -------------------------------------
|
||||
const R = PACKAGES_ROOT;
|
||||
/**
|
||||
* A package's private internals: anything nested inside a package subfolder.
|
||||
* The package's root files are its entry points and are NOT matched here —
|
||||
* they stay importable from outside.
|
||||
*/
|
||||
const PACKAGE_INTERNALS = `^${R}/[^/]+/[^/]+/`;
|
||||
|
||||
/** @type {import('dependency-cruiser').IConfiguration} */
|
||||
module.exports = {
|
||||
forbidden: [
|
||||
{
|
||||
name: "entrypoint-boundary-from-app",
|
||||
comment:
|
||||
"App/root code may import a package's entry points (its root files), but nothing inside its subfolders.",
|
||||
severity: "error",
|
||||
from: { pathNot: `^${R}/` }, // importer is NOT inside any package
|
||||
to: { path: PACKAGE_INTERNALS },
|
||||
},
|
||||
{
|
||||
name: "entrypoint-boundary-across-packages",
|
||||
comment:
|
||||
"A package's own files import each other freely, but may reach OTHER packages only through their entry points — never their internals.",
|
||||
severity: "error",
|
||||
// importer is inside a package ($1), but is not a test file
|
||||
from: { path: `^${R}/([^/]+)/`, pathNot: `^${R}/[^/]+/tests/` },
|
||||
to: {
|
||||
path: PACKAGE_INTERNALS,
|
||||
pathNot: `^${R}/$1/`, // same package → intra-package freedom
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tests-through-entrypoints",
|
||||
comment:
|
||||
"A package's tests exercise it through its entry points like everyone else: they may import any package's entry points and their own tests/ fixtures, but never any package's internals — not even their own.",
|
||||
severity: "error",
|
||||
from: { path: `^${R}/([^/]+)/tests/` }, // a test file, in package $1
|
||||
to: {
|
||||
path: PACKAGE_INTERNALS,
|
||||
pathNot: `^${R}/$1/tests/`, // own tests/ fixtures → allowed
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tests-folder-is-private",
|
||||
comment:
|
||||
"A package's tests/ folder is reachable only from tests — nothing else may import fixtures.",
|
||||
severity: "error",
|
||||
from: { pathNot: `^${R}/[^/]+/tests/` }, // importer is not itself a test
|
||||
to: { path: `^${R}/[^/]+/tests/` },
|
||||
},
|
||||
{
|
||||
name: "no-circular",
|
||||
comment: "No dependency cycles. Scope to `^${R}/` if you want to allow cycles outside packages.",
|
||||
severity: "error",
|
||||
from: {},
|
||||
to: { circular: true },
|
||||
},
|
||||
|
||||
// --- Layering (optional, off by default) ----------------------------------
|
||||
// Interface-hiding controls HOW you import (through the entry points).
|
||||
// Layering controls WHICH packages may depend on which. Add your own rules
|
||||
// here, e.g.:
|
||||
//
|
||||
// {
|
||||
// name: "ui-may-not-depend-on-billing",
|
||||
// severity: "error",
|
||||
// from: { path: `^${R}/ui/` },
|
||||
// to: { path: `^${R}/billing/` },
|
||||
// },
|
||||
],
|
||||
options: {
|
||||
doNotFollow: { path: "node_modules" },
|
||||
tsConfig: { fileName: "tsconfig.json" },
|
||||
enhancedResolveOptions: {
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user