From 129baf636807fed6441b1ab8ce3da91923273838 Mon Sep 17 00:00:00 2001 From: Ben Sykes Date: Thu, 9 Jul 2026 10:38:46 -0400 Subject: [PATCH 01/15] feat(lavish): added a safer version of Lavish AXI skill Signed-off-by: Ben Sykes --- .lavish/repo-architecture.html | 717 ++++++++++++++++++ README.md | 1 + .../agentsmd-generator/scripts/repo-inventory | 8 +- skills/lavish-safe/NOTICE | 22 + skills/lavish-safe/SKILL.md | 180 +++++ tests/test_skill_contract.py | 1 + 6 files changed, 928 insertions(+), 1 deletion(-) create mode 100644 .lavish/repo-architecture.html create mode 100644 skills/lavish-safe/NOTICE create mode 100644 skills/lavish-safe/SKILL.md diff --git a/.lavish/repo-architecture.html b/.lavish/repo-architecture.html new file mode 100644 index 0000000..3542444 --- /dev/null +++ b/.lavish/repo-architecture.html @@ -0,0 +1,717 @@ + + + + + + skills repo architecture + + + +
+
+

SystemFiles/skills · local architecture map

+

How this skills catalog is wired

+

+ This repo is a skills.sh source: authored and vendored + agent skills under skills/, plus Python tooling that keeps + upstream copies fresh and contract-tested. Agents install from here via + the skills CLI — they do not run this repo as an app. +

+
+ Evidence: README.md, CONTRIBUTING.md, Taskfile.yml, upstream-skills.toml + Design: hand-written local CSS (no project design system / no CDN) +
+
+ + + + +
+

System overview

+

+ What question does this answer? Where do skills come from, how do + they land in this repo, and how do agents get them? +

+ +
+ + Skills catalog system overview + + Upstream GitHub repos and local authors feed skills into this repo. + Sync tooling vendors catalog entries. The skills CLI installs into + agent skill directories. CI validates contracts and lint. + + + + + + + + + + + + + + + Upstream repos + e.g. vercel-labs/… + + + Authored skills + hand-written SKILL.md + + + Project installs + capture-project → catalog + + + + This repo · SystemFiles/skills + + + upstream-skills.toml + catalog of vendored skills + + + scripts/sync_upstream_skills.py + clone → copy → lockfile + + + skills/<name>/SKILL.md + installable skill packages (9) + + + tests/ + Taskfile + CI + + + + skills CLI + npx skills add + SystemFiles/skills + + + Agent skill dirs + ~/.agents/skills/ + Cursor / Claude / … + + + + + + + + + + + + + +
+
+ Authored + Vendored / upstream + Quality gates + Install path +
+
+ + +
+

Skill lifecycle

+

+ Two paths into skills/: write it here, or declare it in the + catalog and let sync vendor it. Both install the same way. +

+ +
+
+
01
+

Declare or author

+

+ Authored: add skills/<name>/SKILL.md. + Vendored: add [[skill]] to + upstream-skills.toml (or + task capture-project). +

+
+
+
02
+

Vendor (if upstream)

+

+ task sync-upstream-skills clones, copies into + skills/<name>/, writes + upstream-skills.lock.json. Nightly GH Action does the same. +

+
+
+
03
+

Validate

+

+ task validate / task ci: every + SKILL.md must satisfy the frontmatter contract; + pre-commit runs markdownlint, cspell, gitleaks, … +

+
+
+
04
+

Install

+

+ Consumers run + npx skills add SystemFiles/skills --skill <name> + (or --skill '*'). Discovery: frontmatter + name, not directory name alone. +

+
+
+ +
+ Vendored rule: do not hand-edit + skills/<name>/ for catalog skills — edit + upstream-skills.toml and re-sync. Currently vendored: + agent-browser (Apache-2.0, commit pinned in lockfile). +
+
+ + +
+

Skill inventory

+

+ Nine packages under skills/ as of this map: eight authored + here, one vendored. Rows are annotation targets — click a name if + something looks wrong. +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SkillOriginExtrasRole
agentsmd-generatorauthoredscripts/repo-inventoryGenerate project AGENTS.md onboarding guides.
jj-case-insensitive-clone-fixauthoredscripts/ diagnose, jj-cloneFix jj clone ref collisions on case-insensitive FS.
lavish-safeauthoredNOTICELocal-only Lavish HTML review; share/telemetry forbidden.
research_codebaseauthoredscripts/spec_metadata.shCitation-backed codebase maps under thoughts/.
sdd-linearauthoredagents/, references/, scripts/SDD workflow with Linear as system of record (explicit invoke).
sync-upstreamauthoredscripts/sync-contextBring a fork’s default branch up to date with upstream.
taskfile-automationauthoredScaffold Taskfile as single local/CI entry point.
work-breakdownauthoredDecompose large scope into parallelizable work units.
agent-browser + vendored + hidden + LICENSE (Apache-2.0)Browser automation CLI; from vercel-labs/agent-browser.
+
+
+ + +
+

Automation & CI

+

+ Task is the single entry point; uv provisions Python. CI runs the same + task ci gate. A separate workflow vendors upstream skills daily. +

+ +
+
+ Taskfile.yml +

Local / CI tasks

+
    +
  • validate / test — pytest skill contract
  • +
  • lint — pre-commit --all-files
  • +
  • ci — validate + lint (what GH Actions runs)
  • +
  • sync-upstream-skills — vendor from catalog
  • +
  • capture-project — append project skills to catalog
  • +
  • verify-discoverynpx skills add ./. --list
  • +
+
+
+ .github/workflows +

GitHub Actions

+
    +
  • ci.yml — push/PR: uv sync → task ci
  • +
  • sync-upstream-skills.yml — daily 06:17 UTC + manual; needs SYNC_UPSTREAM_PAT so vendor pushes trigger CI
  • +
  • pr-title-lint.yml — Conventional Commits on PR titles
  • +
+
+
+ scripts/ +

Repo tooling (not skills)

+
    +
  • sync_upstream_skills.py — clone, copy, lock, refuse copyleft
  • +
  • capture_project_skills.py — reconcile project lock → catalog
  • +
+
+
+ tests/ +

Contract & unit coverage

+
    +
  • test_skill_contract.py — every SKILL.md frontmatter
  • +
  • test_sync_upstream_skills.py, test_upstream_catalog.py
  • +
  • test_capture_project_skills.py
  • +
  • Per-skill script tests (jj-clone, sync-context, linear, inventory, …)
  • +
+
+
+ +
+ + CI and vendor sync flow + + + + + + + Push / PR + ci.yml + + + uv sync + Python 3.12+ + + + task ci + validate + lint + + + Parallel: vendor sync + sync-upstream-skills.yml + schedule → sync script → commit + PAT push retriggers CI + + + + +
+
+ + +
+

Repo layout (what matters)

+

+ Not every file — the structural spine. Paths cited from the working tree. +

