diff --git a/.changeset/prototype-primary-secondary-source.md b/.changeset/prototype-primary-secondary-source.md new file mode 100644 index 0000000..c574128 --- /dev/null +++ b/.changeset/prototype-primary-secondary-source.md @@ -0,0 +1,5 @@ +--- +"mattpocock-skills": minor +--- + +Reword how the **`prototype`** skill lets go of its artifacts, around a **primary vs secondary source** distinction. The _secondary source_ is the distilled answer (issue/ADR/commit) — still the part that matters most. The _primary source_ is the prototype itself: rather than deleting it, it's now preserved as runnable evidence on a throwaway branch (`prototype/`) linked from the relevant issue, never merged, so the main branch keeps only the validated decision. diff --git a/docs/engineering/prototype.md b/docs/engineering/prototype.md index af8ca42..14cb85d 100644 --- a/docs/engineering/prototype.md +++ b/docs/engineering/prototype.md @@ -31,9 +31,11 @@ The question decides the shape, and there are two shapes: Picking the wrong branch wastes the whole prototype, so the question comes first. Both branches keep state in memory, run from one command, and surface the full state on every step. -## The answer is the artifact +## Primary and secondary source -The code is disposable; the **answer** is the only thing worth keeping. When the prototype has settled its question, capture the verdict somewhere durable — a commit message, an ADR, an issue, or a `NOTES.md` next to it — alongside the question it answered, then delete or absorb the code. A prototype left rotting in the repo has outlived its purpose. +A finished prototype leaves two things, and they're kept differently. The **secondary source** is the _answer_ — the verdict plus the question it settled, distilled into durable prose (a commit message, an ADR, an issue). That's what future readers consult, and it's the part that matters most. The **primary source** is the _prototype itself_ — the runnable evidence the answer came from. + +The prototype doesn't belong in the main repo: no tests, no error handling, nothing to maintain. But that's not a reason to destroy it. Once the answer is captured, fold any validated decision into the real code, then move the prototype onto a throwaway branch linked from the relevant issue — never merged. The main branch stays clean; the raw exploration stays one click away for anyone who wants to re-run it. A prototype left rotting in the main branch has outlived its purpose — a prototype preserved as a primary source on a side branch hasn't. ## Where it fits diff --git a/skills/engineering/prototype/LOGIC.md b/skills/engineering/prototype/LOGIC.md index 526ecb1..854ae9a 100644 --- a/skills/engineering/prototype/LOGIC.md +++ b/skills/engineering/prototype/LOGIC.md @@ -68,7 +68,7 @@ Give the user the run command. They'll drive it themselves; the interesting mome ### 7. Capture the answer -When the prototype has done its job, the answer to the question is the only thing worth keeping. If the user is around, ask what it taught them. If not, leave a `NOTES.md` next to the prototype so the answer can be filled in (or filled in by you, if you've watched the session) before the prototype gets deleted. +When the prototype has done its job, capture the **answer** — the secondary source — somewhere durable (issue, ADR, commit message). If the user is around, ask what it taught them; if not, leave a `NOTES.md` next to the prototype so the answer can be filled in (or filled in by you, if you've watched the session). Then let go of the prototype the way the [SKILL](SKILL.md) describes: fold any validated logic into the real module, and move the prototype itself — the primary source — onto a throwaway branch linked from the relevant issue rather than deleting it. ## Anti-patterns diff --git a/skills/engineering/prototype/SKILL.md b/skills/engineering/prototype/SKILL.md index 9425f7d..685b499 100644 --- a/skills/engineering/prototype/SKILL.md +++ b/skills/engineering/prototype/SKILL.md @@ -21,10 +21,16 @@ The two branches produce very different artifacts — getting this wrong wastes 1. **Throwaway from day one, and clearly marked as such.** Locate the prototype code close to where it will actually be used (next to the module or page it's prototyping for) so context is obvious — but name it so a casual reader can see it's a prototype, not production. For throwaway UI routes, obey whatever routing convention the project already uses; don't invent a new top-level structure. 2. **One command to run.** Whatever the project's existing task runner supports — `pnpm `, `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 and then delete it. +4. **Skip the polish.** No tests, no error handling beyond what makes the prototype _runnable_, no abstractions. The point is to learn something fast and then let it go. 5. **Surface the state.** After every action (logic) or on every variant switch (UI), print or render the full relevant state so the user can see what changed. -6. **Delete or absorb when done.** When the prototype has answered its question, either delete it or fold the validated decision into the real code — don't leave it rotting in the repo. +6. **Let go of it when done.** Fold any validated decision into the real code, then preserve the prototype itself as a **primary source** — committed to a throwaway branch linked from the relevant issue, never merged to main (see _Primary vs secondary source_ below). The main branch keeps only the validated decision. -## When done +## When done: primary vs secondary source -The _answer_ is the only thing worth keeping from a prototype. Capture it somewhere durable (commit message, ADR, issue, or a `NOTES.md` next to the prototype) along with the question it was answering. If the user is around, that capture is a quick conversation; if not, leave the placeholder so they (or you, on the next pass) can fill in the verdict before deleting the prototype. +A finished prototype leaves two things behind, and they're kept differently. + +- **The secondary source is the _answer_** — the verdict plus the question it settled, distilled into durable prose: an issue comment, an ADR, or a commit message. This is what future readers actually consult, and it's the part that matters most. If the user is around, capturing it is a quick conversation; if not, leave a placeholder (a `NOTES.md` next to the prototype) so the verdict can be filled in — by them, or by you if you watched the session — before you move the code. + +- **The primary source is the _prototype itself_** — the runnable artifact the answer came from. It's the evidence behind the verdict, so you don't delete it; but with no tests and no maintenance story, it doesn't belong in the main repo either. Once the answer is captured, get the prototype out: commit it to a throwaway branch (e.g. `prototype/`), push it, and link that branch from the relevant issue. Never merge it. The main branch keeps only the validated decision folded into real code; the raw exploration stays on the branch, one click away for anyone who wants to re-run it. + +This is distinct from _absorbing_: lifting a validated reducer, state machine, or UI direction into the real module keeps the **decision**, not the prototype. The throwaway branch is what preserves the primary source. diff --git a/skills/engineering/prototype/UI.md b/skills/engineering/prototype/UI.md index f3b6e64..b99f2c6 100644 --- a/skills/engineering/prototype/UI.md +++ b/skills/engineering/prototype/UI.md @@ -97,12 +97,12 @@ Surface the URL (and the `?variant=` keys). The user will flip through whenever ### 6. Capture the answer and clean up -Once a variant has won, write down which one and why (commit message, ADR, issue, or a `NOTES.md` next to the prototype if running AFK and the user hasn't responded yet). Then: +Once a variant has won, capture the **answer** — the secondary source: which variant and why (commit message, ADR, issue, or a `NOTES.md` next to the prototype if running AFK and the user hasn't responded yet). Then fold the winner into the real code and get the prototype scaffolding out of the main branch: -- **Sub-shape A** — delete the losing variants and the switcher; fold the winner into the existing page. -- **Sub-shape B** — promote the winning variant to a real route, delete the throwaway route and the switcher. +- **Sub-shape A** — fold the winner into the existing page; drop the losing variants and the switcher from main. +- **Sub-shape B** — promote the winning variant to a real route; drop the throwaway route and the switcher from main. -Don't leave variant components or the switcher lying around. They rot fast and confuse the next reader. +Don't leave variant components or the switcher lying around in the main branch — they rot fast and confuse the next reader. But don't just destroy them either: the full set of variants is the primary source, so preserve the prototype on a throwaway branch linked from the relevant issue (see [SKILL](SKILL.md)) before you strip it out of main. ## Anti-patterns