Compare commits

..
Author SHA1 Message Date
Matt PocockandClaude Opus 4.8 575d14b806 Trim Notes block to its definition (cut no-op behavioral prose)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:44:56 +01:00
Matt PocockandClaude Opus 4.8 79a5c9cc55 Keep Prototype name; move Notes block to end of map
- Revert Sketch back to Prototype to mesh with the /prototype skill and
  existing vocabulary, keeping the generalized function-first definition.
- Move the `## Notes` block from the top of the map to the end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:40:52 +01:00
Matt PocockandClaude Opus 4.8 3dc6800550 Generalize decision-mapping beyond engineering
Decouple the skill from engineering so it plans any domain (e.g. course
content) without specializing toward it. Domain specifics live in the map,
not the skill body.

- Add an optional `## Notes` block to the map: declares the domain + skills
  to consult, plus freeform standing preferences. Loaded into every session,
  honored by every session, accrues as it emerges.
- Rename the Prototype ticket type to Sketch and define it by function —
  raise the fidelity of the discussion with a cheap concrete artifact to
  react to (/prototype is one medium among outline/rough-take/stub).
- Note that validation is the thread through all three types, not a type.
- Resolve step now consults skills named in Notes.

The session machinery (one ticket per session, claiming, parallelism,
handoff) is unchanged — it follows from the AI agent's smart/dumb zones, not
the domain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:38:34 +01:00
Matt PocockandClaude Opus 4.8 801dca6885 docs(CLAUDE.md): point to scripts/link-skills.sh for (re)linking skills
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 11:36:43 +01:00
Matt PocockandGitHub 999745ead1 Merge pull request #395 from mattpocock/dev-only-link-skills-note
docs(link-skills): mark as a dev-only script
2026-06-30 10:29:36 +01:00
Matt PocockandClaude Opus 4.8 6b8c429bc6 docs(link-skills): mark as a dev-only script
Add a header note clarifying that link-skills.sh is a dev-only script for
maintainers, not a supported installer, and that modifications or requests
for modification will not be approved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:28:50 +01:00
Matt PocockandGitHub 7936cd4b8b Merge pull request #394 from mattpocock/review-smell-baseline
feat(review): always-on Fowler smell baseline in the Standards axis
2026-06-30 10:24:14 +01:00
Matt PocockandClaude Opus 4.8 7a4c7561d4 Add changeset for review smell baseline
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:19:49 +01:00
Matt PocockandClaude Opus 4.8 0894b3300f review: add a terse fix to each smell in the baseline
Each smell now reads what-it-is → how-to-fix, telegraphic style — the fix
is the actionable half a reviewer needs, kept to a clause.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:02:31 +01:00
Matt PocockandClaude Opus 4.8 cac470445b feat(review): add an always-on Fowler smell baseline to the Standards axis
Folds Refactoring ch.3 "Bad Smells in Code" into the in-progress `review`
skill. The skill stays two-axis: smells layer into the Standards axis as a
fixed, always-on baseline alongside a repo's documented standards, rather
than a third axis.

Because the baseline is needed on every run it lives inline in SKILL.md.
Each smell is one line — its name carries the definition (the names are
strong leading words) plus a diff-specific cue. Curated to ~12 high-signal
smells; vague ones (Comments, Loops) and tooling-caught ones (Long
Parameter List) are deliberately dropped.

