--- name: capture-knowledge description: Capture durable, project-specific learnings from the work just done into the knowledge/ base — gotchas, implementation quirks, things looked up or web-searched, live-API behaviours, and design decisions (as ADRs). Use before opening a PR (it runs automatically in /deliver), or any time you've learned something worth remembering. Records only non-obvious, reusable facts; skips anything already in the repo, CLAUDE.md, or git history. --- # Capture Knowledge Fold what you just learned into the committed knowledge base at `knowledge/`, so a future session (or contributor) doesn't have to re-learn or re-discover it. Run this **before a PR** — in `/deliver` it runs automatically pre-PR, so the notes land in the same PR as the change. ## What to capture (be selective) Record only **durable, project-specific, non-obvious** facts — the test is "would a future me waste time without this?": - **Gotchas / quirks** → `knowledge/gotchas.md` — a trap you hit, a tooling surprise, anything that needed a **web search or doc lookup** to resolve. - **Live-API behaviours** → `knowledge/tmdb-api-notes.md` — a nullable/absent field, an undocumented response shape, an enum value the docs omit. - **Design decisions** → a new **ADR** in `knowledge/decisions/` (next number — take it from [`decisions/README.md`](../../../knowledge/decisions/README.md)'s index, not a directory listing), using `decisions/0000-template.md`. Any non-obvious choice and its rationale — why this approach over the alternatives. Add the new row to that index. - **Deferred breaking changes** → `knowledge/next-major.md` — any fix rejected or deferred *because* it is breaking. The skill-improvement log remembers the "no"; this file is what makes the deferred "yes" resurface at the next major. **Reconcile its status line whenever you add an entry:** it states which major window is open, and an entry filed against a window that already shipped is invisible at exactly the moment it should fire. A window is open until the version is **tagged** — an unreleased `CHANGELOG` section is not a release (`git tag --list | sort -V | tail -1`). ## What NOT to capture Mirror the discipline of a good memory — don't record: - Anything already in the code, `CLAUDE.md`, the topic docs under `.claude/docs/`, the DocC docs, or git history. - Facts that only mattered to this one task and won't recur. - Restatements of the obvious. If asked to record something obvious, capture the *non-obvious* part (what surprised you) or skip it. Quality over volume: a few high-signal entries beat a long dump. **A learning that implies work is not a knowledge entry.** `knowledge/` records what is *true*; an issue records what should *change*. "The decoder drops `tv` rows" is not a gotcha to remember, it is a defect to file — writing it here instead buries it in a file nobody greps until they hit the same wall. When a candidate is really a task, file it per [`.github/ISSUE_FILING.md`](../../../.github/ISSUE_FILING.md) and record only the durable half here, if any survives. The two are not exclusive: a live-API quirk often earns a `tmdb-api-notes.md` line *and* an issue for the code that mishandles it — cite the issue number from the entry so they stay tied. ## When an entry records a count, ask what enforces it If a candidate entry states a **known-remaining defect count** — "54 sites still do X", "3 models still decode Y unsafely" — stop and ask the follow-up: **"what fails if that number changes?"** A number in a markdown file is a promise with nothing behind it; it silently goes stale as sites are fixed *or* regressed, and the two cancel out. Prefer a committed check with an **explicit set**, not a count (so a fix and a regression can't cancel, and an empty scan can't pass), wired into **both** `make lint` and CI — no workflow runs `make`, so one alone is invisible. `Scripts/check-defaulted-witnesses.py` is the worked example, and its history is the lesson: it began as an allowlist of sites still to fix, which checked only that the count went *down*. Once those sites were rewritten it had to also assert that each one's replacements were actually written, because every other gate is deletion-side — a missing replacement is a silent source break that passes lint, build, test and CI. **Ask which direction your check runs in**, and whether its green would look any different if the scan had matched nothing at all. If a guard isn't worth building, say so in the entry, so the number reads as an observation rather than an invariant. ## Steps 1. **Start from the candidates list, if one exists.** If the caller passed a **knowledge-candidates** list as the argument (`$ARGUMENTS` below — `/deliver` pastes its ledger list here), use that as your input — that's the reliable source, jotted while the learnings were fresh, and it reaches you intact even if the caller's context was compacted. Otherwise, reconstruct candidates by reviewing the work just done — the diff, the dead-ends, the things you looked up, the decisions you made. 2. **Filter** against "What NOT to capture". Drop the rest. 3. **Check for duplicates** — skim the relevant `knowledge/` file; **update** an existing entry rather than adding a near-duplicate. 4. **Write each entry** in the right file: - Gotchas / API notes: a short dated subsection (`### `), newest at the top, under the right heading. Date it with today's date. - **Cite the PR that did the work, not the issue it came from.** A bare `#NNN` is ambiguous — this repo's numbers interleave issues and PRs, so `(#432)` and `(#417)` look identical and only one is the change. Take the number from the branch's own PR; if it doesn't exist yet, leave a placeholder and backfill it. Name the issue only when the issue itself is the subject, and then say "issue #NNN" in words. (See [`knowledge/README.md`](../../../knowledge/README.md) → *How to use it*.) - Decisions: copy `decisions/0000-template.md` to `decisions/NNNN-<kebab-title>.md` (next free number), fill in Status / Date / Context / Decision / Consequences / Alternatives. Cross-link related ADRs. 5. **Retire what the diff invalidates — sweep by citation, not by neighbourhood.** The base is a **cache of current truths, not an archive** (git history is the archive — see [`knowledge/README.md`](../../../knowledge/README.md) → *Maintenance & retention*). Staleness is usually caused by the very change you are capturing for, so find it from the diff: 1. List the infrastructure files this change touched: ```bash git diff --name-only origin/main...HEAD | \ grep -E '^(Makefile|Package\.swift|Package\.resolved|\.github/workflows/|\.claude/)' ``` 2. For each hit, grep **all of `knowledge/`** (not just the file you are writing to) for entries citing that file, its targets, or its pinned values — e.g. `grep -n 'Makefile\|make ci\|TEST_TARGET' knowledge/*.md`, `grep -n 'ci\.yml\|Xcode_[0-9]' knowledge/*.md`. Test-target lists, pinned tool/Xcode versions, `SWIFTCI_DOCC`, scratch paths, and workflow step names are the usual casualties. If the change renames or removes a `knowledge/` entry heading, also grep `.claude/` and `CLAUDE.md` for citations of the old title — skills cite gotchas by title. 3. **Read every citing entry and reconcile it**: still true → leave it; partly true → rewrite it in the present tense; no longer true → delete it. **Never append an "Update:" / "Resolved in #NNN" note to a stale entry — rewrite or delete it.** An entry that narrates its own history contradicts itself within weeks (this is exactly how the `.docc` gotcha decayed across #396–#402). 4. Report the sweep as one line: `swept: <infra files touched> → <entries rewritten/deleted | none cited>`. No infra files in the diff → `swept: n/a`, and fall back to scanning the neighbouring entries of any knowledge file you edited. 6. **Keep it tidy** — blank lines around headings/lists/code fences, a language on every fence, one `#` H1 per file. `knowledge/**` **is** in the `make lint-markdown` scope and the CI `Lint Markdown` job, so a malformed entry fails the gate rather than rendering wrong in silence (a raw `|` inside a table cell once did exactly that). Run `make lint-markdown` after writing. Aim for ~80-col prose; long `jq`/URL lines are fine — `MD013` is disabled. 7. **Update `knowledge/README.md`** only if you added a new file or category (the per-entry index inside each file is enough otherwise). ## Return Report concisely what you captured: each entry → which file (and ADR number for decisions), and note anything you deliberately skipped as not durable. If nothing met the bar, say so plainly — capturing nothing is a valid outcome. List any issues you filed for candidates that turned out to be work rather than knowledge. **Always end with the `swept:` line from step 5.4** — `swept: <infra files> → <entries rewritten/deleted | none cited>`, or `swept: n/a`. It is not optional prose: `/deliver` copies it into the retro and treats its **absence as proof the retirement sweep never ran**. A report without it reads as a skipped step. Arguments: $ARGUMENTS