From 7d22ff0c0244fb0774095995b02c6052de8b6b36 Mon Sep 17 00:00:00 2001 From: alexion Date: Fri, 31 Jul 2026 10:40:56 -0400 Subject: [PATCH] fix(skills): align invocation and prose conventions --- skills/prototype/LOGIC.md | 72 ++++++++++++++++++++++++++++----------- skills/prototype/SKILL.md | 41 ++++++++++++++++------ skills/prototype/UI.md | 67 ++++++++++++++++++++++++++---------- skills/research/SKILL.md | 6 ++-- skills/wayfinder/SKILL.md | 14 ++++---- 5 files changed, 143 insertions(+), 57 deletions(-) diff --git a/skills/prototype/LOGIC.md b/skills/prototype/LOGIC.md index e27f63f..8170a1e 100644 --- a/skills/prototype/LOGIC.md +++ b/skills/prototype/LOGIC.md @@ -1,6 +1,7 @@ # Logic Prototype -A tiny interactive terminal app that lets the user drive a state model by hand. Use this when the question is about **business logic, state transitions, or data shape** — the kind of thing that looks reasonable on paper but only feels wrong once you push it through real cases. +A tiny interactive terminal app that lets the user drive a state model by hand. +Use this when the question is about **business logic, state transitions, or data shape** — the kind of thing that looks reasonable on paper but only feels wrong once you push it through real cases. ## When this is the right shape @@ -9,47 +10,65 @@ A tiny interactive terminal app that lets the user drive a state model by hand. - "I want to feel out what the API should look like before writing it." - Anything where the user wants to **press buttons and watch state change**. -If the question is "what should this look like" — wrong branch. Use [UI.md](UI.md). +If the question is "what should this look like" — wrong branch. +Use [UI.md](UI.md). ## Process ### 1. State the question -Before writing code, write down what state model and what question you're prototyping. One paragraph, in the prototype's README or a comment at the top of the file. A logic prototype that answers the wrong question is pure waste — make the question explicit so it can be checked later, whether the user is watching now or returning to it AFK. +Before writing code, write down what state model and what question you're prototyping. +One paragraph, in the prototype's README or a comment at the top of the file. +A logic prototype that answers the wrong question is pure waste — make the question explicit so it can be checked later, whether the user is watching now or returning to it AFK. ### 2. Pick the language -Use whatever the host project uses. If the project has no obvious runtime (e.g. a docs repo), ask. +Use whatever the host project uses. +If the project has no obvious runtime (e.g. a docs repo), ask. Match the project's existing conventions for tooling — don't add a new package manager or runtime just for the prototype. ### 3. Isolate the logic in a portable module -Put the actual logic — the bit that's answering the question — behind a small, pure interface that could be lifted out and dropped into the real codebase later. The TUI around it is throwaway; the logic module shouldn't be. +Put the actual logic — the bit that's answering the question — behind a small, pure interface that could be lifted out and dropped into the real codebase later. +The TUI around it is throwaway. +The logic module shouldn't be. The right shape depends on the question: -- **A pure reducer** — `(state, action) => state`. Good when actions are discrete events and state is a single value. -- **A state machine** — explicit states and transitions. Good when "which actions are even legal right now" is part of the question. -- **A small set of pure functions** over a plain data type. Good when there's no implicit current state — just transformations. +- **A pure reducer** — `(state, action) => state`. + Good when actions are discrete events and state is a single value. +- **A state machine** — explicit states and transitions. + Good when "which actions are even legal right now" is part of the question. +- **A small set of pure functions** over a plain data type. + Good when there's no implicit current state — just transformations. - **A class or module with a clear method surface** when the logic genuinely owns ongoing internal state. -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. +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 on its own. ### 4. Build the smallest TUI that exposes the state -Build it as a **lightweight TUI** — on every tick, clear the screen (`console.clear()` / `print("\033[2J\033[H")` / equivalent) and re-render the whole frame. The user should always see one stable view, not an ever-growing scrollback. +Build it as a **lightweight TUI** — on every tick, clear the screen (`console.clear()` / `print("\033[2J\033[H")` / equivalent) and re-render the whole frame. +The user should always see one stable view, not an ever-growing scrollback. Each frame has two parts, in this order: -1. **Current state**, pretty-printed and diff-friendly (one field per line, or formatted JSON). Use **bold** for field names or section headers and **dim** for less important context (timestamps, IDs, derived values). Native ANSI escape codes are fine — `\x1b[1m` bold, `\x1b[2m` dim, `\x1b[0m` reset. No need to pull in a styling library unless one is already in the project. -2. **Keyboard shortcuts**, listed at the bottom: `[a] add user [d] delete user [t] tick clock [q] quit`. Bold the key, dim the description, or vice-versa — whatever reads cleanly. +1. **Current state**, pretty-printed and diff-friendly (one field per line, or formatted JSON). + Use **bold** for field names or section headers and **dim** for less important context (timestamps, IDs, derived values). + Native ANSI escape codes are fine — `\x1b[1m` bold, `\x1b[2m` dim, `\x1b[0m` reset. + No need to pull in a styling library unless one is already in the project. +2. **Keyboard shortcuts**, listed at the bottom: `[a] add user [d] delete user [t] tick clock [q] quit`. + Bold the key, dim the description, or vice-versa — whatever reads cleanly. Behaviour: -1. **Initialise state** — a single in-memory object/struct. Render the first frame on start. +1. **Initialise state** — a single in-memory object/struct. + Render the first frame on start. 2. **Read one keystroke (or one line)** at a time, dispatch to a handler that mutates state. 3. **Re-render** the full frame after every action — don't append, replace. 4. **Loop until quit.** @@ -58,13 +77,18 @@ The whole frame should fit on one screen. ### 5. Make it runnable in one command -Add a script to the project's existing task runner (`package.json` scripts, `Makefile`, `justfile`, `pyproject.toml`). The user should run `pnpm run ` or equivalent — never need to remember a path. +Add a script to the project's existing task runner (`package.json` scripts, `Makefile`, `justfile`, `pyproject.toml`). +The user should run `pnpm run ` or equivalent — never need to remember a path. If the host project has no task runner, just put the command at the top of the prototype's README. ### 6. Hand it over -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. +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 and the prototype @@ -75,8 +99,16 @@ The TUI shell rides along to the throwaway branch that keeps the prototype as a ## Anti-patterns -- **Don't add tests.** A prototype that needs tests is no longer a prototype. -- **Don't wire it to the real database.** Use an in-memory store unless the question is specifically about persistence. -- **Don't generalise.** No "what if we wanted to support X later." The prototype answers one question. -- **Don't blur the logic and the TUI together.** If the reducer / state machine references `console.log`, prompts, or terminal escape codes, it's no longer portable. Keep the TUI as a thin shell over a pure module. -- **Don't ship the TUI shell into production.** The shell is optimised for being driven by hand from a terminal. The logic module behind it is the bit worth keeping. +- **Don't add tests.** + A prototype that needs tests is no longer a prototype. +- **Don't wire it to the real database.** + Use an in-memory store unless the question is specifically about persistence. +- **Don't generalise.** + No "what if we wanted to support X later." + The prototype answers one question. +- **Don't blur the logic and the TUI together.** + If the reducer / state machine references `console.log`, prompts, or terminal escape codes, it's no longer portable. + Keep the TUI as a thin shell over a pure module. +- **Don't ship the TUI shell into production.** + The shell is optimised for being driven by hand from a terminal. + The logic module behind it is the bit worth keeping. diff --git a/skills/prototype/SKILL.md b/skills/prototype/SKILL.md index 4ca8748..bb2fca7 100644 --- a/skills/prototype/SKILL.md +++ b/skills/prototype/SKILL.md @@ -5,22 +5,43 @@ description: Build a throwaway prototype to answer a design question. Use when t # Prototype -A prototype is **throwaway code that answers a question**. The question decides the shape. +A prototype is **throwaway code that answers a question**. +The question decides the shape. ## Pick a branch Identify which question is being answered — from the user's prompt, the surrounding code, or by asking if the user is around: -- **"Does this logic / state model feel right?"** → [LOGIC.md](LOGIC.md). Build a tiny interactive terminal app that pushes the state machine through cases that are hard to reason about on paper. -- **"What should this look like?"** → [UI.md](UI.md). Generate several radically different UI variations on a single route, switchable via a URL search param and a floating bottom bar. +- **"Does this logic / state model feel right?"** → [LOGIC.md](LOGIC.md). + Build a tiny interactive terminal app that pushes the state machine through cases that are hard to reason about on paper. +- **"What should this look like?"** → [UI.md](UI.md). + Generate several radically different UI variations on a single route, switchable via a URL search param and a floating bottom bar. -The two branches produce very different artifacts — getting this wrong wastes the whole prototype. If the question is genuinely ambiguous and the user isn't reachable, default to whichever branch better matches the surrounding code (a backend module → logic; a page or component → UI) and state the assumption at the top of the prototype. +The two branches produce very different artifacts — getting this wrong wastes the whole prototype. +If the question is genuinely ambiguous and the user isn't reachable, default to whichever branch better matches the surrounding code (a backend module → logic, a page or component → UI) and state the assumption at the top of the prototype. ## Rules that apply to both -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 `, `python `, `bun `, 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. -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. **Capture it when done.** When the caller permits implementation, fold any validated decision into the real code. A planning-only caller such as Wayfinder stops at the verdict. Commit the prototype itself to a throwaway branch, out of main, as a **primary source**. Write a Prototype artifact under `$(xdg-user-dir DOCUMENTS)/ai-artifacts/projects//`, where `` is the lowercase basename of the current working directory, following the vault's `AGENTS.md`. Include the question, a context pointer to the branch, run instructions, and the verdict, plus screenshots and useful code snippets where they help preserve the result. The main branch keeps only a validated decision that the caller permitted the skill to fold in. +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 `, `python `, `bun `, 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. +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. **Capture it when done.** + When the caller permits implementation, fold any validated decision into the real code. + A planning-only caller such as Wayfinder stops at the verdict. + Commit the prototype itself to a throwaway branch, out of main, as a **primary source**. + Write a Prototype artifact under `$(xdg-user-dir DOCUMENTS)/ai-artifacts/projects//`, where `` is the lowercase basename of the current working directory, following the vault's `AGENTS.md`. + Include the question, a context pointer to the branch, run instructions, and the verdict, plus screenshots and useful code snippets where they help preserve the result. + The main branch keeps only a validated decision that the caller permitted the skill to fold in. diff --git a/skills/prototype/UI.md b/skills/prototype/UI.md index 06933b0..83deee9 100644 --- a/skills/prototype/UI.md +++ b/skills/prototype/UI.md @@ -1,8 +1,10 @@ # UI Prototype -Generate **several radically different UI variations** on a single route, switchable from a floating bottom bar. The user flips between variants in the browser, picks one (or steals bits from each), then throws the rest away. +Generate **several radically different UI variations** on a single route, switchable from a floating bottom bar. +The user flips between variants in the browser, picks one (or steals bits from each), then throws the rest away. -If the question is about logic/state rather than what something looks like — wrong branch. Use [LOGIC.md](LOGIC.md). +If the question is about logic/state rather than what something looks like — wrong branch. +Use [LOGIC.md](LOGIC.md). ## When this is the right shape @@ -13,21 +15,32 @@ If the question is about logic/state rather than what something looks like — w ## Two sub-shapes — strongly prefer sub-shape A -A UI prototype is much easier to judge when it's **butting up against the rest of the app** — real header, real sidebar, real data, real density. A throwaway route on its own is a vacuum: every variant looks fine in isolation. Default to sub-shape A whenever there's a plausible existing page to host the variants. Only reach for sub-shape B if the prototype genuinely has no nearby home. +A UI prototype is much easier to judge when it's **butting up against the rest of the app** — real header, real sidebar, real data, real density. +A throwaway route on its own is a vacuum: every variant looks fine in isolation. +Default to sub-shape A whenever there's a plausible existing page to host the variants. +Only reach for sub-shape B if the prototype genuinely has no nearby home. ### Sub-shape A — adjustment to an existing page (preferred) -The route already exists. Variants are rendered **on the same route**, gated by a `?variant=` URL search param. The existing data fetching, params, and auth all stay — only the rendering swaps. This is the default; pick it unless there's a specific reason not to. +The route already exists. +Variants are rendered **on the same route**, gated by a `?variant=` URL search param. +The existing data fetching, params, and auth all stay — only the rendering swaps. +This is the default. +Pick it unless there's a specific reason not to. -If the prototype is for something that doesn't yet have a page but *would naturally live inside one* (a new section of the dashboard, a new card on the settings screen, a new step in an existing flow) — that's still sub-shape A. Mount the variants inside the host page. +If the prototype is for something that doesn't yet have a page but *would naturally live inside one* (a new section of the dashboard, a new card on the settings screen, a new step in an existing flow) — that's still sub-shape A. +Mount the variants inside the host page. ### Sub-shape B — a new page (last resort) Only use this when the thing being prototyped genuinely has no existing page to live inside — e.g. an entirely new top-level surface, or a flow that can't be embedded anywhere sensible. -Create a **throwaway route** following whatever routing convention the project already uses — don't invent a new top-level structure. Name it so it's obviously a prototype (e.g. include the word `prototype` in the path or filename). Same `?variant=` pattern. +Create a **throwaway route** following whatever routing convention the project already uses — don't invent a new top-level structure. +Name it so it's obviously a prototype (e.g. include the word `prototype` in the path or filename). +Same `?variant=` pattern. -Before committing to sub-shape B, sanity-check: is there really no existing page this could be embedded in? An empty route hides design problems that a populated one would expose. +Before committing to sub-shape B, sanity-check: is there really no existing page this could be embedded in? +An empty route hides design problems that a populated one would expose. In both sub-shapes the floating bottom bar is identical. @@ -35,7 +48,8 @@ In both sub-shapes the floating bottom bar is identical. ### 1. State the question and pick N -Default to **3 variants**. More than 5 stops being radically different and starts being noise — cap there. +Default to **3 variants**. +More than 5 stops being radically different and starts being noise — cap there. Write down the plan in one line, in the prototype's location or a top-of-file comment: @@ -45,13 +59,16 @@ This works whether the user is here to push back or not. ### 2. Generate radically different variants -Draft each variant. Hold each one to: +Draft each variant. +Hold each one to: - The page's purpose and the data it has access to. - The project's component library / styling system (TailwindCSS, shadcn, MUI, plain CSS, whatever). - A clear exported component name, e.g. `VariantA`, `VariantB`, `VariantC`. -Variants must be **structurally different** — different layout, different information hierarchy, different primary affordance, not just different colours. Three slightly-tweaked card grids isn't a UI prototype, it's wallpaper. If two drafts come out too similar, redo one with explicit "do not use a card grid" guidance. +Variants must be **structurally different** — different layout, different information hierarchy, different primary affordance, not just different colours. +Three slightly-tweaked card grids isn't a UI prototype, it's wallpaper. +If two drafts come out too similar, redo one with explicit "do not use a card grid" guidance. ### 3. Wire them together @@ -70,7 +87,8 @@ return ( ); ``` -For sub-shape A (existing page): keep all the existing data fetching above the switcher; only the rendered subtree changes per variant. +For sub-shape A (existing page): keep all the existing data fetching above the switcher. +Only the rendered subtree changes per variant. For sub-shape B (new page): the throwaway route under `/prototype/` mounts the same switcher. @@ -85,15 +103,19 @@ A small fixed-position bar at the bottom-centre of the screen with three pieces: Behaviour: - Clicking an arrow updates the URL search param (use the framework's router — `router.replace` on Next, `navigate` on React Router, etc) so the variant is shareable and reload-stable. -- Keyboard: `←` and `→` arrow keys also cycle. Don't intercept arrow keys when an ``, `