From 80cecf33501635176aee9bc632a581f24467aff9 Mon Sep 17 00:00:00 2001 From: alexion Date: Fri, 31 Jul 2026 15:13:31 -0400 Subject: [PATCH] feat(skills): adapt planning skills to artifact workflow --- skills/prototype/LOGIC.md | 99 +++++++++++++----------------- skills/prototype/SKILL.md | 95 ++++++++++++++++++----------- skills/prototype/UI.md | 88 ++++++++++----------------- skills/research/SKILL.md | 48 ++++++++++++--- skills/wayfinder/ARTIFACTS.md | 28 +++++++-- skills/wayfinder/SKILL.md | 110 +++++++++++++++++++++------------- 6 files changed, 264 insertions(+), 204 deletions(-) diff --git a/skills/prototype/LOGIC.md b/skills/prototype/LOGIC.md index 8170a1e..f5a649c 100644 --- a/skills/prototype/LOGIC.md +++ b/skills/prototype/LOGIC.md @@ -1,7 +1,6 @@ # 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 @@ -10,105 +9,89 @@ Use this when the question is about **business logic, state transitions, or data - "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. +Use 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, so make the question explicit enough to check later whether the user is watching now or returning to it AFK. + +Done when the prototype states one concrete logic question and the model being tested. ### 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. +Match the project's existing conventions for tooling. +Don't add a new package manager or runtime just for the prototype. + +Done when the prototype has a runnable host-project language and toolchain without introducing a new runtime convention. ### 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. +This is what makes the prototype useful past its own lifetime. +When the question is answered, the validated reducer, machine, or function set can be lifted into the real module on its own. + +Done when all tested logic lives behind one portable, pure interface and the TUI depends on it in only one direction. ### 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.** The whole frame should fit on one screen. +Done when every available action re-renders a complete one-screen view of the current state and shortcuts. + ### 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. +If the host project has no task runner, put the command at the top of the prototype's README. + +Done when a fresh user can launch the prototype with one documented command. ### 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. +They drive it themselves. +The interesting moments are when they say "wait, that shouldn't be possible" or "huh, I assumed X would be different" because those expose bugs in the idea. +Add actions when the feedback needs them. -### 7. Capture the answer and the prototype +Done when the user can exercise the model and the prototype exposes every state transition needed to reach a verdict. -Once the prototype has answered its question, capture the answer, then capture the prototype the way the [SKILL](SKILL.md) describes. -When the caller permits implementation, the validated reducer, machine, or function set lifts into the real module as the absorbed decision. -A planning-only caller leaves the real module unchanged. -The TUI shell rides along to the throwaway branch that keeps the prototype as a primary source. +## Production mapping + +When the shared [SKILL](SKILL.md) permits production work, lift the validated reducer, machine, or function set into the real module. +Keep the TUI shell on the throwaway branch. ## 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 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 bb2fca7..15f9d0d 100644 --- a/skills/prototype/SKILL.md +++ b/skills/prototype/SKILL.md @@ -5,43 +5,70 @@ 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 one question**. +The question decides the branch. -## Pick a branch +## 1. 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: +Identify the question from the user's prompt and surrounding code. +Ask when it remains genuinely ambiguous and the user is reachable. -- **"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 or state model feel right?** + Follow [`LOGIC.md`](LOGIC.md) to build a tiny interactive terminal app that pushes the model through hard-to-reason-about cases. +- **What should this look like?** + Follow [`UI.md`](UI.md) to build several radically different UI variants on one route with a URL-controlled switcher. -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. +When the user is unavailable, default to Logic for a backend module and UI for a page or component, then state the assumption in the prototype. -## Rules that apply to both +Done when exactly one branch and one design question govern the prototype. -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. +## Common rules + +- **Throwaway from day one.** + Locate the code close to where it would be used, but name it so nobody mistakes it for production. + Follow the project's routing and source-layout conventions rather than inventing a new top-level structure. +- **One command to run.** + Use the project's existing task runner so the user does not need to remember a path or setup sequence. +- **No persistence by default.** + Keep state in memory unless persistence is the question being tested. + Use an unmistakably disposable database or local file when that question requires one. +- **Skip polish.** + Add no tests, production-grade error handling, speculative abstractions, or unrelated cleanup. +- **Surface state.** + Show the full relevant state after every Logic action or UI variant switch. + +## 2. Build and reach a verdict + +Follow the selected branch through its handover step and iterate on the prototype in response to the user's feedback. +Do not treat a runnable prototype as the result. +The result is the verdict that answers the design question. + +Done when the user has reached an explicit verdict or stated that the prototype did not resolve the question. + +## 3. Capture the primary source + +Commit the complete prototype to a throwaway branch outside main. +The branch is the primary source. + +Resolve the AI artifacts vault through `$(xdg-user-dir DOCUMENTS)/ai-artifacts` and read its `AGENTS.md` before writing. +Use the lowercase basename of the current working directory as the project. + +When the caller provides an allocated filename and `parent`, use them exactly and do not advance `.counter`. +Create only the Prototype artifact and leave the parent artifact unchanged. +Otherwise, allocate the next vault-sequence identifier and name the artifact `---prototype.md`. +Include `parent` only when an earlier artifact directly caused the prototype. + +The Prototype artifact links the throwaway branch and preserves the question, run instructions, verdict, and branch-appropriate evidence: + +- UI evidence uses screenshots. +- Logic evidence uses useful code snippets and, where needed, a short interaction transcript. + +Done when the complete prototype is committed outside main and exactly one Prototype artifact preserves the result according to the vault convention. + +## 4. Fold in the decision when permitted + +A planning-only caller such as Wayfinder stops after the verdict and leaves production code unchanged. +Otherwise, fold the validated decision into production only when the caller permits implementation. +Follow the selected branch's **Production mapping** and keep all other throwaway code out of main. + +Done when production is unchanged for a planning-only run, or contains only the permitted validated decision for an implementation run. diff --git a/skills/prototype/UI.md b/skills/prototype/UI.md index 83deee9..e75d566 100644 --- a/skills/prototype/UI.md +++ b/skills/prototype/UI.md @@ -1,10 +1,8 @@ # 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 @@ -15,32 +13,21 @@ Use [LOGIC.md](LOGIC.md). ## 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. @@ -48,8 +35,7 @@ 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: @@ -57,18 +43,19 @@ Write down the plan in one line, in the prototype's location or a top-of-file co This works whether the user is here to push back or not. +Done when the prototype states one concrete UI question, its host route, and a variant count from three through five. + ### 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. + +Done when every variant materially differs in layout, information hierarchy, and primary affordance while using the project's existing design system. ### 3. Wire them together @@ -87,11 +74,12 @@ 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. +Done when one route renders every variant from the URL parameter without duplicating data loading. + ### 4. Build the floating switcher A small fixed-position bar at the bottom-centre of the screen with three pieces: @@ -103,43 +91,31 @@ 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 ``, `