Two rules bind the baseline: a documented repo standard always overrides
it, and every smell is reported as a judgement call, never a hard
violation. The Standards sub-agent prompt now carries the baseline and
these rules.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:58:26 +01:00
Matt PocockandClaude Opus 4.8 43ea0884b0 tdd: add tautological-test anti-pattern
Tests whose assertion is recomputed the way the code computes it pass by
construction and give zero confidence. Add it as a peer of the existing
implementation-coupling anti-pattern: a Philosophy principle, a per-cycle
checklist gate, and a BAD/GOOD example pair in tests.md. Includes a patch
changeset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 21:22:50 +01:00
Matt PocockandClaude Opus 4.8 a116824938 decision-mapping: key tickets by dash-case slugs, not numeric ids
Replace numeric ticket ids (#1, #2…) with short, token-efficient
dash-case slugs (e.g. relational-db) that read as mini-titles. Update
the Structure example, the next-ticket selection rule (lowest-numbered →
first in document order), the optional-arg wording, and the Handoff
copy-paste examples accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 16:50:46 +01:00
Matt Pocock 448d0adee7 Merge branch 'main' of https://github.com/mattpocock/skills
# Conflicts:
#	skills/in-progress/decision-mapping/SKILL.md
2026-06-29 13:53:15 +01:00
Matt Pocock 850873cd73 feat: implement model-invoked prototype skill and enhance wizard functionality 2026-06-29 13:51:38 +01:00
15 changed files with 373 additions and 31 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"mattpocock-skills": minor
---
Make the **`prototype`** skill model-invoked, so the agent can reach for it autonomously (and other skills can too). Its description is rewritten around the leading word _prototype_ — throwaway code that answers a design question — with one trigger per branch (state/logic sanity-check, or UI exploration).
+5
View File
@@ -0,0 +1,5 @@
---
"mattpocock-skills": patch
---
Give the in-progress **`review`** skill an always-on Fowler smell baseline on its Standards axis. A curated ~12 high-signal "Bad Smells in Code" (Mysterious Name, Duplicated Code, Feature Envy, Data Clumps, Primitive Obsession, Repeated Switches, Shotgun Surgery, Divergent Change, Speculative Generality, Message Chains, Middle Man, Refused Bequest) are inlined into `SKILL.md` as a fixed baseline alongside whatever the repo documents — not a new third axis. Two binding rules keep it safe: a documented repo standard overrides the baseline, and every smell is reported as a judgement call, never a hard violation.
+5
View File
@@ -0,0 +1,5 @@
---
"mattpocock-skills": patch
---
Add the **tautological test** anti-pattern to the `tdd` skill. Tests whose assertion is recomputed the way the code computes it pass by construction and give zero confidence — distinct from the implementation-coupling anti-pattern already covered. Added as a peer at the same three sites: a Philosophy principle (expected values must come from an independent source of truth), a per-cycle checklist gate, and a BAD/GOOD example pair in `tests.md`.
+2
View File
@@ -14,3 +14,5 @@ Each skill entry in the top-level `README.md` must link the skill name to its `S
Each bucket folder has a `README.md` that lists every skill in the bucket with a one-line description, with the skill name linked to its `SKILL.md`. Bucket `README.md`s and the top-level `README.md` group entries into **User-invoked** and **Model-invoked**. Each bucket folder has a `README.md` that lists every skill in the bucket with a one-line description, with the skill name linked to its `SKILL.md`. Bucket `README.md`s and the top-level `README.md` group entries into **User-invoked** and **Model-invoked**.
Every `SKILL.md` is either user-invoked (`disable-model-invocation: true`, reachable only by the human) or model-invoked (model- or user-reachable). For the full definitions, description conventions, and why a user-invoked skill can invoke model-invoked skills but never another user-invoked one, see [docs/invocation.md](./docs/invocation.md). Every `SKILL.md` is either user-invoked (`disable-model-invocation: true`, reachable only by the human) or model-invoked (model- or user-reachable). For the full definitions, description conventions, and why a user-invoked skill can invoke model-invoked skills but never another user-invoked one, see [docs/invocation.md](./docs/invocation.md).
To (re)link every skill into the local harness skill directories (`~/.claude/skills`, `~/.agents/skills`), run `scripts/link-skills.sh`. Each entry is a symlink into this repo, so a `git pull` keeps installed skills current; re-run the script after adding, removing, or renaming a skill.
+1 -1
View File
@@ -156,10 +156,10 @@ Skills I use daily for code work.
- **[setup-matt-pocock-skills](./skills/engineering/setup-matt-pocock-skills/SKILL.md)** — Configure this repo for the engineering skills (issue tracker, triage labels, domain doc layout). Run once per repo before using the other engineering skills. - **[setup-matt-pocock-skills](./skills/engineering/setup-matt-pocock-skills/SKILL.md)** — Configure this repo for the engineering skills (issue tracker, triage labels, domain doc layout). Run once per repo before using the other engineering skills.
- **[to-issues](./skills/engineering/to-issues/SKILL.md)** — Break any plan, spec, or PRD into independently-grabbable issues using vertical slices. - **[to-issues](./skills/engineering/to-issues/SKILL.md)** — Break any plan, spec, or PRD into independently-grabbable issues using vertical slices.
- **[to-prd](./skills/engineering/to-prd/SKILL.md)** — Turn the current conversation into a PRD and publish it to the issue tracker. No interview — just synthesizes what you've already discussed. - **[to-prd](./skills/engineering/to-prd/SKILL.md)** — Turn the current conversation into a PRD and publish it to the issue tracker. No interview — just synthesizes what you've already discussed.
- **[prototype](./skills/engineering/prototype/SKILL.md)** — Build a throwaway prototype to flesh out a design — either a runnable terminal app for state/business-logic questions, or several radically different UI variations toggleable from one route.
**Model-invoked** **Model-invoked**
- **[prototype](./skills/engineering/prototype/SKILL.md)** — Build a throwaway prototype to answer a design question — a runnable terminal app for state/logic questions, or several radically different UI variations toggleable from one route.
- **[diagnosing-bugs](./skills/engineering/diagnosing-bugs/SKILL.md)** — Disciplined diagnosis loop for hard bugs and performance regressions: reproduce → minimise → hypothesise → instrument → fix → regression-test. - **[diagnosing-bugs](./skills/engineering/diagnosing-bugs/SKILL.md)** — Disciplined diagnosis loop for hard bugs and performance regressions: reproduce → minimise → hypothesise → instrument → fix → regression-test.
- **[tdd](./skills/engineering/tdd/SKILL.md)** — Test-driven development with a red-green-refactor loop. Builds features or fixes bugs one vertical slice at a time. - **[tdd](./skills/engineering/tdd/SKILL.md)** — Test-driven development with a red-green-refactor loop. Builds features or fixes bugs one vertical slice at a time.
- **[domain-modeling](./skills/engineering/domain-modeling/SKILL.md)** — Actively build and sharpen a project's domain model — challenge terms against the glossary, stress-test with edge-case scenarios, and update `CONTEXT.md` and ADRs inline. - **[domain-modeling](./skills/engineering/domain-modeling/SKILL.md)** — Actively build and sharpen a project's domain model — challenge terms against the glossary, stress-test with edge-case scenarios, and update `CONTEXT.md` and ADRs inline.
+4
View File
@@ -1,6 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# NOTE: This is a dev-only script, intended for use by maintainers of this repo.
# It is not a supported installer. Modifications to it — or requests for
# modifications — will not be approved.
#
# Links all skills in the repository into the local skill directories used by # Links all skills in the repository into the local skill directories used by
# each agent harness: # each agent harness:
# - ~/.claude/skills — Claude Code # - ~/.claude/skills — Claude Code
+2 -1
View File
@@ -13,12 +13,13 @@ 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. - **[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-issues](./to-issues/SKILL.md)** — Break any plan, spec, or PRD into independently-grabbable issues using vertical slices. - **[to-issues](./to-issues/SKILL.md)** — Break any plan, spec, or PRD into independently-grabbable issues using vertical slices.
- **[to-prd](./to-prd/SKILL.md)** — Turn the current conversation into a PRD and publish it to the issue tracker. - **[to-prd](./to-prd/SKILL.md)** — Turn the current conversation into a PRD and publish it to the issue tracker.
- **[prototype](./prototype/SKILL.md)** — Build a throwaway prototype — a runnable terminal app for state/logic questions, or several toggleable UI variations.
## Model-invoked ## Model-invoked
Model- or user-reachable (rich trigger phrasing so the model can reach for them). Model- or user-reachable (rich trigger phrasing so the model can reach for them).
- **[prototype](./prototype/SKILL.md)** — Build a throwaway prototype to answer a design question: a runnable terminal app for state/logic, or several toggleable UI variations.
- **[diagnosing-bugs](./diagnosing-bugs/SKILL.md)** — Disciplined diagnosis loop for hard bugs and performance regressions: reproduce → minimise → hypothesise → instrument → fix → regression-test. - **[diagnosing-bugs](./diagnosing-bugs/SKILL.md)** — Disciplined diagnosis loop for hard bugs and performance regressions: reproduce → minimise → hypothesise → instrument → fix → regression-test.
- **[tdd](./tdd/SKILL.md)** — Test-driven development with a red-green-refactor loop. Builds features or fixes bugs one vertical slice at a time. - **[tdd](./tdd/SKILL.md)** — Test-driven development with a red-green-refactor loop. Builds features or fixes bugs one vertical slice at a time.
- **[domain-modeling](./domain-modeling/SKILL.md)** — Actively build and sharpen a project's domain model — challenge terms, stress-test with scenarios, update `CONTEXT.md` and ADRs inline. - **[domain-modeling](./domain-modeling/SKILL.md)** — Actively build and sharpen a project's domain model — challenge terms, stress-test with scenarios, update `CONTEXT.md` and ADRs inline.
+1 -2
View File
@@ -1,7 +1,6 @@
--- ---
name: prototype name: prototype
description: Build a throwaway prototype to flesh out a design — a runnable terminal app for state/business-logic questions, or several radically different UI variations toggleable from one route. description: Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.
disable-model-invocation: true
--- ---
# Prototype # Prototype
+3
View File
@@ -13,6 +13,8 @@ description: Test-driven development. Use when the user wants to build features
**Bad tests** are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior. **Bad tests** are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior.
**Tautological tests** restate the implementation inside the assertion, so they pass by construction and give zero confidence. When the expected value is computed the way the code computes it — `expect(add(a, b)).toBe(a + b)`, snapshotting a figure you derived by hand the same way the code does, asserting a constant equals itself — the test can never disagree with the code: break the code wrong and the assertion breaks wrong with it. The expected value must come from an independent source of truth — a known-good literal, a worked example, the spec.
See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines. See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines.
## Anti-Pattern: Horizontal Slices ## Anti-Pattern: Horizontal Slices
@@ -103,6 +105,7 @@ After all tests pass, look for [refactor candidates](refactoring.md):
[ ] Test describes behavior, not implementation [ ] Test describes behavior, not implementation
[ ] Test uses public interface only [ ] Test uses public interface only
[ ] Test would survive internal refactor [ ] Test would survive internal refactor
[ ] Expected values are independent literals, not recomputed from the code
[ ] Code is minimal for this test [ ] Code is minimal for this test
[ ] No speculative features added [ ] No speculative features added
``` ```
+16
View File
@@ -59,3 +59,19 @@ test("createUser makes user retrievable", async () => {
expect(retrieved.name).toBe("Alice"); expect(retrieved.name).toBe("Alice");
}); });
``` ```
**Tautological tests**: Expected value restates the implementation, so the test passes by construction.
```typescript
// BAD: Expected value is recomputed the way the code computes it
test("calculateTotal sums line items", () => {
const items = [{ price: 10 }, { price: 5 }];
const expected = items.reduce((sum, i) => sum + i.price, 0);
expect(calculateTotal(items)).toBe(expected);
});
// GOOD: Expected value is an independent, known literal
test("calculateTotal sums line items", () => {
expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);
});
```
+1
View File
@@ -5,6 +5,7 @@ Skills that are still being developed. They're not ready to ship — expect roug
- **[decision-mapping](./decision-mapping/SKILL.md)** — Turn a loose idea into a sequenced map of investigation tickets, then drive them to resolution one at a time. User-invoked. - **[decision-mapping](./decision-mapping/SKILL.md)** — Turn a loose idea into a sequenced map of investigation tickets, then drive them to resolution one at a time. User-invoked.
- **[loop-me](./loop-me/SKILL.md)** — Grill yourself into implementable workflow specs over multiple sessions, using the current directory as a stateful workspace. User-invoked. - **[loop-me](./loop-me/SKILL.md)** — Grill yourself into implementable workflow specs over multiple sessions, using the current directory as a stateful workspace. User-invoked.
- **[review](./review/SKILL.md)** — Review changes since a fixed point along two parallel axes: **Standards** (does the diff follow the repo's coding standards?) and **Spec** (does the diff faithfully implement the originating issue/PRD?). - **[review](./review/SKILL.md)** — Review changes since a fixed point along two parallel axes: **Standards** (does the diff follow the repo's coding standards?) and **Spec** (does the diff faithfully implement the originating issue/PRD?).
- **[wizard](./wizard/SKILL.md)** — Generate an interactive bash wizard that walks a human through a manual procedure (setup, a one-off migration, a state transition) — opening URLs, capturing values, writing `.env` and GitHub Actions secrets. User-invoked.
- **[writing-beats](./writing-beats/SKILL.md)** — Shape an article as a journey of beats, choose-your-own-adventure style. Pick a starting beat, write only that beat, then pivot to the next, until the article reaches a natural end. - **[writing-beats](./writing-beats/SKILL.md)** — Shape an article as a journey of beats, choose-your-own-adventure style. Pick a starting beat, write only that beat, then pivot to the next, until the article reaches a natural end.
- **[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-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. - **[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.
+50 -25
View File
@@ -4,7 +4,7 @@ description: Turn a loose idea into a sequenced map of investigation tickets, th
disable-model-invocation: true disable-model-invocation: true
--- ---
This skill is invoked when a loose idea requires more than one agent session to turn into a plan. It creates a stateful decision map in a markdown file, and drives the user through a sequence of tickets to resolve the open questions - which may require either prototyping, research or discussion. This skill is invoked when a loose idea requires more than one agent session to turn into a plan. It creates a stateful decision map in a markdown file, and drives the user through a sequence of tickets to resolve the open questions - which may require either prototyping, research or grilling. The map is domain-agnostic: it plans engineering work, course content, or anything else that fits the same shape.
## The Decision Map ## The Decision Map
@@ -14,12 +14,15 @@ Assets created during tickets should be linked to from the map, not duplicated w
### Structure ### Structure
Numbered entries ("tickets"), each its own section keyed by its number: Entries ("tickets"), each its own section keyed by a short dash-case slug that
reads as a mini-title (e.g. `relational-db`, `auth-strategy`, `cache-layer`) —
terse enough to stay token-efficient, and unique within the map.
```markdown ```markdown
## #1: Relational Or Non-Relational Database? ## relational-db: Relational Or Non-Relational Database?
Blocked by: #<ticket-number>, #<ticket-number> Blocked by: <slug>, <slug>
Status: open | in-progress | resolved
Type: Research | Prototype | Grilling Type: Research | Prototype | Grilling
### Question ### Question
@@ -31,6 +34,12 @@ Type: Research | Prototype | Grilling
<answer-here> <answer-here>
``` ```
The slug is the canonical id, used in every `Blocked by` edge and prose
reference; the title after the colon is optional. A ticket
is **unblocked** when every ticket in its `Blocked by` list is `resolved`. A
session **claims** its ticket by setting `Status: in-progress` and saving the map
before any work, so concurrent sessions skip it.
Each ticket must be sized to one 100K token agent session. Each ticket must be sized to one 100K token agent session.
## Ticket Types ## Ticket Types
@@ -38,47 +47,63 @@ Each ticket must be sized to one 100K token agent session.
There are three types of tickets: There are three types of tickets:
- **Research**: Reading documentation, third-party API's, or local resources like knowledge bases. Creates a markdown summary as an asset. Use this when knowledge outside the current working directory is required. - **Research**: Reading documentation, third-party API's, or local resources like knowledge bases. Creates a markdown summary as an asset. Use this when knowledge outside the current working directory is required.
- **Prototype**: Writing UI or logic code to test a hypothesis, or to explore a design space. Uses the /prototype skill. Creates a prototype as an asset. Use this when "how should it look" or "how should it behave" is the key question. - **Prototype**: Raise the fidelity of the discussion by making a cheap, rough, concrete artifact to react to — an outline, a rough take, a stub, or UI/logic code via the /prototype skill. Creates the prototype as an asset. Use this when "how should it look" or "how should it behave" is the key question.
- **Grilling**: Conversation with the agent. Uses the /grilling and /domain-modeling skills. Asks one question at a time. The default case. - **Grilling**: Conversation with the agent. Uses the /grilling and /domain-modeling skills. Asks one question at a time. The default case.
Validation isn't a fourth type — it's the thread running through all three.
## Fog of war ## Fog of war
The map is _deliberately_ incomplete beyond the frontier. Your job is to investigate the frontier, and to resolve tickets in order to push the frontier forward. Push back the fog of war, one node at a time. The map is _deliberately_ incomplete beyond the frontier. Your job is to investigate the frontier, and to resolve tickets in order to push the frontier forward. Push back the fog of war, one node at a time — until the path to the finish line is clear and no tickets remain.
At some point, the fog of war should have been pushed back far enough that the path to the finish line is clear. At that point, no more tickets will be required and the decision map can be considered 'done'.
## Invocation ## Invocation
There are two ways this skill can be invoked: **bootstrap** and **resume**. Two branches. Either way, **every session ends with a [Handoff](#handoff)** — never resolve more than one ticket per session.
### Bootstrap ### Create the map
User invokes with a loose idea. User invokes with a loose idea.
1. Run a /grilling + /domain-modeling session to surface the open decisions. Ask one question at a time. 1. Run a `/grilling` and `/domain-modeling` session to surface the open decisions. Ask one question at a time.
2. Write a new decision map — mostly fog, frontier identified, trivially-decidable entries resolved inline. 2. Write a new decision map — mostly fog, frontier identified, trivially-decidable entries resolved inline.
3. Stop. Map-building is one session's work; do not also resolve tickets. 3. Handoff. Map-building is one session's work; do not also resolve tickets.
### Resume ### Work through the map
User invokes with a path to an existing map and a ticket number. User invokes with a path to an existing map. A ticket slug is **optional** — without one, you pick the next decision, not the user.
1. Load the **whole map** as context. 1. Load the **whole map** as context.
2. Run a session to resolve the ticket, invoking skills as needed. If in doubt, use `/grilling` and `/domain-modeling`. 2. Choose the ticket. If the user named one, use it. Otherwise pick the first `open` ticket in document order that is [unblocked](#structure). [Claim it](#structure): set `Status: in-progress` and save before any work.
3. Record what the session resolved in the ticket's body. 3. Resolve it, invoking skills as needed — including any the `## Notes` block names. If in doubt, use `/grilling` and `/domain-modeling`.
4. Add newly-discovered tickets (with correct `blocked_by` edges). 4. Record the answer in the ticket's body and set `Status: resolved`.
5. Stop. 5. Add newly-discovered tickets with correct `Blocked by` edges. If the decisions made invalidate other parts of the map, update or delete those nodes.
6. Handoff.
If the decisions made invalidate other parts of the map, update or delete those nodes. The user may run unblocked tickets in parallel, so expect other agents to be editing the map in their own sessions.
## Parallelism ## Handoff
The user may choose to run tickets in parallel, so expect other agents to make changes to the map. End every session by clearing the context and opening one or more fresh sessions. Close with a **Next steps** block the user can copy-paste. Two cases:
## Skipping The Decision Map **Open tickets remain.** List the currently-unblocked tickets, then give two copy-paste options: a bare command for one session (you pick the next ticket), and one pinned command per unblocked ticket for running them in parallel. Paste one line per fresh window — opening one, some, or all of them.
Many times, the initial grilling will result in no fog of war. No unresolved tickets. Nothing to do, except implement. > **Next steps** — 3 tickets unblocked: `auth-strategy`, `cache-layer`, `rate-limits`.
> Clear the context, then open fresh sessions.
>
> **One session** — resolves the next unblocked ticket:
> ```
> Invoke /decision-mapping with the map at <path>.
> ```
>
> **Parallel** — paste one line per window, up to all 3:
> ```
> Invoke /decision-mapping with the map at <path>, ticket auth-strategy.
> Invoke /decision-mapping with the map at <path>, ticket cache-layer.
> Invoke /decision-mapping with the map at <path>, ticket rate-limits.
> ```
In those situations, you should offer the user the chance to skip the decision map - since the decision map is only needed if multi-session decisions need to be made. **No open tickets remain.** The fog is pushed back far enough that the path to the finish line is clear — the map is done. (The initial grilling may also surface no fog at all, in which case there was never a map to build.) Recommend implementing directly, or using `/to-prd` to schedule a multi-session implementation.
If they skip it, you should recommend either implementing directly or using `/to-prd` to schedule a multi-session implementation. ## Notes
An optional block declaring the **domain**, any skills every session should `consult`, and freeform standing preferences the planning surfaces.
+22 -2
View File
@@ -35,6 +35,26 @@ Look for the originating spec, in this order:
Anything in the repo that documents how code should be written, such as `CODING_STANDARDS.md` or `CONTRIBUTING.md`. Anything in the repo that documents how code should be written, such as `CODING_STANDARDS.md` or `CONTRIBUTING.md`.
On top of whatever the repo documents, the Standards axis always carries the **smell baseline** below — a fixed set of Fowler code smells (_Refactoring_, ch.3) that applies even when a repo documents nothing. Two rules bind it:
- **The repo overrides.** A documented repo standard always wins; where it endorses something the baseline would flag, suppress the smell.
- **Always a judgement call.** Each smell is a labelled heuristic ("possible Feature Envy"), never a hard violation — and, like any standard here, skip anything tooling already enforces.
Each smell reads *what it is**how to fix*; match it against the diff:
- **Mysterious Name** — a function, variable, or type whose name doesn't reveal what it does or holds. → rename it; if no honest name comes, the design's murky.
- **Duplicated Code** — the same logic shape appears in more than one hunk or file in the change. → extract the shared shape, call it from both.
- **Feature Envy** — a method that reaches into another object's data more than its own. → move the method onto the data it envies.
- **Data Clumps** — the same few fields or params keep travelling together (a type wanting to be born). → bundle them into one type, pass that.
- **Primitive Obsession** — a primitive or string standing in for a domain concept that deserves its own type. → give the concept its own small type.
- **Repeated Switches** — the same `switch`/`if`-cascade on the same type recurs across the change. → replace with polymorphism, or one map both sites share.
- **Shotgun Surgery** — one logical change forces scattered edits across many files in the diff. → gather what changes together into one module.
- **Divergent Change** — one file or module is edited for several unrelated reasons. → split so each module changes for one reason.
- **Speculative Generality** — abstraction, parameters, or hooks added for needs the spec doesn't have. → delete it; inline back until a real need shows.
- **Message Chains** — long `a.b().c().d()` navigation the caller shouldn't depend on. → hide the walk behind one method on the first object.
- **Middle Man** — a class or function that mostly just delegates onward. → cut it, call the real target direct.
- **Refused Bequest** — a subclass or implementer that ignores or overrides most of what it inherits. → drop the inheritance, use composition.
### 4. Spawn both sub-agents in parallel ### 4. Spawn both sub-agents in parallel
Send a single message with two `Agent` tool calls. Use the `general-purpose` subagent for both. Send a single message with two `Agent` tool calls. Use the `general-purpose` subagent for both.
@@ -42,8 +62,8 @@ Send a single message with two `Agent` tool calls. Use the `general-purpose` sub
**Standards sub-agent prompt** — include: **Standards sub-agent prompt** — include:
- The full diff command and commit list. - The full diff command and commit list.
- The list of standards-source files you found in step 3. - The list of standards-source files you found in step 3, **plus the smell baseline from step 3** pasted in full — the sub-agent has no other access to it.
- The brief: "Report — per file/hunk where relevant — every place the diff violates a documented standard. Cite the standard (file + the rule). Distinguish hard violations from judgement calls. Skip anything tooling enforces. Under 400 words." - The brief: "Report — per file/hunk where relevant — (a) every place the diff violates a documented standard: cite the standard (file + the rule); and (b) any baseline smell you spot: name it and quote the hunk. Distinguish hard violations from judgement calls — documented-standard breaches can be hard, but baseline smells are always judgement calls, and a documented repo standard overrides the baseline. Skip anything tooling enforces. Under 400 words."
**Spec sub-agent prompt** — include: **Spec sub-agent prompt** — include:
+45
View File
@@ -0,0 +1,45 @@
---
name: wizard
description: Generate an interactive bash wizard that walks a human through a manual procedure — third-party setup, a one-off migration, an A→B state transition — opening URLs, capturing values, confirming each step, and writing .env files and GitHub Actions secrets.
disable-model-invocation: true
---
# Wizard
A **wizard** is a bash script that walks a human, step by step, through a manual procedure that's tedious to do by hand and tedious to re-explain to an AI every time. It opens each URL, says exactly what to click and copy, captures the values, writes them where they belong (`.env`, GitHub secrets), confirms at every stage, and shows how much is left. It might configure third-party services, run a one-off migration, or move the project from one state to another.
The delightful UX is already solved by [template.sh](template.sh) — progress with time-remaining, confirmation gates, cross-platform URL opening (including WSL), hidden secret entry, idempotent `.env` upserts, `gh secret`/`gh variable` writes, and a closing summary. **Your job is only to scope the procedure and author its stages.** The library above the `STAGES` marker is identical in every wizard; that consistency is the point — never hand-edit it.
A wizard is ephemeral by default — built for one run, saved to a scratch or `scripts/` path, deleted when the job's done. Commit it only when the user wants a repeatable setup path that should live in the repo.
## Process
### 1. Scope the procedure
Work out every manual step the human must take and every value that gets captured along the way. Read the repo first — don't ask cold:
- For setup: `.env`, `.env.example`, `.env.*`, `README`, `docker-compose*`, framework config, and `.github/workflows/*` (every `secrets.*` / `vars.*` reference is a value the wizard must produce).
- For a migration or transition: the current state, the target state, and the irreversible actions between them.
Then show the user the ordered list of stages and the values each produces, and confirm — they may add, drop, or reorder.
**Done when:** every stage is named in order, and for each captured value you know (a) where the human gets it, (b) where it's written (`.env`, a GitHub secret, both, or nowhere — some stages are pure actions), and (c) whether it's secret (hidden entry) or public.
### 2. Map each stage's journey
For each stage, write the precise path a human follows: which URL to open, what to do there, where a value is shown, which variable it fills — e.g. "Dashboard → Developers → API keys → Reveal test key → copy". Where you don't actually know the current UI or the exact command, say so and ask the user or check the docs — never invent steps that may not exist.
**Done when:** every stage traces to concrete instructions a stranger could follow.
### 3. Author the wizard
Copy `template.sh` to the target path. Replace the example stage with one `stage` per step, in dependency order. Use the library helpers — `stage`, `say`/`step`, `open_url`, `ask`/`ask_secret`, `write_env`, `set_secret`/`set_var`, `pause`/`confirm` — and set `TOTAL_STAGES` and `TOTAL_MINUTES` to honest estimates (this drives the time-remaining display).
Hold the bar the template sets: open the URL before asking for its value, use `ask_secret` for anything secret, `write_env` every persisted value, `set_secret` only the values CI actually needs, and `confirm` before any irreversible action. Each `stage` clears the screen so only the current step is visible — keep a stage to one focused task so nothing the human needs scrolls away. Don't touch the library above the marker.
### 4. Verify and hand off
- `bash -n <script>`; run `shellcheck` if available.
- `chmod +x <script>`.
- Don't run it end-to-end yourself — it opens browsers and blocks on human input. Trace it statically instead: every value from step 1 is captured and lands where step 1 said, and every `set_secret` name exactly matches a `secrets.*` reference in CI.
- Tell the user how to run it. If it's a repeatable setup path, commit it and link it from the README so the next person runs the script instead of asking an AI.
+211
View File
@@ -0,0 +1,211 @@
#!/usr/bin/env bash
#
# A wizard — walks a human through a manual procedure step by step.
# Generated by the /wizard skill.
#
# Everything above the "STAGES" marker is the wizard library: do not hand-edit
# it. Author the per-step stages below the marker.
set -euo pipefail
# ──────────────────────────────────────────────────────────────────────────
# Wizard library — delightful, consistent UX. Identical across every wizard.
# ──────────────────────────────────────────────────────────────────────────
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && [[ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]]; then
BOLD=$(tput bold); DIM=$(tput dim); RESET=$(tput sgr0)
BLUE=$(tput setaf 4); GREEN=$(tput setaf 2); YELLOW=$(tput setaf 3); RED=$(tput setaf 1)
else
BOLD=""; DIM=""; RESET=""; BLUE=""; GREEN=""; YELLOW=""; RED=""
fi
# Author sets these two at the top of the stages section.
TOTAL_STAGES=0
TOTAL_MINUTES=0
_STAGE_INDEX=0
_MINUTES_ELAPSED=0
ENV_FILE="${ENV_FILE:-.env}"
WRITTEN_ENV=() # KEYs written to ENV_FILE this run
WRITTEN_SECRET=() # secret NAMEs set this run
SKIPPED=() # things we couldn't do (e.g. gh missing)
# _clear — wipe the terminal so only the current step is on screen. No-op when
# output isn't a terminal, so piped logs stay readable.
_clear() {
[[ -t 1 ]] || return 0
if command -v tput >/dev/null 2>&1; then tput clear; else printf '\033[2J\033[3J\033[H'; fi
}
# banner "Title" — opening frame: what this wizard does and how long it takes.
banner() {
_clear
printf '\n%s%s %s%s\n' "$BOLD" "$BLUE" "$1" "$RESET"
printf '%s %s stages · about %s minutes%s\n\n' \
"$DIM" "$TOTAL_STAGES" "$TOTAL_MINUTES" "$RESET"
printf '%s You drive the browser; this wizard tells you exactly what to do and\n' "$DIM"
printf ' captures the values you copy back. Stop any time with Ctrl-C and re-run\n'
printf ' later — it remembers values already saved.%s\n' "$RESET"
pause "Ready to start?"
}
# stage "Name" <minutes> — clear the screen, then announce a stage and show
# progress + time remaining. Clearing keeps only the current step on screen.
stage() {
_clear
_STAGE_INDEX=$((_STAGE_INDEX + 1))
local remaining=$((TOTAL_MINUTES - _MINUTES_ELAPSED))
(( remaining < 0 )) && remaining=0
_MINUTES_ELAPSED=$((_MINUTES_ELAPSED + ${2:-0}))
printf '\n%s%s▸ Stage %s/%s · %s%s %s(~%s min left)%s\n' \
"$BOLD" "$BLUE" "$_STAGE_INDEX" "$TOTAL_STAGES" "$1" "$RESET" "$DIM" "$remaining" "$RESET"
}
# say "..." — a plain instruction line.
say() { printf ' %s\n' "$1"; }
# step "..." — a numbered-feeling action the human takes in the browser.
step() { printf ' %s•%s %s\n' "$BLUE" "$RESET" "$1"; }
note() { printf ' %s%s%s\n' "$DIM" "$1" "$RESET"; }
warn() { printf ' %s⚠ %s%s\n' "$YELLOW" "$1" "$RESET"; }
# open_url URL — open in the human's browser, cross-platform incl. WSL.
open_url() {
local url="$1"
printf ' %s↗ opening%s %s\n' "$GREEN" "$RESET" "$url"
{ if command -v wslview >/dev/null 2>&1; then wslview "$url"
elif command -v explorer.exe >/dev/null 2>&1; then explorer.exe "$url"
elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$url"
elif command -v open >/dev/null 2>&1; then open "$url"
else warn "couldn't open a browser — visit it manually: $url"; fi
} >/dev/null 2>&1 || warn "couldn't open a browser — visit it manually: $url"
}
# pause "msg" — wait for the human to confirm they've done the manual part.
pause() {
printf ' %s%s%s ' "$DIM" "${1:-Press Enter to continue}" "$RESET"
read -r _ || true
}
# confirm "question" — y/N gate; returns success on yes.
confirm() {
local reply=""
printf ' %s? %s [y/N] ' "$YELLOW" "$1"
read -r reply || true
[[ "$reply" =~ ^[Yy] ]]
}
# _existing KEY — current value of KEY in ENV_FILE, if any.
_existing() {
[[ -f "$ENV_FILE" ]] || return 1
local line; line=$(grep -E "^${1}=" "$ENV_FILE" | tail -n1) || return 1
printf '%s' "${line#*=}"
}
# ask KEY "Prompt" — read a value into $KEY. Offers the existing .env value as
# a default on re-runs (Enter keeps it). Visible input (non-secret).
ask() {
local key="$1" prompt="$2" current input
current=$(_existing "$key" || true)
if [[ -n "$current" ]]; then
printf ' %s%s%s %s[Enter keeps current]%s ' "$BOLD" "$prompt" "$RESET" "$DIM" "$RESET"
else
printf ' %s%s%s ' "$BOLD" "$prompt" "$RESET"
fi
read -r input || true
[[ -z "$input" && -n "$current" ]] && input="$current"
printf -v "$key" '%s' "$input"
}
# ask_secret KEY "Prompt" — like ask, but input is hidden.
ask_secret() {
local key="$1" prompt="$2" current input
current=$(_existing "$key" || true)
if [[ -n "$current" ]]; then
printf ' %s%s%s %s[Enter keeps current]%s ' "$BOLD" "$prompt" "$RESET" "$DIM" "$RESET"
else
printf ' %s%s%s ' "$BOLD" "$prompt" "$RESET"
fi
read -rs input || true
printf '\n'
[[ -z "$input" && -n "$current" ]] && input="$current"
printf -v "$key" '%s' "$input"
}
# write_env KEY VALUE — upsert KEY=VALUE into ENV_FILE (creates it; replaces
# any existing line). Idempotent.
write_env() {
local key="$1" value="$2" tmp
touch "$ENV_FILE"
tmp=$(mktemp)
grep -vE "^${key}=" "$ENV_FILE" > "$tmp" || true
printf '%s=%s\n' "$key" "$value" >> "$tmp"
mv "$tmp" "$ENV_FILE"
WRITTEN_ENV+=("$key")
printf ' %s✓ wrote%s %s → %s\n' "$GREEN" "$RESET" "$key" "$ENV_FILE"
}
# set_secret NAME VALUE — set a GitHub Actions repo secret via gh. Falls back
# to a warning (and records it) if gh is unavailable or unauthenticated.
set_secret() {
local name="$1" value="$2"
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
if printf '%s' "$value" | gh secret set "$name" >/dev/null 2>&1; then
WRITTEN_SECRET+=("$name")
printf ' %s✓ set%s GitHub secret %s\n' "$GREEN" "$RESET" "$name"
return
fi
fi
SKIPPED+=("GitHub secret $name (set it manually: gh secret set $name)")
warn "skipped GitHub secret $name — gh not ready; set it later"
}
# set_var NAME VALUE — set a GitHub Actions repo variable (non-secret).
set_var() {
local name="$1" value="$2"
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
if gh variable set "$name" --body "$value" >/dev/null 2>&1; then
printf ' %s✓ set%s GitHub variable %s\n' "$GREEN" "$RESET" "$name"
return
fi
fi
SKIPPED+=("GitHub variable $name")
warn "skipped GitHub variable $name — gh not ready; set it later"
}
# finish — clear, then a closing summary of everything configured.
finish() {
_clear
printf '\n%s%s ✓ Setup complete%s\n' "$BOLD" "$GREEN" "$RESET"
(( ${#WRITTEN_ENV[@]} )) && note "wrote ${#WRITTEN_ENV[@]} value(s) to $ENV_FILE: ${WRITTEN_ENV[*]}"
(( ${#WRITTEN_SECRET[@]} )) && note "set ${#WRITTEN_SECRET[@]} GitHub secret(s): ${WRITTEN_SECRET[*]}"
if (( ${#SKIPPED[@]} )); then
printf '\n'; warn "still to do by hand:"
for s in "${SKIPPED[@]}"; do note " - $s"; done
fi
printf '\n'
}
# ──────────────────────────────────────────────────────────────────────────
# STAGES — author this section. One stage() per step the human takes.
# Replace the example below. Set the two totals to match the stages you write.
# ──────────────────────────────────────────────────────────────────────────
TOTAL_STAGES=1
TOTAL_MINUTES=5
banner "Stripe setup"
# ── Example stage: replace with your real steps ───────────────────────────
stage "Stripe — API keys" 5
say "We'll grab your Stripe test keys and store them for local dev + CI."
open_url "https://dashboard.stripe.com/test/apikeys"
step "On the API keys page, copy the Publishable key (starts pk_test_)."
ask STRIPE_PUBLISHABLE_KEY "Paste the publishable key:"
step "Click 'Reveal test key' on the Secret key row, then copy it."
ask_secret STRIPE_SECRET_KEY "Paste the secret key:"
write_env STRIPE_PUBLISHABLE_KEY "$STRIPE_PUBLISHABLE_KEY"
write_env STRIPE_SECRET_KEY "$STRIPE_SECRET_KEY"
set_secret STRIPE_SECRET_KEY "$STRIPE_SECRET_KEY" # CI needs this one
# ──────────────────────────────────────────────────────────────────────────
finish