+ +
+
+skills/                          # installable packages (SKILL.md each)
+scripts/                         # catalog sync + capture (repo tooling)
+tests/                           # contract + script unit tests
+Taskfile.yml                     # single automation entry
+upstream-skills.toml             # vendored skill catalog
+upstream-skills.lock.json        # pinned commits / licenses
+.github/workflows/               # ci + daily vendor sync + PR title lint
+pyproject.toml / uv.lock         # pytest, pyyaml, pre-commit via uv
+
+
+ +
+ Not an application runtime. There is no server, Docker + layer, or package to install from this repo itself + (package = false in pyproject). The product is the skill + directories plus the sync/validate tooling around them. +
+
+ +
+ Artifact: .lavish/repo-architecture.html · local-only Lavish + session · do not use browser “Publish link” · design source: hand-written + CSS (repo has no UI design system) +
+
+ + diff --git a/README.md b/README.md index 377dc7d..88d277f 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Each skill is a directory under `skills//` containing a `SKILL.md` (plus a | --- | --- | | `agentsmd-generator` | Generate project-level `AGENTS.md` onboarding guides covering structure, tooling, testing, task flow, and conventions. | | `jj-case-insensitive-clone-fix` | Diagnose and fix the `jj git clone` "Failed to update refs" error on case-insensitive filesystems (e.g. macOS APFS). | +| `lavish-safe` | Local-only Lavish HTML review via `lavish-axi`, with share and telemetry forbidden. | | `research_codebase` | Map how a codebase works today and save a dated, citation-backed report under `thoughts/`, using parallel sub-agents by default. | | `sdd-linear` | Run the Spec-Driven Development (SDD) workflow with Linear issues, sub-issues, attachments, and comments as the system of record instead of `docs/specs`. | | `sync-upstream` | Sync a fork's default branch with its upstream remote using merge or rebase, resolving conflicts as needed. | diff --git a/skills/agentsmd-generator/scripts/repo-inventory b/skills/agentsmd-generator/scripts/repo-inventory index 9670b48..ca99de7 100755 --- a/skills/agentsmd-generator/scripts/repo-inventory +++ b/skills/agentsmd-generator/scripts/repo-inventory @@ -158,7 +158,13 @@ if command -v tree >/dev/null 2>&1; then tree -a -I '.git|.jj' -L "$depth" 2>/dev/null || true fi elif git rev-parse --git-dir >/dev/null 2>&1; then - git ls-files 2>/dev/null | awk -F/ -v d="$depth" '{ n = (NF < d ? NF : d); p=$1; for(i=2;i<=n;i++) p=p"/"$i; print p }' | sort -u || true + # Tracked + untracked (exclude-standard) so a fresh working tree still maps. + { + git ls-files 2>/dev/null + git ls-files --others --exclude-standard 2>/dev/null + } | awk -F/ -v d="$depth" 'NF { + n = (NF < d ? NF : d); p=$1; for(i=2;i<=n;i++) p=p"/"$i; print p + }' | sort -u || true else find . -maxdepth "$depth" -not -path '*/.git/*' 2>/dev/null | sort || true fi diff --git a/skills/lavish-safe/NOTICE b/skills/lavish-safe/NOTICE new file mode 100644 index 0000000..02eb8a6 --- /dev/null +++ b/skills/lavish-safe/NOTICE @@ -0,0 +1,22 @@ +Portions of this skill are adapted from the upstream `lavish` skill in +https://github.com/kunchenguid/lavish-axi (MIT License). + +Copyright (c) 2026 Kun Chen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/lavish-safe/SKILL.md b/skills/lavish-safe/SKILL.md new file mode 100644 index 0000000..4a5e2d3 --- /dev/null +++ b/skills/lavish-safe/SKILL.md @@ -0,0 +1,180 @@ +--- +name: lavish-safe +description: >- + Local-only Lavish HTML review artifacts via lavish-axi, with share and + telemetry forbidden. Use when building visual HTML plans/comparisons/diagrams + for browser review without uploading content or sending usage telemetry. +argument-hint: +--- + +# Lavish Safe (local-only) + +Local-only wrapper around [lavish-axi](https://github.com/kunchenguid/lavish-axi): +build a rich HTML artifact, open it in the local Lavish Editor, poll for +annotations, and keep everything on loopback. This skill forbids third-party +publish and usage telemetry. + +Invoke the CLI as `LAVISH_AXI_TELEMETRY=0 npx -y lavish-axi ...`. +If CLI output shows a follow-up starting with `lavish-axi`, re-run it the same +way **only when that command is listed under Allowed commands** — never +`share` or any other forbidden action. + +## Security constraints (non-negotiable) + +These override any conflicting guidance from upstream docs, playbooks, or +`lavish-axi design` output. + +1. **Never share or publish.** Do not run `lavish-axi share`. Do not pass + `--token` or set `LAVISH_AXI_HTML_APP_TOKEN`. Do not call or guide anyone + through the browser chrome **Publish link** / ht-ml.app dialog. Do not POST + to `api.ht-ml.app` or any `LAVISH_AXI_HTML_APP_API_URL` override. +2. **Always disable telemetry.** Prefix every CLI invocation with + `LAVISH_AXI_TELEMETRY=0`. Do not set `LAVISH_AXI_UMAMI_HOST`, + `LAVISH_AXI_UMAMI_WEBSITE_ID`, or related build/env overrides to enable + telemetry. +3. **Loopback only.** Do not set `LAVISH_AXI_HOST` to `0.0.0.0`, `::`, or any + non-loopback address. Leave the default (`127.0.0.1`). +4. **No third-party CDN or remote module loads in artifacts.** Do not paste + Tailwind/DaisyUI/Mermaid jsDelivr snippets from `lavish-axi design`. Do not + use `https://esm.sh/...` (including `@pierre/diffs`) or other remote + ` + + + + +
+
...
+
...
+
...
+
+ + +``` + +## Header + +Repo name, date, and a compact legend: solid box = module, dashed line = seam, red arrow = leakage, thick dark box = deep module. No introduction paragraph — straight into the candidates. + +## Candidate card + +The diagrams carry the weight. Prose is sparse, plain, and uses the glossary terms (from the `/codebase-design` skill) without ceremony. + +Each candidate is one `
`: + +- **Title** — short, names the deepening (e.g. "Collapse the Order intake pipeline"). +- **Badge row** — recommendation strength (`Strong` = emerald, `Worth exploring` = amber, `Speculative` = slate), plus a tag for the dependency category (`in-process`, `local-substitutable`, `ports & adapters`, `mock`). +- **Files** — monospaced list, `font-mono text-sm`. +- **Before / After diagram** — the centrepiece. Two columns, side by side. See patterns below. +- **Problem** — one sentence. What hurts. +- **Solution** — one sentence. What changes. +- **Wins** — bullets, ≤6 words each. e.g. "Tests hit one interface", "Pricing logic stops leaking", "Delete 4 shallow wrappers". +- **ADR callout** (if applicable) — one line in an amber-tinted box. + +No paragraphs of explanation. If the diagram needs a paragraph to be understood, redraw the diagram. + +## Diagram patterns + +Pick the pattern that fits the candidate. Mix them. Don't make every diagram look the same — variety is part of the point. + +### Mermaid graph (the workhorse for dependencies / call flow) + +Use a Mermaid `flowchart` or `graph` when the point is "X calls Y calls Z, and look at the mess." Wrap it in a Tailwind-styled card so it doesn't feel parachuted in. Style with classDef to colour leakage edges red and the deep module dark. Sequence diagrams work well for "before: 6 round-trips; after: 1." + +```html +
+
+    flowchart LR
+      A[OrderHandler] --> B[OrderValidator]
+      B --> C[OrderRepo]
+      C -.leak.-> D[PricingClient]
+      classDef leak stroke:#dc2626,stroke-width:2px;
+      class C,D leak
+  
+
+``` + +### Hand-built boxes-and-arrows (when Mermaid's layout fights you) + +Modules as `
`s with borders and labels. Arrows as inline SVG `` or `` elements positioned absolutely over a relative container. Reach for this when you want the "after" diagram to feel like one thick-bordered deep module with greyed-out internals — Mermaid won't render that with the right weight. + +### Cross-section (good for layered shallowness) + +Stack horizontal bands (`h-12 border-l-4`) to show layers a call passes through. Before: 6 thin layers each doing nothing. After: 1 thick band labelled with the consolidated responsibility. + +### Mass diagram (good for "interface as wide as implementation") + +Two rectangles per module — one for interface surface area, one for implementation. Before: interface rectangle is nearly as tall as the implementation rectangle (shallow). After: interface rectangle is short, implementation rectangle is tall (deep). + +### Call-graph collapse + +Before: a tree of function calls rendered as nested boxes. After: the same tree collapsed into one box, with the now-internal calls shown faded inside it. + +## Style guidance + +- Lean editorial, not corporate-dashboard. Generous whitespace. Serif optional for headings (`font-serif` works well with stone/slate). +- Colour sparingly: one accent (emerald or indigo) plus red for leakage and amber for warnings. +- Keep diagrams ~320px tall so before/after sits comfortably side by side without scrolling. +- Use `text-xs uppercase tracking-wider` for module labels inside diagrams — they should read as schematic, not as UI. +- The only scripts are the Tailwind CDN and the Mermaid ESM import. The report is otherwise static — no app code, no interactivity beyond Mermaid's own rendering. + +## Top recommendation section + +One larger card. Candidate name, one sentence on why, anchor link to its card. That's it. + +## Tone + +Plain English, concise — but the architectural nouns and verbs come straight from the `/codebase-design` skill. Concision is not an excuse to drift. + +**Use exactly:** module, interface, implementation, depth, deep, shallow, seam, adapter, leverage, locality. + +**Never substitute:** component, service, unit (for module) · API, signature (for interface) · boundary (for seam) · layer, wrapper (for module, when you mean module). + +**Phrasings that fit the style:** + +- "Order intake module is shallow — interface nearly matches the implementation." +- "Pricing leaks across the seam." +- "Deepen: one interface, one place to test." +- "Two adapters justify the seam: HTTP in prod, in-memory in tests." + +**Wins bullets** name the gain in glossary terms: *"locality: bugs concentrate in one module"*, *"leverage: one interface, N call sites"*, *"interface shrinks; implementation absorbs the wrappers"*. Don't write *"easier to maintain"* or *"cleaner code"* — those terms aren't in the glossary and don't earn their place. + +No hedging, no throat-clearing, no "it's worth noting that…". If a sentence could be a bullet, make it a bullet. If a bullet could be cut, cut it. If a term isn't in the `/codebase-design` glossary, reach for one that is before inventing a new one. diff --git a/skills/improve-codebase-architecture/LICENSE b/skills/improve-codebase-architecture/LICENSE new file mode 100644 index 0000000..f1dd2c0 --- /dev/null +++ b/skills/improve-codebase-architecture/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Matt Pocock + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/improve-codebase-architecture/SKILL.md b/skills/improve-codebase-architecture/SKILL.md new file mode 100644 index 0000000..b56969e --- /dev/null +++ b/skills/improve-codebase-architecture/SKILL.md @@ -0,0 +1,71 @@ +--- +name: improve-codebase-architecture +description: Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick. +disable-model-invocation: true +--- + +# Improve Codebase Architecture + +Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability. + +This command is _informed_ by the project's domain model and built on a shared design vocabulary: + +- Run the `/codebase-design` skill for the architecture vocabulary (**module**, **interface**, **depth**, **seam**, **adapter**, **leverage**, **locality**) and its principles (the deletion test, "the interface is the test surface", "one adapter = hypothetical seam, two = real"). Use these terms exactly in every suggestion — don't drift into "component," "service," "API," or "boundary." +- The domain language in `CONTEXT.md` gives names to good seams; ADRs in `docs/adr/` record decisions this command should not re-litigate. + +## Process + +### 1. Explore + +**Scope before you scan — YAGNI.** Deepening a module pays off by making future changes to it easier, so put extra weight on the parts of the codebase that have recently changed. Decide *where* to look before you look: + +- If the user named a direction — a module, a subsystem, a pain point — take it, and skip the inference below. +- Otherwise, walk back a good stretch of the commit history (`git log --oneline`) to find the codebase's hot spots — the files and areas that keep coming up — and let those paths pull your attention first. If the changes are scattered with no clear hot spot, widen the net. + +Read the project's domain glossary (`CONTEXT.md`) and any ADRs in the area you're touching first. + +Then use the Agent tool with `subagent_type=Explore` to walk the codebase. Don't follow rigid heuristics — explore organically and note where you experience friction: + +- Where does understanding one concept require bouncing between many small modules? +- Where are modules **shallow** — interface nearly as complex as the implementation? +- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)? +- Where do tightly-coupled modules leak across their seams? +- Which parts of the codebase are untested, or hard to test through their current interface? + +Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want. + +### 2. Present candidates as an HTML report + +Write a self-contained HTML file to the OS temp directory so nothing lands in the repo. Resolve the temp dir from `$TMPDIR`, falling back to `/tmp` (or `%TEMP%` on Windows), and write to `/architecture-review-.html` so each run gets a fresh file. Open it for the user — `xdg-open ` on Linux, `open ` on macOS, `start ` on Windows — and tell them the absolute path. + +The report uses **Tailwind via CDN** for layout and styling, and **Mermaid via CDN** for diagrams where a graph/flow/sequence reliably communicates the structure. Mix Mermaid with hand-crafted CSS/SVG visuals — use Mermaid when relationships are graph-shaped (call graphs, dependencies, sequences), and hand-built divs/SVG when you want something more editorial (mass diagrams, cross-sections, collapse animations). Each candidate gets a **before/after visualisation**. Be visual. + +For each candidate, render a card with: + +- **Files** — which files/modules are involved +- **Problem** — why the current architecture is causing friction +- **Solution** — plain English description of what would change +- **Benefits** — explained in terms of locality and leverage, and how tests would improve +- **Before / After diagram** — side-by-side, custom-drawn, illustrating the shallowness and the deepening +- **Recommendation strength** — one of `Strong`, `Worth exploring`, `Speculative`, rendered as a badge + +End the report with a **Top recommendation** section: which candidate you'd tackle first and why. + +**Use CONTEXT.md vocabulary for the domain, and the `/codebase-design` vocabulary for the architecture.** If `CONTEXT.md` defines "Order," talk about "the Order intake module" — not "the FooBarHandler," and not "the Order service." + +**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly in the card (e.g. a warning callout: _"contradicts ADR-0007 — but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids. + +See [HTML-REPORT.md](HTML-REPORT.md) for the full HTML scaffold, diagram patterns, and styling guidance. + +Do NOT propose interfaces yet. After the file is written, ask the user: "Which of these would you like to explore?" + +### 3. Grilling loop + +Once the user picks a candidate, run the `/grilling` skill to walk the decision tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive. + +Side effects happen inline as decisions crystallize — run the `/domain-modeling` skill to keep the domain model current as you go: + +- **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the term to `CONTEXT.md`. Create the file lazily if it doesn't exist. +- **Sharpening a fuzzy term during the conversation?** Update `CONTEXT.md` right there. +- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. +- **Want to explore alternative interfaces for the deepened module?** Run the `/codebase-design` skill and use its design-it-twice parallel sub-agent pattern. diff --git a/skills/improve-codebase-architecture/agents/openai.yaml b/skills/improve-codebase-architecture/agents/openai.yaml new file mode 100644 index 0000000..706fdca --- /dev/null +++ b/skills/improve-codebase-architecture/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Improve Codebase Architecture" + short_description: "Find and grill architecture improvements" +policy: + allow_implicit_invocation: false diff --git a/skills/teach/GLOSSARY-FORMAT.md b/skills/teach/GLOSSARY-FORMAT.md new file mode 100644 index 0000000..9cae84c --- /dev/null +++ b/skills/teach/GLOSSARY-FORMAT.md @@ -0,0 +1,35 @@ +# GLOSSARY.md Format + +`GLOSSARY.md` is the canonical language for this teaching workspace. All explainers, exercises, and learning records should adhere to its terminology. Building it is itself part of learning: compressing a concept into a tight definition is evidence the user understands it. + +## Structure + +```md +# {Topic} Glossary + +{One or two sentence description of the topic this glossary covers.} + +## Terms + +**Hypertrophy**: +Muscle growth driven by mechanical tension and metabolic stress over repeated training sessions. +_Avoid_: Bulking, getting big + +**Progressive overload**: +Systematically increasing the demand on a muscle over time — via load, volume, or intensity. +_Avoid_: Pushing harder, levelling up + +**RPE (Rate of Perceived Exertion)**: +A 1–10 self-rating of how hard a set felt, where 10 is failure and 8 means two reps left in the tank. +_Avoid_: Effort score, intensity rating +``` + +## Rules + +- **Add a term only when the user understands it.** The glossary is a record of compressed knowledge, not a dictionary the user reads to learn. If the user has just been introduced to a concept, wait until they can use it correctly before promoting it here. +- **Be opinionated.** When several words exist for the same concept, pick the best one and list the rest as aliases to avoid. This is how language compresses. +- **Keep definitions tight.** One or two sentences. Define what the term IS, not what it does or how to do it. +- **Use the glossary's own terms inside definitions.** Once a term is in the glossary, prefer it everywhere — including inside other definitions. This is what makes complex terms easier to grasp later. +- **Group under subheadings** when natural clusters emerge (e.g. `## Anatomy`, `## Programming`). A flat list is fine when terms cohere. +- **Flag ambiguities explicitly.** If a term is used loosely in the wider field, note the resolution: "In this workspace, 'set' always means a working set — warm-ups are tracked separately." +- **Revise as understanding deepens.** A definition the user wrote in week one may be wrong by week six. Update in place; do not leave stale entries. diff --git a/skills/teach/LEARNING-RECORD-FORMAT.md b/skills/teach/LEARNING-RECORD-FORMAT.md new file mode 100644 index 0000000..2faa7c9 --- /dev/null +++ b/skills/teach/LEARNING-RECORD-FORMAT.md @@ -0,0 +1,46 @@ +# Learning Record Format + +Learning records live in `./learning-records/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc. Create the directory lazily — only when the first record is written. + +They are the teaching equivalent of ADRs: they capture non-obvious lessons, key insights, and stated prior knowledge that will steer future sessions. They are used to calculate the zone of proximal development. + +## Template + +```md +# {Short title of what was learned or established} + +{1-3 sentences: what was learned (or what prior knowledge was established), and why it matters for future sessions.} +``` + +That is the whole format. A learning record can be a single paragraph. The value is recording _that_ this is now known and _why_ it changes what to teach next — not in filling out sections. + +## Optional sections + +Only include these when they add genuine value. Most records won't need them. + +- **Status** frontmatter (`active | superseded by LR-NNNN`) — useful when an earlier understanding turns out to be wrong and is replaced. +- **Evidence** — how the user demonstrated the understanding (a question answered, an exercise completed, prior experience cited). Useful when the claim might be revisited. +- **Implications** — what this unlocks or rules out for future sessions. Worth recording when non-obvious. + +## Numbering + +Scan `./learning-records/` for the highest existing number and increment by one. + +## When to write a learning record + +Write one when any of these is true: + +1. **The user demonstrated genuine understanding of something non-trivial** — not just exposure, but evidence they can use the concept correctly. This sets a new floor for what to teach next. +2. **The user disclosed prior knowledge** — "I already know X." Record it so future sessions don't re-teach it. Also record the _depth_ claimed. +3. **A misconception was corrected** — the user previously believed something wrong and now sees why. These are high-value: they predict future stumbling blocks for related topics. +4. **The mission shifted in response to learning** — the user discovered they cared about something different than they thought. Cross-link to [[MISSION.md]] and update it. + +### What does _not_ qualify + +- Material that was merely covered. Coverage is not learning. Wait for evidence. +- Anything already captured tersely in [[GLOSSARY.md]] as a term definition. Don't duplicate. +- Session-by-session activity logs. Learning records are not a journal — they are decision-grade insights. + +## Supersession + +When a later record contradicts an earlier one (the user's understanding deepened or corrected), mark the old record `Status: superseded by LR-NNNN` rather than deleting it. The history of how understanding evolved is itself useful signal. diff --git a/skills/teach/LICENSE b/skills/teach/LICENSE new file mode 100644 index 0000000..f1dd2c0 --- /dev/null +++ b/skills/teach/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Matt Pocock + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/teach/MISSION-FORMAT.md b/skills/teach/MISSION-FORMAT.md new file mode 100644 index 0000000..5dac184 --- /dev/null +++ b/skills/teach/MISSION-FORMAT.md @@ -0,0 +1,31 @@ +# MISSION.md Format + +`MISSION.md` lives at the workspace root. It captures the _reason_ the user is learning this topic. Every teaching decision — what to teach next, which resources to surface, which exercises to design — should trace back to this document. + +## Template + +```md +# Mission: {Topic} + +## Why +{1-3 sentences. The concrete real-world goal the user is chasing. What changes in their life or work when they have this skill? Avoid abstract framings like "to understand X" — push for the underlying outcome.} + +## Success looks like +- {A specific, observable thing the user will be able to do} +- {Another specific thing} +- {…} + +## Constraints +- {Time, budget, prior commitments, learning preferences, anything that bounds the approach} + +## Out of scope +- {Adjacent topics the user explicitly does not want to chase right now — protects the zone of proximal development} +``` + +## Rules + +- **One mission per workspace.** If the user wants to learn two unrelated things, that is two workspaces. +- **Concrete over abstract.** "Run a half marathon by October" beats "get fitter." "Ship a Rust CLI to my team" beats "learn Rust." +- **Push back on vagueness.** If the user cannot articulate why, interview them before writing anything. A bad mission is worse than no mission. +- **Revise when reality shifts.** Missions change. When the user's goal moves, update this file — don't leave a stale mission steering future sessions. +- **Keep it short.** If `MISSION.md` runs past a screen, it has stopped being a compass and started being a plan. diff --git a/skills/teach/RESOURCES-FORMAT.md b/skills/teach/RESOURCES-FORMAT.md new file mode 100644 index 0000000..c94aac6 --- /dev/null +++ b/skills/teach/RESOURCES-FORMAT.md @@ -0,0 +1,32 @@ +# RESOURCES.md Format + +`RESOURCES.md` is the curated set of trusted sources for this topic. Knowledge for explainers should be drawn from here, not from parametric guesses. Wisdom comes from the communities listed here. + +## Structure + +```md +# {Topic} Resources + +## Knowledge + +- [Book: _The Science and Practice of Strength Training_ — Zatsiorsky & Kraemer](https://example.com) + Foundational text on programming and adaptation. Use for: anything to do with periodisation, recovery, intensity zones. +- [Article: "How Much Should I Train?" — Greg Nuckols (Stronger By Science)](https://example.com) + Evidence-based review of volume landmarks. Use for: weekly set targets per muscle group. + +## Wisdom (Communities) + +- [r/weightroom](https://reddit.com/r/weightroom) + High-signal subreddit, moderated against bro-science. Use for: programme critique, plateau troubleshooting. +- Local: Tuesday strength class at {gym name} + Use for: real-time coaching feedback on lifts. +``` + +## Rules + +- **High-trust only.** Prefer primary sources, recognised experts, peer-reviewed work, and communities with strong moderation. If a resource is marketing dressed as education, leave it out. +- **Annotate every entry.** A bare link is useless in three months. Add one line: what it covers and when to reach for it. +- **Group by Knowledge / Wisdom.** Mirrors the philosophy in [SKILL.md](./SKILL.md). It is fine for a resource to appear in only one group. +- **Surface gaps explicitly.** If no good resource exists for an area the mission needs, write a `## Gaps` section listing what is missing. This drives future search. +- **Prune ruthlessly.** A resource that turned out to be wrong, shallow, or off-mission should be removed, not buried. Better five sharp sources than thirty mediocre ones. +- **Record community preferences.** If the user has opted out of joining communities, note it here so future sessions don't keep proposing them. diff --git a/skills/teach/SKILL.md b/skills/teach/SKILL.md new file mode 100644 index 0000000..b1603e5 --- /dev/null +++ b/skills/teach/SKILL.md @@ -0,0 +1,140 @@ +--- +name: teach +description: Teach the user a new skill or concept, within this workspace. +disable-model-invocation: true +argument-hint: "What would you like to learn about?" +--- + +The user has asked you to teach them something. This is a stateful request - they intend to learn the topic over multiple sessions. + +## Teaching Workspace + +Treat the current directory as a teaching workspace. The state of their learning is captured in this directory in several files: + +- `MISSION.md`: A document capturing the _reason_ the user is interested in the topic. This should be used to ground all teaching. Use the format in [MISSION-FORMAT.md](./MISSION-FORMAT.md). +- `./reference/*.html`: A directory of reference materials. These are the compressed learnings from the lessons - cheat sheets, reference algorithms, syntax, yoga poses, glossaries. They are the raw units of learning. They should be beautiful documents which print out well, and are designed for quick reference. +- `RESOURCES.md`: A list of resources which can be explored to ground your teaching in contextual knowledge, or to acquire knowledge and wisdom. Use the format in [RESOURCES-FORMAT.md](./RESOURCES-FORMAT.md). +- `./learning-records/*.md`: A directory of learning records, which capture what the user has learned. These are loosely equivalent to architectural decision records in software development - they capture non-obvious lessons and key insights that may need to be revised later, or drive future sessions. These should be used to calculate the zone of proximal development. They are titled `0001-.md`, where the number increments each time. Use the format in [LEARNING-RECORD-FORMAT.md](./LEARNING-RECORD-FORMAT.md). +- `./lessons/*.html`: A directory of lessons. A **lesson** is a single, self-contained HTML output that teaches one tightly-scoped thing tied to the mission. This is the primary unit of teaching in this workspace. +- `./assets/*`: Reusable **components** shared across lessons. See [Assets](#assets). +- `NOTES.md`: A scratchpad for you to jot down user preferences, or working notes. + +## Philosophy + +To learn at a deep level, the user needs three things: + +- **Knowledge**, captured from high-quality, high-trust resources +- **Skills**, acquired through highly-relevant interactive lessons devised by you, based on the knowledge +- **Wisdom**, which comes from interacting with other learners and practitioners + +Before the `RESOURCES.md` is well-populated, your focus should be to find high-quality resources which will help the user acquire knowledge. Never trust your parametric knowledge. + +Some topics may require more skills than knowledge. Learning more about theoretical physics might be more knowledge-based. For yoga, more skills-based. + +### Fluency vs Storage Strength + +You should be careful to split between two types of learning: + +- **Fluency strength**: in-the-moment retrieval of knowledge +- **Storage strength**: long-term retention of knowledge + +Fluency can give the user an illusory sense of mastery, but storage strength is the real goal. Try to design lessons which build long-term retention by desirable difficulty: + +- Using retrieval practice (recall from memory) +- Spacing (distributing practice over time) +- Interleaving (mixing up different but related topics in practice - for skills practice only) + +## Lessons + +A lesson is the main thing you produce — the unit in which knowledge and skills reach the user. Each lesson is one self-contained HTML file, saved to `./lessons/` and titled `0001-.html` where the number increments each time. + +A lesson should be **beautiful** — clean, readable typography and layout — since the user will return to these later to review. Think Tufte. + +The lesson should be short, and completable very quickly. Learners' working memory is very small, and we need to stay within it. But each lesson should give the user a single tangible win that they can build on. It should be directly tied to the mission, and should be in the user's zone of proximal development. + +If possible, open the lesson file for the user by running a CLI command. + +Each lesson should link via HTML anchors to other lessons and reference documents. + +Each lesson should recommend a primary source for the user to read or watch. This should be the most high-quality, high-trust resource you found on the topic. + +Each lesson should contain a reminder to ask followup questions to the agent. The agent is their teacher, and can assist with anything that's unclear. + +## Assets + +Lessons are built from reusable **components**, stored in `./assets/`: stylesheets, quiz widgets, simulators, diagram helpers — anything a second lesson could reuse. + +Reuse is the default, not the exception. Before authoring a lesson, read `./assets/` and build from the components already there. When a lesson needs something new and reusable, write it as a component in `./assets/` and link to it — never inline code a future lesson would duplicate. + +A shared stylesheet is the first component every workspace earns: every lesson links it, so the lessons look like one consistent course rather than a pile of one-offs. As the workspace grows, so should the component library. + +## The Mission + +Every lesson should be tied into the mission - the reason that the user is interested in learning about the topic. + +If the user is unclear about the mission, or the `MISSION.md` is not populated, your first job should be to question the user on why they want to learn this. + +Failing to understand the mission will mean knowledge acquisition is not grounded in real-world goals. Lessons will feel too abstract. You will have no way of judging what the user should do next. + +Missions may change as the user develops more skills and knowledge. This is normal - make sure to update the `MISSION.md` and add a learning record to capture the change. Confirm with the user before changing the mission. + +## Zone Of Proximal Development + +Each lesson, the user should always feel as if they are being challenged 'just enough'. + +The user may specify an exact thing they want to learn. If they don't, figure out their zone of proximal development by: + +- Reading their `learning-records` +- Figuring out the right thing to teach them based on their mission +- Teach the most relevant thing that fits in their zone of proximal development + +## Knowledge + +Lessons should be designed around a skill the user is going to learn. The knowledge in the lesson should be only what's required to acquire that skill. You teach the knowledge first, then get the user to practice the skills via an interactive feedback loop. + +Knowledge should first be gathered from trusted resources. Use `RESOURCES.md` to keep track of them. Lessons should be littered with citations - links to external resources to back up any claim made. This increases the trustworthiness of the lesson. + +For acquiring knowledge, difficulty is the enemy. It eats working memory you need for understanding. + +## Skills + +If knowledge is all about acquisition, skills are about durability and flexibility. Make the knowledge stick. + +For skill acquisition, difficulty is the tool. Effortful retrieval is what builds storage strength. Skills should be taught through interactive lessons. There are several tools at your disposal: + +- Interactive lessons, using quizzes and light in-browser tasks +- Lessons which guide the user through a list of real-world steps to take (for instance, yoga poses) + +Each of these should be based on a **feedback loop**, where the user receives feedback on their performance. This feedback loop should be as tight as possible, giving feedback immediately - and ideally automatically. + +For quizzes, each answer should be exactly the same number of words (and characters, if possible). Don't give the user any clues about the answer through formatting. + +## Acquiring Wisdom + +Wisdom comes from true real-world interaction - testing your skills outside the learning environment. + +When the user asks a question that appears to require wisdom, your default posture should be to attempt to answer - but to ultimately delegate to a **community**. + +A community is a place (online or offline) where the user can test their skills in the real world. This might be a forum, a subreddit, a real-world class (budget permitting) or a local interest group. + +You should attempt to find high-reputation communities the user can join. If the user expresses a preference that they don't want to join a community, respect it. + +## Reference Documents + +While creating lessons, you should also create reference documents. Lessons can reference these documents - they are useful for tracking raw units of knowledge useful across lessons. + +Lessons will rarely be revisited later - reference documents will be. They should be the compressed essence of the lesson, in a format designed for quick reference. + +Some learning topics lend themselves to reference: + +- Syntax and code snippets for programming +- Algorithms and flowcharts for processes +- Yoga poses and sequences for yoga +- Exercises and routines for fitness +- Glossaries for any topic with its own nomenclature + +Glossaries, in particular, are an essential reference. Once one is created, it should be adhered to in every lesson. + +## `NOTES.md` + +The user will sometimes express preferences of how they want to be taught, or things you should keep in mind. This is the place to record those preferences, so you can refer back to them when designing lessons or working with the user. diff --git a/skills/teach/agents/openai.yaml b/skills/teach/agents/openai.yaml new file mode 100644 index 0000000..3452a85 --- /dev/null +++ b/skills/teach/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Teach" + short_description: "Learn a concept in a guided workspace" +policy: + allow_implicit_invocation: false diff --git a/skills/test-driven-development/LICENSE b/skills/test-driven-development/LICENSE new file mode 100644 index 0000000..abf0390 --- /dev/null +++ b/skills/test-driven-development/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Jesse Vincent + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/test-driven-development/SKILL.md b/skills/test-driven-development/SKILL.md new file mode 100644 index 0000000..60d2609 --- /dev/null +++ b/skills/test-driven-development/SKILL.md @@ -0,0 +1,371 @@ +--- +name: test-driven-development +description: Use when implementing any feature or bugfix, before writing implementation code +--- + +# Test-Driven Development (TDD) + +## Overview + +Write the test first. Watch it fail. Write minimal code to pass. + +**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing. + +**Violating the letter of the rules is violating the spirit of the rules.** + +## When to Use + +**Always:** +- New features +- Bug fixes +- Refactoring +- Behavior changes + +**Exceptions (ask your human partner):** +- Throwaway prototypes +- Generated code +- Configuration files + +Thinking "skip TDD just this once"? Stop. That's rationalization. + +## The Iron Law + +``` +NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST +``` + +Write code before the test? Delete it. Start over. + +**No exceptions:** +- Don't keep it as "reference" +- Don't "adapt" it while writing tests +- Don't look at it +- Delete means delete + +Implement fresh from tests. Period. + +## Red-Green-Refactor + +```dot +digraph tdd_cycle { + rankdir=LR; + red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"]; + verify_red [label="Verify fails\ncorrectly", shape=diamond]; + green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"]; + verify_green [label="Verify passes\nAll green", shape=diamond]; + refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"]; + next [label="Next", shape=ellipse]; + + red -> verify_red; + verify_red -> green [label="yes"]; + verify_red -> red [label="wrong\nfailure"]; + green -> verify_green; + verify_green -> refactor [label="yes"]; + verify_green -> green [label="no"]; + refactor -> verify_green [label="stay\ngreen"]; + verify_green -> next; + next -> red; +} +``` + +### RED - Write Failing Test + +Write one minimal test showing what should happen. + + +```typescript +test('retries failed operations 3 times', async () => { + let attempts = 0; + const operation = () => { + attempts++; + if (attempts < 3) throw new Error('fail'); + return 'success'; + }; + + const result = await retryOperation(operation); + + expect(result).toBe('success'); + expect(attempts).toBe(3); +}); +``` +Clear name, tests real behavior, one thing + + + +```typescript +test('retry works', async () => { + const mock = jest.fn() + .mockRejectedValueOnce(new Error()) + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce('success'); + await retryOperation(mock); + expect(mock).toHaveBeenCalledTimes(3); +}); +``` +Vague name, tests mock not code + + +**Requirements:** +- One behavior +- Clear name +- Real code (no mocks unless unavoidable) + +### Verify RED - Watch It Fail + +**MANDATORY. Never skip.** + +```bash +npm test path/to/test.test.ts +``` + +Confirm: +- Test fails (not errors) +- Failure message is expected +- Fails because feature missing (not typos) + +**Test passes?** You're testing existing behavior. Fix test. + +**Test errors?** Fix error, re-run until it fails correctly. + +### GREEN - Minimal Code + +Write simplest code to pass the test. + + +```typescript +async function retryOperation(fn: () => Promise): Promise { + for (let i = 0; i < 3; i++) { + try { + return await fn(); + } catch (e) { + if (i === 2) throw e; + } + } + throw new Error('unreachable'); +} +``` +Just enough to pass + + + +```typescript +async function retryOperation( + fn: () => Promise, + options?: { + maxRetries?: number; + backoff?: 'linear' | 'exponential'; + onRetry?: (attempt: number) => void; + } +): Promise { + // YAGNI +} +``` +Over-engineered + + +Don't add features, refactor other code, or "improve" beyond the test. + +### Verify GREEN - Watch It Pass + +**MANDATORY.** + +```bash +npm test path/to/test.test.ts +``` + +Confirm: +- Test passes +- Other tests still pass +- Output pristine (no errors, warnings) + +**Test fails?** Fix code, not test. + +**Other tests fail?** Fix now. + +### REFACTOR - Clean Up + +After green only: +- Remove duplication +- Improve names +- Extract helpers + +Keep tests green. Don't add behavior. + +### Repeat + +Next failing test for next feature. + +## Good Tests + +| Quality | Good | Bad | +|---------|------|-----| +| **Minimal** | One thing. "and" in name? Split it. | `test('validates email and domain and whitespace')` | +| **Clear** | Name describes behavior | `test('test1')` | +| **Shows intent** | Demonstrates desired API | Obscures what code should do | + +## Why Order Matters + +**"I'll write tests after to verify it works"** + +Tests written after code pass immediately. Passing immediately proves nothing: +- Might test wrong thing +- Might test implementation, not behavior +- Might miss edge cases you forgot +- You never saw it catch the bug + +Test-first forces you to see the test fail, proving it actually tests something. + +**"I already manually tested all the edge cases"** + +Manual testing is ad-hoc. You think you tested everything but: +- No record of what you tested +- Can't re-run when code changes +- Easy to forget cases under pressure +- "It worked when I tried it" ≠ comprehensive + +Automated tests are systematic. They run the same way every time. + +**"Deleting X hours of work is wasteful"** + +Sunk cost fallacy. The time is already gone. Your choice now: +- Delete and rewrite with TDD (X more hours, high confidence) +- Keep it and add tests after (30 min, low confidence, likely bugs) + +The "waste" is keeping code you can't trust. Working code without real tests is technical debt. + +**"TDD is dogmatic, being pragmatic means adapting"** + +TDD IS pragmatic: +- Finds bugs before commit (faster than debugging after) +- Prevents regressions (tests catch breaks immediately) +- Documents behavior (tests show how to use code) +- Enables refactoring (change freely, tests catch breaks) + +"Pragmatic" shortcuts = debugging in production = slower. + +**"Tests after achieve the same goals - it's spirit not ritual"** + +No. Tests-after answer "What does this do?" Tests-first answer "What should this do?" + +Tests-after are biased by your implementation. You test what you built, not what's required. You verify remembered edge cases, not discovered ones. + +Tests-first force edge case discovery before implementing. Tests-after verify you remembered everything (you didn't). + +30 minutes of tests after ≠ TDD. You get coverage, lose proof tests work. + +## Common Rationalizations + +| Excuse | Reality | +|--------|---------| +| "Too simple to test" | Simple code breaks. Test takes 30 seconds. | +| "I'll test after" | Tests passing immediately prove nothing. | +| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" | +| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. | +| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. | +| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. | +| "Need to explore first" | Fine. Throw away exploration, start with TDD. | +| "Test hard = design unclear" | Listen to test. Hard to test = hard to use. | +| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. | +| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. | +| "Existing code has no tests" | You're improving it. Add tests for existing code. | + +## Red Flags - STOP and Start Over + +- Code before test +- Test after implementation +- Test passes immediately +- Can't explain why test failed +- Tests added "later" +- Rationalizing "just this once" +- "I already manually tested it" +- "Tests after achieve the same purpose" +- "It's about spirit not ritual" +- "Keep as reference" or "adapt existing code" +- "Already spent X hours, deleting is wasteful" +- "TDD is dogmatic, I'm being pragmatic" +- "This is different because..." + +**All of these mean: Delete code. Start over with TDD.** + +## Example: Bug Fix + +**Bug:** Empty email accepted + +**RED** +```typescript +test('rejects empty email', async () => { + const result = await submitForm({ email: '' }); + expect(result.error).toBe('Email required'); +}); +``` + +**Verify RED** +```bash +$ npm test +FAIL: expected 'Email required', got undefined +``` + +**GREEN** +```typescript +function submitForm(data: FormData) { + if (!data.email?.trim()) { + return { error: 'Email required' }; + } + // ... +} +``` + +**Verify GREEN** +```bash +$ npm test +PASS +``` + +**REFACTOR** +Extract validation for multiple fields if needed. + +## Verification Checklist + +Before marking work complete: + +- [ ] Every new function/method has a test +- [ ] Watched each test fail before implementing +- [ ] Each test failed for expected reason (feature missing, not typo) +- [ ] Wrote minimal code to pass each test +- [ ] All tests pass +- [ ] Output pristine (no errors, warnings) +- [ ] Tests use real code (mocks only if unavoidable) +- [ ] Edge cases and errors covered + +Can't check all boxes? You skipped TDD. Start over. + +## When Stuck + +| Problem | Solution | +|---------|----------| +| Don't know how to test | Write wished-for API. Write assertion first. Ask your human partner. | +| Test too complicated | Design too complicated. Simplify interface. | +| Must mock everything | Code too coupled. Use dependency injection. | +| Test setup huge | Extract helpers. Still complex? Simplify design. | + +## Debugging Integration + +Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression. + +Never fix bugs without a test. + +## Testing Anti-Patterns + +When adding mocks or test utilities, read [testing-anti-patterns.md](testing-anti-patterns.md) to avoid common pitfalls: +- Testing mock behavior instead of real behavior +- Adding test-only methods to production classes +- Mocking without understanding dependencies + +## Final Rule + +``` +Production code → test exists and failed first +Otherwise → not TDD +``` + +No exceptions without your human partner's permission. diff --git a/skills/test-driven-development/testing-anti-patterns.md b/skills/test-driven-development/testing-anti-patterns.md new file mode 100644 index 0000000..e77ab6b --- /dev/null +++ b/skills/test-driven-development/testing-anti-patterns.md @@ -0,0 +1,299 @@ +# Testing Anti-Patterns + +**Load this reference when:** writing or changing tests, adding mocks, or tempted to add test-only methods to production code. + +## Overview + +Tests must verify real behavior, not mock behavior. Mocks are a means to isolate, not the thing being tested. + +**Core principle:** Test what the code does, not what the mocks do. + +**Following strict TDD prevents these anti-patterns.** + +## The Iron Laws + +``` +1. NEVER test mock behavior +2. NEVER add test-only methods to production classes +3. NEVER mock without understanding dependencies +``` + +## Anti-Pattern 1: Testing Mock Behavior + +**The violation:** +```typescript +// ❌ BAD: Testing that the mock exists +test('renders sidebar', () => { + render(); + expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument(); +}); +``` + +**Why this is wrong:** +- You're verifying the mock works, not that the component works +- Test passes when mock is present, fails when it's not +- Tells you nothing about real behavior + +**your human partner's correction:** "Are we testing the behavior of a mock?" + +**The fix:** +```typescript +// ✅ GOOD: Test real component or don't mock it +test('renders sidebar', () => { + render(); // Don't mock sidebar + expect(screen.getByRole('navigation')).toBeInTheDocument(); +}); + +// OR if sidebar must be mocked for isolation: +// Don't assert on the mock - test Page's behavior with sidebar present +``` + +### Gate Function + +``` +BEFORE asserting on any mock element: + Ask: "Am I testing real component behavior or just mock existence?" + + IF testing mock existence: + STOP - Delete the assertion or unmock the component + + Test real behavior instead +``` + +## Anti-Pattern 2: Test-Only Methods in Production + +**The violation:** +```typescript +// ❌ BAD: destroy() only used in tests +class Session { + async destroy() { // Looks like production API! + await this._workspaceManager?.destroyWorkspace(this.id); + // ... cleanup + } +} + +// In tests +afterEach(() => session.destroy()); +``` + +**Why this is wrong:** +- Production class polluted with test-only code +- Dangerous if accidentally called in production +- Violates YAGNI and separation of concerns +- Confuses object lifecycle with entity lifecycle + +**The fix:** +```typescript +// ✅ GOOD: Test utilities handle test cleanup +// Session has no destroy() - it's stateless in production + +// In test-utils/ +export async function cleanupSession(session: Session) { + const workspace = session.getWorkspaceInfo(); + if (workspace) { + await workspaceManager.destroyWorkspace(workspace.id); + } +} + +// In tests +afterEach(() => cleanupSession(session)); +``` + +### Gate Function + +``` +BEFORE adding any method to production class: + Ask: "Is this only used by tests?" + + IF yes: + STOP - Don't add it + Put it in test utilities instead + + Ask: "Does this class own this resource's lifecycle?" + + IF no: + STOP - Wrong class for this method +``` + +## Anti-Pattern 3: Mocking Without Understanding + +**The violation:** +```typescript +// ❌ BAD: Mock breaks test logic +test('detects duplicate server', () => { + // Mock prevents config write that test depends on! + vi.mock('ToolCatalog', () => ({ + discoverAndCacheTools: vi.fn().mockResolvedValue(undefined) + })); + + await addServer(config); + await addServer(config); // Should throw - but won't! +}); +``` + +**Why this is wrong:** +- Mocked method had side effect test depended on (writing config) +- Over-mocking to "be safe" breaks actual behavior +- Test passes for wrong reason or fails mysteriously + +**The fix:** +```typescript +// ✅ GOOD: Mock at correct level +test('detects duplicate server', () => { + // Mock the slow part, preserve behavior test needs + vi.mock('MCPServerManager'); // Just mock slow server startup + + await addServer(config); // Config written + await addServer(config); // Duplicate detected ✓ +}); +``` + +### Gate Function + +``` +BEFORE mocking any method: + STOP - Don't mock yet + + 1. Ask: "What side effects does the real method have?" + 2. Ask: "Does this test depend on any of those side effects?" + 3. Ask: "Do I fully understand what this test needs?" + + IF depends on side effects: + Mock at lower level (the actual slow/external operation) + OR use test doubles that preserve necessary behavior + NOT the high-level method the test depends on + + IF unsure what test depends on: + Run test with real implementation FIRST + Observe what actually needs to happen + THEN add minimal mocking at the right level + + Red flags: + - "I'll mock this to be safe" + - "This might be slow, better mock it" + - Mocking without understanding the dependency chain +``` + +## Anti-Pattern 4: Incomplete Mocks + +**The violation:** +```typescript +// ❌ BAD: Partial mock - only fields you think you need +const mockResponse = { + status: 'success', + data: { userId: '123', name: 'Alice' } + // Missing: metadata that downstream code uses +}; + +// Later: breaks when code accesses response.metadata.requestId +``` + +**Why this is wrong:** +- **Partial mocks hide structural assumptions** - You only mocked fields you know about +- **Downstream code may depend on fields you didn't include** - Silent failures +- **Tests pass but integration fails** - Mock incomplete, real API complete +- **False confidence** - Test proves nothing about real behavior + +**The Iron Rule:** Mock the COMPLETE data structure as it exists in reality, not just fields your immediate test uses. + +**The fix:** +```typescript +// ✅ GOOD: Mirror real API completeness +const mockResponse = { + status: 'success', + data: { userId: '123', name: 'Alice' }, + metadata: { requestId: 'req-789', timestamp: 1234567890 } + // All fields real API returns +}; +``` + +### Gate Function + +``` +BEFORE creating mock responses: + Check: "What fields does the real API response contain?" + + Actions: + 1. Examine actual API response from docs/examples + 2. Include ALL fields system might consume downstream + 3. Verify mock matches real response schema completely + + Critical: + If you're creating a mock, you must understand the ENTIRE structure + Partial mocks fail silently when code depends on omitted fields + + If uncertain: Include all documented fields +``` + +## Anti-Pattern 5: Integration Tests as Afterthought + +**The violation:** +``` +✅ Implementation complete +❌ No tests written +"Ready for testing" +``` + +**Why this is wrong:** +- Testing is part of implementation, not optional follow-up +- TDD would have caught this +- Can't claim complete without tests + +**The fix:** +``` +TDD cycle: +1. Write failing test +2. Implement to pass +3. Refactor +4. THEN claim complete +``` + +## When Mocks Become Too Complex + +**Warning signs:** +- Mock setup longer than test logic +- Mocking everything to make test pass +- Mocks missing methods real components have +- Test breaks when mock changes + +**your human partner's question:** "Do we need to be using a mock here?" + +**Consider:** Integration tests with real components often simpler than complex mocks + +## TDD Prevents These Anti-Patterns + +**Why TDD helps:** +1. **Write test first** → Forces you to think about what you're actually testing +2. **Watch it fail** → Confirms test tests real behavior, not mocks +3. **Minimal implementation** → No test-only methods creep in +4. **Real dependencies** → You see what the test actually needs before mocking + +**If you're testing mock behavior, you violated TDD** - you added mocks without watching test fail against real code first. + +## Quick Reference + +| Anti-Pattern | Fix | +|--------------|-----| +| Assert on mock elements | Test real component or unmock it | +| Test-only methods in production | Move to test utilities | +| Mock without understanding | Understand dependencies first, mock minimally | +| Incomplete mocks | Mirror real API completely | +| Tests as afterthought | TDD - tests first | +| Over-complex mocks | Consider integration tests | + +## Red Flags + +- Assertion checks for `*-mock` test IDs +- Methods only called in test files +- Mock setup is >50% of test +- Test fails when you remove mock +- Can't explain why mock is needed +- Mocking "just to be safe" + +## The Bottom Line + +**Mocks are tools to isolate, not things to test.** + +If TDD reveals you're testing mock behavior, you've gone wrong. + +Fix: Test real behavior or question why you're mocking at all. diff --git a/skills/wayfinder/LICENSE b/skills/wayfinder/LICENSE new file mode 100644 index 0000000..f1dd2c0 --- /dev/null +++ b/skills/wayfinder/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Matt Pocock + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/wayfinder/SKILL.md b/skills/wayfinder/SKILL.md new file mode 100644 index 0000000..42e3644 --- /dev/null +++ b/skills/wayfinder/SKILL.md @@ -0,0 +1,128 @@ +--- +name: wayfinder +description: Plan a huge chunk of work — more than one agent session can hold — as a shared map of decision tickets on your issue tracker, and resolve them one at a time until the way to the destination is clear. +disable-model-invocation: true +--- + +A loose idea has arrived — too big for one agent session, and wrapped in fog: the way from here to the **destination** isn't visible yet. Wayfinding is about finding that way, not charging at the destination. This skill charts the way as a **shared map** on the repo's issue tracker, then works its **decision tickets** — questions whose resolution is a decision, not slices of a build to execute — one at a time until the route is clear. + +The destination varies per effort, and naming it is the first act of charting — it shapes every ticket. It might be a spec to hand off and iterate on, a decision to lock before planning starts, or a change made in place like a data-structure migration. The map is domain-agnostic — engineering work, course content, whatever fits the shape. + +## Plan, don't do + +Wayfinder is **planning** by default: each ticket resolves a decision, and the map is done when the way is clear — nothing left to decide before someone goes and does the thing. The pull to just do the work is usually the signal you've reached the edge of the map and it's time to hand off. An effort can override this in its **Notes** — carrying execution into the map itself — but absent that, produce decisions, not deliverables. + +## Refer by name + +Every map and ticket is an issue, so it has a **name** — its title. In everything the human reads — narration, the map's Decisions-so-far — refer to it by that name, never by a bare id, number, or slug. A wall of `#42, #43, #44` is illegible; names read at a glance. The id and URL don't vanish — a name wraps its link — but they ride *inside* the name, never stand in for it. + +## The Map + +The map is a single issue on this repo's issue tracker, labelled `wayfinder:map` — the canonical artifact. Its tickets are child issues of the map. + +The map is an **index**, not a store. It lists the decisions made and points at the tickets that hold their detail; a decision lives in exactly one place — its ticket — so the map never restates it, only gists it and links. + +**Where the map, its child tickets, blocking, and frontier queries physically live is tracker-specific.** The issue tracker should have been provided to you — run `/setup-matt-pocock-skills` if not. Consult the tracker doc's "Wayfinding operations" section for how _this_ repo expresses them. If no tracker has been provided, default to the local-markdown tracker. + +### The map body + +The whole map at low resolution, loaded once per session. Open tickets are **not** listed — they are open child issues, found by query. + +```markdown +## Destination + + + +## Notes + + + +## Decisions so far + + + +- [](link) — + +## Not yet specified + + + +## Out of scope + + +``` + +### Tickets + +Each ticket is a **child issue** of the map; the tracker's issue id is its identity. Its body is the question, sized to one 100K token agent session: + +```markdown +## Question + + +``` + +Each ticket carries a `wayfinder:` label — one of `research`, `prototype`, `grilling`, `task` (see [Ticket Types](#ticket-types)). + +A session **claims** a ticket by assigning it to the dev driving the map, **first**, before any work, so concurrent sessions skip it. That assignee _is_ the claim: an open, unassigned ticket is unclaimed. + +Blocking uses the tracker's **native** dependency relationship — essential because it renders the frontier _visually_ in the tracker's own UI, so the human sees what's takeable without opening the map. Only a tracker that lacks native blocking falls back to a body convention. A ticket is **unblocked** when every ticket blocking it is closed; the **frontier** is the open, unblocked, unclaimed children — the edge of the known. + +The answer isn't part of the body — it's recorded on resolution (see [Work through the map](#work-through-the-map)). Assets created while resolving a ticket are linked from the issue, not pasted in. + +## Ticket Types + +Every ticket is either **HITL** — human in the loop, worked *with* a human who speaks for themselves — or **AFK**, driven by the agent alone. A HITL ticket only resolves through that live exchange; the agent never stands in for the human's side of it (a grilling agent that answers its own questions has broken this). + +- **Research** (AFK): Reading documentation, third-party APIs, or local resources like knowledge bases to surface a fact a decision waits on. Resolved by a `/research` **subagent**. Use when knowledge outside the current working directory is required. +- **Prototype** (HITL): 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. Links the prototype as an asset. Use when "how should it look" or "how should it behave" is the key question. +- **Grilling** (HITL): Conversation via the /grilling and /domain-modeling skills, one question at a time. The default case. +- **Task** (HITL or AFK): Manual work that must happen before a *decision* can be made — nothing to decide, prototype, or research, but the discussion is blocked until it's done. Signing up for a service so its API can be judged, provisioning access, moving data so its shape can be seen. This is the one type that *does* rather than decides — and it earns its place by unblocking a decision, not by delivering the destination. The agent drives it alone where it can (AFK); otherwise it hands the human a precise checklist (HITL). Resolved when the work is done; the answer records what was done and any resulting facts (credentials location, new URLs, row counts) later tickets depend on. + +## Fog of war + +The map is _deliberately_ incomplete: don't chart what you can't yet see. Beyond the live tickets lies the **fog of war** — the dim view of decisions and investigations you can tell are coming but can't yet pin down, because they hang on questions still open. Resolving a ticket clears the fog ahead of it, graduating whatever's now specifiable into fresh tickets — one at a time, until the way to the destination is clear and no tickets remain. + +The map's **Not yet specified** section is where that dim view is written down: the suspected question, the area to revisit later. It's the undiscovered frontier _toward_ the destination — everything here is in scope, just not sharp enough to ticket. Write as loosely or as fully as the view allows; it doubles as a signpost for collaborators reading where the effort is headed. + +**Fog or ticket?** The test is whether you can state the question precisely now — _not_ whether you can answer it now. + +- **Ticket when** the question is already sharp — even if it's blocked and you can't act on it yet. +- **Not yet specified when** you can't yet phrase it that sharply. Don't pre-slice the fog into ticket-sized pieces: it's coarser than a ticket, and one patch may graduate into several tickets, or none, once the frontier reaches it. + +**Not yet specified** excludes what's already decided (Decisions so far), what's already a live ticket, and what's out of scope (the next section). + +## Out of scope + +Fog only ever gathers _toward_ the destination. The destination fixes the scope, so work beyond it is **out of scope** — it isn't fog, and it doesn't belong in **Not yet specified**. It gets its own **Out of scope** section on the map: work you've consciously ruled out of _this_ effort. Scope, not sharpness, lands it here. + +Out-of-scope work never graduates — the frontier stops at the destination — so it returns only if the destination is redrawn, and then as a fresh effort, not a resumption. + +Ruling something out of scope is a scoping act, not a step on the route. When a ticket that already exists turns out to sit past the destination — mis-scoped in while charting, or exposed by a resolution — **close it** (a closed ticket is unambiguously off the frontier) and leave one line in the **Out of scope** section: the gist plus why it's out of scope, linking the closed ticket. It stays out of **Decisions so far**, which records the route actually walked — a scope boundary isn't a step on it. + +## Invocation + +Two modes. Either way, **never resolve more than one ticket per session** — with the exception of research tickets. + +### Chart the map + +User invokes with a loose idea. + +1. **Name the destination.** Run a `/grilling` and `/domain-modeling` session to pin down what this map is finding its way to — the spec, decision, or change. The destination fixes the scope, so it's settled first. +2. **Map the frontier.** Grill again, **breadth-first** this time: fan out across the whole space rather than deep on any one thread, surfacing the open decisions and the first steps takeable now. **If this surfaces no fog** — the way to the destination is already clear, the whole journey small enough for one session — you don't need a map. Stop and ask the user how they'd like to proceed. +3. **Create the map** (label `wayfinder:map`): Destination and Notes filled in, Decisions-so-far empty, the fog sketched into **Not yet specified**. +4. **Create the tickets you can specify now** as child issues of the map — then wire blocking edges in a **second pass** (issues need ids before they can reference each other). Wiring sorts them into the frontier and the blocked; everything you can't yet specify stays in the fog — the **Not yet specified** section. +5. **Fire the research subagents.** For each `research` ticket you just created, spin up a `/research` subagent to resolve it in parallel, capturing its findings on a throwaway `research/` branch with a context pointer from the ticket. +6. Stop — charting is one session's work; it hand-resolves nothing. + +### Work through the map + +User invokes with a map (URL or number). A ticket is **optional** — without one, you pick the next decision, not the user. + +1. Load the **map** — the low-res view, not every ticket body. +2. Choose the ticket. If the user named one, use it. Otherwise take the first frontier ticket in order. **Claim it**: assign it to yourself before any work. +3. Resolve it — **zoom as needed**: fetch the full body of any related or closed ticket on demand; invoke the skills the `## Notes` block names. If in doubt, use `/grilling` and `/domain-modeling`. +4. Record the resolution: post the answer as a **resolution comment**, **close** the issue, and **append a context pointer** to the map's Decisions-so-far. +5. Add newly-surfaced tickets (create-then-wire); graduate any fog the answer has made specifiable, clearing each graduated patch from **Not yet specified** so it lives only as its new ticket. If the answer reveals a ticket — this one or another — sits beyond the destination, **rule it out of scope** rather than resolving it on the route. If the decision invalidates other parts of the map, update or delete those tickets. + +The user may run unblocked tickets in parallel, so expect other sessions to be editing the tracker concurrently. diff --git a/skills/wayfinder/agents/openai.yaml b/skills/wayfinder/agents/openai.yaml new file mode 100644 index 0000000..b375447 --- /dev/null +++ b/skills/wayfinder/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Wayfinder" + short_description: "Map a large effort as decision tickets" +policy: + allow_implicit_invocation: false diff --git a/upstream-skills.lock.json b/upstream-skills.lock.json index 27d7abd..9c95b47 100644 --- a/upstream-skills.lock.json +++ b/upstream-skills.lock.json @@ -9,6 +9,69 @@ "commit": "81c336c1c20b80ac648e0416a7b6e0c0ae7878bb", "license": "Apache-2.0", "synced_at": "2026-07-20T13:53:03.306628+00:00" + }, + "grill-me": { + "repo": "mattpocock/skills", + "source_url": "https://github.com/mattpocock/skills", + "path": "skills/productivity/grill-me", + "ref": null, + "commit": "9603c1cc8118d08bc1b3bf34cf714f62178dea3b", + "license": "MIT", + "synced_at": "2026-07-20T19:42:20.646037+00:00" + }, + "grill-with-docs": { + "repo": "mattpocock/skills", + "source_url": "https://github.com/mattpocock/skills", + "path": "skills/engineering/grill-with-docs", + "ref": null, + "commit": "9603c1cc8118d08bc1b3bf34cf714f62178dea3b", + "license": "MIT", + "synced_at": "2026-07-20T19:42:21.112847+00:00" + }, + "grilling": { + "repo": "mattpocock/skills", + "source_url": "https://github.com/mattpocock/skills", + "path": "skills/productivity/grilling", + "ref": null, + "commit": "9603c1cc8118d08bc1b3bf34cf714f62178dea3b", + "license": "MIT", + "synced_at": "2026-07-20T19:42:21.615914+00:00" + }, + "improve-codebase-architecture": { + "repo": "mattpocock/skills", + "source_url": "https://github.com/mattpocock/skills", + "path": "skills/engineering/improve-codebase-architecture", + "ref": null, + "commit": "9603c1cc8118d08bc1b3bf34cf714f62178dea3b", + "license": "MIT", + "synced_at": "2026-07-20T19:42:22.097327+00:00" + }, + "teach": { + "repo": "mattpocock/skills", + "source_url": "https://github.com/mattpocock/skills", + "path": "skills/productivity/teach", + "ref": null, + "commit": "9603c1cc8118d08bc1b3bf34cf714f62178dea3b", + "license": "MIT", + "synced_at": "2026-07-20T19:42:22.589660+00:00" + }, + "test-driven-development": { + "repo": "obra/superpowers", + "source_url": "https://github.com/obra/superpowers", + "path": "skills/test-driven-development", + "ref": null, + "commit": "d884ae04edebef577e82ff7c4e143debd0bbec99", + "license": "MIT", + "synced_at": "2026-07-20T19:42:23.138636+00:00" + }, + "wayfinder": { + "repo": "mattpocock/skills", + "source_url": "https://github.com/mattpocock/skills", + "path": "skills/engineering/wayfinder", + "ref": null, + "commit": "9603c1cc8118d08bc1b3bf34cf714f62178dea3b", + "license": "MIT", + "synced_at": "2026-07-20T19:42:23.614754+00:00" } } } diff --git a/upstream-skills.toml b/upstream-skills.toml index 5902b8f..f7a92c1 100644 --- a/upstream-skills.toml +++ b/upstream-skills.toml @@ -20,28 +20,34 @@ path = "skills/agent-browser" [[skill]] name = "grill-me" repo = "mattpocock/skills" +path = "skills/productivity/grill-me" [[skill]] name = "grill-with-docs" repo = "mattpocock/skills" +path = "skills/engineering/grill-with-docs" [[skill]] name = "grilling" repo = "mattpocock/skills" +path = "skills/productivity/grilling" [[skill]] name = "improve-codebase-architecture" repo = "mattpocock/skills" +path = "skills/engineering/improve-codebase-architecture" [[skill]] name = "teach" repo = "mattpocock/skills" +path = "skills/productivity/teach" [[skill]] name = "test-driven-development" repo = "obra/superpowers" -path = "skills/test-driven-development/SKILL.md" +path = "skills/test-driven-development" [[skill]] name = "wayfinder" repo = "mattpocock/skills" +path = "skills/engineering/wayfinder" From cd8ed94623927f0504a5f59a1bc80945efc38dc3 Mon Sep 17 00:00:00 2001 From: Ben Sykes Date: Tue, 28 Jul 2026 14:22:50 -0400 Subject: [PATCH 11/15] feat: add sdd-qa skill for one-by-one SDD questions Signed-off-by: Ben Sykes --- README.md | 1 + skills/sdd-qa/SKILL.md | 26 ++++++ skills/sdd-qa/references/qa-format.md | 81 ++++++++++++++++++ skills/sdd-qa/scripts/find-questions.py | 104 ++++++++++++++++++++++++ tests/test_find_questions.py | 70 ++++++++++++++++ tests/test_skill_contract.py | 1 + 6 files changed, 283 insertions(+) create mode 100644 skills/sdd-qa/SKILL.md create mode 100644 skills/sdd-qa/references/qa-format.md create mode 100755 skills/sdd-qa/scripts/find-questions.py create mode 100644 tests/test_find_questions.py diff --git a/README.md b/README.md index 8928f17..e2288c2 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Each skill is a directory under `skills//` containing a `SKILL.md` (plus a | `lavish-safe` | Local-only Lavish HTML review via `lavish-axi`, with share and telemetry forbidden. | | `research_codebase` | Map how a codebase works today and save a dated, citation-backed report under `thoughts/`, using parallel sub-agents by default. | | `sdd-linear` | Run the Spec-Driven Development (SDD) workflow with Linear issues, sub-issues, attachments, and comments as the system of record instead of `docs/specs`. | +| `sdd-qa` | Ask SDD `docs/specs` clarification questions ONE-by-ONE and write decisions back to the questions file (explicit slash invocation). | | `sync-upstream` | Sync a fork's default branch with its upstream remote using merge or rebase, resolving conflicts as needed. | | `taskfile-automation` | Scaffold consistent, portable repo automation with a `Taskfile` as the single entry point (run the same locally and in CI), adding Docker/Compose only when external runtime deps demand it. | | `visual-explain` | Interactive local HTML explanation of a diff/branch/PR (Background, Intuition, Code walkthrough, Quiz). Adapted from sighup/claude-workflow `cw-explain`. | diff --git a/skills/sdd-qa/SKILL.md b/skills/sdd-qa/SKILL.md new file mode 100644 index 0000000..d5c5f72 --- /dev/null +++ b/skills/sdd-qa/SKILL.md @@ -0,0 +1,26 @@ +--- +name: sdd-qa +description: "Ask SDD spec clarification questions ONE-by-ONE and write decisions back to the questions file. Slash-command only." +disable-model-invocation: true +--- + +# SDD Q&A + +Ask me questions from the spec questions file ONE-by-ONE and write the decision and any additional context back to the question file. + +## Resolve the file + +```bash +python3 {{skill_dir}}/scripts/find-questions.py +``` + +`` = user arg, path, `01`, feature slug, or glob like `01*questions.md`. Cwd = workspace root. If 0 or >1 matches, ask which file. + +## Run the loop + +1. Read `{{skill_dir}}/references/qa-format.md` — follow its response + file-write formats exactly. +2. Find the first unanswered question (no `**Decision:**`, or all options still `[ ]`). +3. Ask **only that question**. Wait. +4. On a clear choice: write decision + context into the file, then ask the next. +5. Clarifying questions mid-flow: answer them; do **not** advance until they pick. +6. After the last answer: write a decision summary table (see reference). diff --git a/skills/sdd-qa/references/qa-format.md b/skills/sdd-qa/references/qa-format.md new file mode 100644 index 0000000..d5159d2 --- /dev/null +++ b/skills/sdd-qa/references/qa-format.md @@ -0,0 +1,81 @@ +# SDD Q&A — formats + +## Question file shape + +Each question block in `NN-questions-*-….md`: + +```markdown +## N. Short title + +Context paragraph(s). Stem question? + +- [ ] (A) Option text +- [ ] (B) Option text +- [ ] (C) Other (describe) + +**Recommended answer(s):** [(A) or (B)] + +**Why these are recommended:** + +- Bullet why. +``` + +After the user answers, mark choice(s) with `[x]` and insert **above** Recommended: + +```markdown +- [x] (A) Option text +- [ ] (B) Option text + +**Decision:** (A) + +**Additional context:** +``` + +If they pick a combo (e.g. A+D), check all relevant boxes; Decision line states the combo. + +## Asking (chat) — one question only + +```markdown +**QN — Short title** + +<1–3 lines of stem / stakes> + +- **(A)** … +- **(B)** … +- **(C)** … + +**Recommended:** + +Pick + any context. +``` + +Do not dump later questions. Keep options scannable (bold letter, short text). + +## After they answer + +1. Edit the questions file (checkbox + Decision + Additional context). +2. Confirm, then ask the next: + +```markdown +**QN recorded:** (X) — + +--- + +**Q{N+1} — Short title** +… +``` + +## Clarifications mid-question + +Answer plainly. Stay on the same Q until they pick a letter (or Other). Then record. + +## Done — decision summary + +```markdown +Round complete. Written to ``. + +| # | Decision | +|---|---| +| 1 | (A) short gloss | +| 2 | (B) short gloss | +``` diff --git a/skills/sdd-qa/scripts/find-questions.py b/skills/sdd-qa/scripts/find-questions.py new file mode 100755 index 0000000..e191dd4 --- /dev/null +++ b/skills/sdd-qa/scripts/find-questions.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""Find SDD questions markdown files under docs/specs/.""" + +from __future__ import annotations + +import argparse +import fnmatch +import json +import sys +from pathlib import Path + +QUESTIONS_RE = "*-questions*.md" + + +def specs_root(workspace: Path) -> Path: + return workspace / "docs" / "specs" + + +def collect(workspace: Path) -> list[Path]: + root = specs_root(workspace) + if not root.is_dir(): + return [] + return sorted(p for p in root.rglob(QUESTIONS_RE) if p.is_file()) + + +def matches(query: str, workspace: Path) -> list[Path]: + all_files = collect(workspace) + q = query.strip() + if not q: + return all_files + + as_path = Path(q) + if as_path.is_file(): + return [as_path.resolve()] + candidate = workspace / q + if candidate.is_file(): + return [candidate.resolve()] + + patterns = [q] + # `01*questions.md` should still hit `01-questions-1-feature.md` + if "questions" in q and q.endswith(".md") and "questions*." not in q: + patterns.append(q.replace("questions.md", "questions*.md")) + + found: set[Path] = set() + for pattern in patterns: + for base in (specs_root(workspace), workspace): + for p in list(base.glob(pattern)) + list(base.rglob(pattern)): + if p.is_file() and "questions" in p.name: + found.add(p.resolve()) + for p in all_files: + rel = str(p.relative_to(workspace)) + if fnmatch.fnmatch(p.name, pattern) or fnmatch.fnmatch(rel, pattern): + found.add(p.resolve()) + # Allow patterns anchored under docs/specs/ + if fnmatch.fnmatch(f"docs/specs/{p.name}", pattern) or fnmatch.fnmatch( + f"docs/specs/{p.parent.name}/{p.name}", pattern + ): + found.add(p.resolve()) + + if found: + return sorted(found) + + needle = q.lower() + return [ + p + for p in all_files + if needle in p.name.lower() + or needle in str(p.relative_to(workspace)).lower() + or needle in p.parent.name.lower() + ] + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "query", + nargs="?", + default="", + help="Spec number, slug, path, or glob (e.g. 01, dataflow-slack, '01*questions.md')", + ) + parser.add_argument( + "--workspace", + default=".", + help="Repo root (default: cwd)", + ) + args = parser.parse_args() + workspace = Path(args.workspace).resolve() + found = matches(args.query, workspace) + print( + json.dumps( + { + "workspace": str(workspace), + "query": args.query, + "count": len(found), + "paths": [str(p) for p in found], + }, + indent=2, + ) + ) + return 0 if found else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_find_questions.py b/tests/test_find_questions.py new file mode 100644 index 0000000..3ffc06e --- /dev/null +++ b/tests/test_find_questions.py @@ -0,0 +1,70 @@ +"""Behavior tests for the sdd-qa `find-questions.py` helper.""" + +from __future__ import annotations + +import json +from pathlib import Path + +from conftest import run_script + +SKILL = "sdd-qa" + + +def _seed_questions(workspace: Path, *, name: str = "01-questions-1-demo.md") -> Path: + spec_dir = workspace / "docs" / "specs" / "01-spec-demo-feature" + spec_dir.mkdir(parents=True) + path = spec_dir / name + path.write_text("# questions\n", encoding="utf-8") + return path + + +def _run(workspace: Path, *args: str): + return run_script( + SKILL, + "find-questions.py", + *args, + "--workspace", + str(workspace), + ) + + +def test_finds_by_spec_number(tmp_path: Path) -> None: + q = _seed_questions(tmp_path) + proc = _run(tmp_path, "01") + assert proc.returncode == 0, proc.stderr + payload = json.loads(proc.stdout) + assert payload["count"] == 1 + assert payload["paths"] == [str(q.resolve())] + + +def test_finds_by_feature_slug(tmp_path: Path) -> None: + q = _seed_questions(tmp_path) + proc = _run(tmp_path, "demo-feature") + assert proc.returncode == 0, proc.stderr + payload = json.loads(proc.stdout) + assert payload["paths"] == [str(q.resolve())] + + +def test_finds_by_loose_questions_glob(tmp_path: Path) -> None: + q = _seed_questions(tmp_path) + proc = _run(tmp_path, "01*questions.md") + assert proc.returncode == 0, proc.stderr + payload = json.loads(proc.stdout) + assert payload["paths"] == [str(q.resolve())] + + +def test_no_match_exits_one(tmp_path: Path) -> None: + _seed_questions(tmp_path) + proc = _run(tmp_path, "zz-missing") + assert proc.returncode == 1 + payload = json.loads(proc.stdout) + assert payload["count"] == 0 + assert payload["paths"] == [] + + +def test_empty_query_lists_all(tmp_path: Path) -> None: + q = _seed_questions(tmp_path) + proc = _run(tmp_path) + assert proc.returncode == 0, proc.stderr + payload = json.loads(proc.stdout) + assert payload["paths"] == [str(q.resolve())] diff --git a/tests/test_skill_contract.py b/tests/test_skill_contract.py index ab4f2bd..f4a4938 100644 --- a/tests/test_skill_contract.py +++ b/tests/test_skill_contract.py @@ -24,6 +24,7 @@ "lavish-safe", "research_codebase", "sdd-linear", + "sdd-qa", "sync-upstream", "taskfile-automation", "visual-explain", From 0fa93255dcd48f5295ee4d2cdd39291722b1df8d Mon Sep 17 00:00:00 2001 From: Ben Sykes Date: Tue, 11 Aug 2026 11:23:08 -0400 Subject: [PATCH 12/15] feat: add pr-feedback-qa skill for review disposition Q&A One-item Address/Skip/GitHub Issue flow with Plan mode gate, file or PR input, and resumable .scratch JSON sessions. Signed-off-by: Ben Sykes --- .gitignore | 3 + README.md | 1 + docs/DEVELOPMENT.md | 1 + skills/pr-feedback-qa/SKILL.md | 97 ++++ .../pr-feedback-qa/references/qa-template.md | 118 +++++ .../references/session-schema.json | 217 ++++++++ skills/pr-feedback-qa/scripts/session_log.py | 486 ++++++++++++++++++ tests/test_pr_feedback_qa_session.py | 230 +++++++++ tests/test_skill_contract.py | 1 + 9 files changed, 1154 insertions(+) create mode 100644 skills/pr-feedback-qa/SKILL.md create mode 100644 skills/pr-feedback-qa/references/qa-template.md create mode 100644 skills/pr-feedback-qa/references/session-schema.json create mode 100755 skills/pr-feedback-qa/scripts/session_log.py create mode 100644 tests/test_pr_feedback_qa_session.py diff --git a/.gitignore b/.gitignore index f040d55..0a39c5e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,9 @@ node_modules/ # issue-triage clarifying Q&A / resume artifacts (subject-repo local state) .issue-triage/ +# pr-feedback-qa session JSON (subject-repo local state; also ensured by session_log.py) +.scratch/ + # skill-creator eval run workspaces (sibling to skill dirs or repo root) *-workspace/ skills/*-workspace/ diff --git a/README.md b/README.md index e2288c2..b5c86f5 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Each skill is a directory under `skills//` containing a `SKILL.md` (plus a | `issue-triage` | Turn a rough GitHub Issue into an agent-executable sealed body with `ready` + size labels (explicit invocation). Clarifying Q&A persists under `.issue-triage/` (gitignored) for resume. Ships `issue_ops` + validators, offline `evals/`, and `mock_gh` for script unit tests. | | `jj-case-insensitive-clone-fix` | Diagnose and fix the `jj git clone` "Failed to update refs" error on case-insensitive filesystems (e.g. macOS APFS). | | `lavish-safe` | Local-only Lavish HTML review via `lavish-axi`, with share and telemetry forbidden. | +| `pr-feedback-qa` | Disposition PR or file-based review feedback one item at a time (Address / Skip / GitHub Issue), with resumable JSON sessions under `.scratch/pr-feedback-qa/` and a final decision table. | | `research_codebase` | Map how a codebase works today and save a dated, citation-backed report under `thoughts/`, using parallel sub-agents by default. | | `sdd-linear` | Run the Spec-Driven Development (SDD) workflow with Linear issues, sub-issues, attachments, and comments as the system of record instead of `docs/specs`. | | `sdd-qa` | Ask SDD `docs/specs` clarification questions ONE-by-ONE and write decisions back to the questions file (explicit slash invocation). | diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 4dc1140..55cf687 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -99,5 +99,6 @@ Or generate candidates: `task capture-project PROJECT=…` (local-only installs | `docs/`, `README.md`, `CONTRIBUTING.md`, `AGENTS.md` | Canon | | `.lavish/` | Local review scratch — do not treat as process source of truth | | `.issue-triage/` | issue-triage clarifying Q&A / resume logs — gitignored | +| `.scratch/pr-feedback-qa/` | pr-feedback-qa session JSON — subject-repo local state (ensure `.scratch/` gitignored) | | `.agents/`, `skills-lock.json` | Install artifacts — gitignored | | `skills/*-workspace/` | skill-creator eval run outputs — gitignored | diff --git a/skills/pr-feedback-qa/SKILL.md b/skills/pr-feedback-qa/SKILL.md new file mode 100644 index 0000000..7f6faf7 --- /dev/null +++ b/skills/pr-feedback-qa/SKILL.md @@ -0,0 +1,97 @@ +--- +name: pr-feedback-qa +description: >- + Work through PR or file-based review feedback one item at a time. For each + item, offer Address, Skip, or GitHub Issue. Persist Q&A under + .scratch/pr-feedback-qa as JSON so a session can resume. Use when the user + wants to disposition multi-model review feedback, PR review comments, or a + review markdown file with skip/track/address decisions and a final summary + table. +compatibility: Requires authenticated gh CLI for PR input and GitHub Issue creation +--- + +# PR feedback Q&A + +Disposition review findings one at a time. Templates: [references/qa-template.md](references/qa-template.md). Session JSON: [references/session-schema.json](references/session-schema.json). Session helper: `scripts/session_log.py`. + +## Hard rules + +1. **Plan mode first.** If not in Plan mode, ask the user to switch (use the mode-switch tool when available). Stop. Do not load feedback or ask dispositions until Plan mode is active. +2. Stay on the current item until Address, Skip, or GitHub Issue is chosen. +3. After a clarifying answer, ask the same disposition prompt again. +4. Do not implement fixes, edit source, or resolve PR review threads in this skill. +5. Create GitHub issues only after the summary table and explicit user confirmation. +6. Never commit `.scratch/` files. + +## Steps + +### 1. Plan mode gate + +Use the Plan-mode prompt in the template. Stop until Plan mode is on. + +### 2. Resolve input + +Accept one source: + +- **File** — local review markdown (or similar). +- **PR** — `owner/repo#N` or URL. Fetch review bodies, inline comments, and conversation comments with `gh`. Keep author and URL on each finding. Deduplicate repeats. Keep conflicting advice as separate items. + +### 3. Session + +Ask: new persisted session, resume matching `.scratch/pr-feedback-qa/*.json`, or ephemeral (no file). + +Persisted path: `.scratch/pr-feedback-qa/.json`. + +```bash +python3 {{skill_dir}}/scripts/session_log.py ensure-gitignore --repo-root . +python3 {{skill_dir}}/scripts/session_log.py init --repo-root . --slug --source-type file|pr --source +python3 {{skill_dir}}/scripts/session_log.py resume --repo-root . --slug +``` + +On every decision or clarification, write the session with `session_log.py record`. Ephemeral runs keep state in chat only. + +### 4. Normalize items + +Build an ordered list of findings. Number them `1..N`. Write findings into the session before the first question. + +### 5. One item at a time + +For each undecided item, follow the item prompt in the template: + +- Short summary of the finding. +- Numbered fix options. Always include **Other (describe)**. +- Disposition: **A** Address, **S** Skip, **I** GitHub Issue. + +Reply forms: `A1`, `S`, `I`, or a clarifying question. + +Record: + +| Disposition | Store | +| --- | --- | +| Address | Selected fix (or custom text) | +| Skip | Reason if given | +| GitHub Issue | Preferred fix if given; label `bug` and/or `enhancement` | + +Do not advance until decided. + +### 6. Summary + +After the last item, show the decision summary table from the template. Persist it. + +```bash +python3 {{skill_dir}}/scripts/session_log.py summary --repo-root . --slug +``` + +### 7. GitHub issues (queued only) + +List queued Issue items. Wait for confirmation. Then create each with `gh issue create`, apply `bug` / `enhancement`, link the PR or file source, and note the preferred fix. Write issue URLs back into the session. + +Parent + sub-issues are allowed when the user asked for that shape. + +## Anti-patterns + +- Starting Q&A outside Plan mode +- Asking two items in one message +- Advancing after a clarification without a new disposition answer +- Creating issues before confirmation +- Silent implementation of Address items diff --git a/skills/pr-feedback-qa/references/qa-template.md b/skills/pr-feedback-qa/references/qa-template.md new file mode 100644 index 0000000..030fc31 --- /dev/null +++ b/skills/pr-feedback-qa/references/qa-template.md @@ -0,0 +1,118 @@ +# PR feedback Q&A — templates + +Use these shapes in chat. Do not invent parallel formats. + +## Plan mode gate + +```markdown +This workflow needs **Plan mode**. + +Switch to Plan mode, then reply **ready**. +I will not load feedback or start Q&A until Plan mode is on. +``` + +If the mode-switch tool is available, call it with target `plan` and a one-line reason, then wait. + +## Session choice + +```markdown +**Session** + +1. New persisted session under `.scratch/pr-feedback-qa/` +2. Resume an existing session +3. Ephemeral (chat only; no file) + +Reply `1`, `2`, or `3`. +``` + +On `2`, list matching `.scratch/pr-feedback-qa/*.json` and ask which slug. + +## Item prompt + +```markdown +**Issue N/M — ** + +<1–3 lines: what is wrong and why it matters> + +**How to fix?** +1.