{"id":18848,"plugin_id":"plugins_6a8ceaf162b88191851ca2442d67e12d","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T23:14:58.547Z","digest":"3d6e81ed57547ebd6a9976d298c31e13fe207a64481a2839fb5ed19b24a2abd2","against":null,"payload":{"description":"Find footguns in code that already exists: swappable arguments, silent fallbacks, unguarded deletes, signatures that are easy to misuse. Use when someone asks \"what could bite us here\", \"what is easy to misuse\", \"poka-yoke this repo\", or wants a diff or PR reviewed for ways to get it wrong. Ranks by blast radius. For code not yet written use design; for something that already broke use retro.","included_files":[],"name":"audit","skill_md_contents":"---\nname: audit\ndescription: >-\n  Find footguns in code that already exists: swappable arguments, silent fallbacks, unguarded deletes, signatures that are easy to misuse. Use when someone asks \"what could bite us here\", \"what is easy to misuse\", \"poka-yoke this repo\", or wants a diff or PR reviewed for ways to get it wrong. Ranks by blast radius. For code not yet written use design; for something that already broke use retro.\n---\n\n# Poka-Yoke Audit\n\nFind the mistakes that are *available* in this code, then close them. You are not looking for\nbugs: a bug is a mistake that already happened. You are looking for **affordances for\nmistakes**: places where doing the wrong thing is easy, silent, and looks correct.\n\nThe load-bearing question throughout: *if a competent, tired engineer used this at 4pm on a\nFriday, what would go wrong and would anything stop them?*\n\n## 1. Establish scope\n\nDefault, when the user names no path:\n\n1. `git diff HEAD`: uncommitted work. This is what they are most likely asking about.\n2. If the tree is clean, `git diff HEAD~5..HEAD`: recent commits.\n3. If neither yields anything (fresh repo, no git), fall back to the risk surfaces below and\n   say that's what you did.\n\nWiden to the whole repo only when asked (\"audit the whole codebase\", \"full audit\"). It is\nslow and it buries the important findings in volume. When you do go wide, prioritize by\n**risk surface** rather than by directory, go straight to code that touches money,\nauthentication, authorization, deletion or overwriting, migrations, external I/O,\nconcurrency, and anything with `admin`, `force`, `bulk`, `sync`, or `delete` in its name.\n\nState the scope you chose in one line before you start, so the user can redirect you cheaply.\n\n## 2. Run the detector, then think\n\n```bash\npython3 ../../scripts/detect_hazards.py --diff   # path is relative to this SKILL.md\n```\n\nOther useful forms: `--paths src/ lib/`, `--staged`, `--since HEAD~10`, `--json`,\n`--severity high`, `--id C1 M2` to filter to specific rules. Run `--help` for the full set.\n\nThe script finds the mechanically detectable shapes, adjacent same-type parameters, boolean\nflag arguments, unbounded deletes, money held as a float, unvalidated request bodies, retries\nwithout an idempotency key. Shapes a real linter already covers, bare `except`, mutable\ndefault arguments, `any` escape hatches, are off by default and named in the footer; `--all`\nruns them too. It is a **fast first pass with real false positives**, not an oracle. Treat\neach hit as a question to investigate, and read the surrounding code before you believe it.\n\nThen do the part the script cannot: read the interfaces and run the three lenses over them.\n\n**Contact, can the wrong thing fit?** Look at every public signature. Are two adjacent\nparameters the same type? Could a caller pass an order ID where a user ID belongs, cents\nwhere dollars belong, a raw string where a validated one belongs? Does the boundary accept\n`any` / `dict` / `interface{}` and hope?\n\n**Fixed-value, can an incomplete or wrong-sized set pass?** Is every enum branch handled,\nand will adding a variant break the build or silently fall through? Can a bulk operation run\nwith an empty or unexpectedly huge set? Is config validated as a whole, or discovered\nmissing at 3am? Are required fields actually required, or optional-with-a-default?\n\n**Motion-step, can the order be wrong?** Must something be called before something else, with\nnothing enforcing it? Can a retry double-charge? Can a resource leak on the error path? Can\ntwo callers interleave between a check and the act that depends on it?\n\nThe script only sees text. These three questions are where the audit's value comes from.\n\n## 3. Classify every finding\n\nEach finding gets four fields. Fill all four: an unclassified finding is just an opinion.\n\n- **Mistake**: the specific wrong thing a person can do, stated as an action.\n  *\"Call `transfer(dst, src)` with the accounts reversed.\"*\n- **Consequence**: what happens when they do, and how loudly. Silence is the aggravator: a mistake that throws immediately is far less dangerous than one that returns a plausible\n  wrong answer.\n- **Current rung**: what exists today, Control / Warning / Detection / **None**.\n- **Proposed device + rung**: the specific change, and the rung it reaches. If you're\n  proposing Warning, say what would be needed for Control and why you didn't.\n\n## 4. Rank by expected damage, not by count\n\nPriority is **blast radius × ease of mistake**, and nothing else. A hundred stringly-typed\ninternal helpers matter less than one `delete_users(filter)` where `filter` can be empty.\n\nBlast radius, descending: irreversible data loss or money movement → security or\nauthorization bypass → silent data corruption → wrong output the user acts on → crash →\ndegraded experience. A crash ranking *below* silent wrong output is deliberate and worth\nsaying out loud: loud failures are cheap, quiet ones compound.\n\nEase of mistake, descending: silent and plausible-looking → requires only forgetting → needs\nan unusual-but-reachable input → needs deliberate misuse.\n\nReport the top findings in priority order and stop somewhere sensible, ten well-argued\nfindings beat forty. Say how many you set aside and why.\n\n## 5. Report\n\nUse this structure. It is short on purpose; the detail lives per-finding.\n\n```markdown\n# Poka-Yoke Audit · <scope> · <YYYY-MM-DD>\n\n**Scope**: <what was examined, e.g. \"uncommitted diff, 7 files, 340 lines\">\n**Verdict**: <one sentence, the single most important thing they should fix>\n\n## Findings\n\n### 1. <Short name of the mistake> · <Blast radius>/<Ease>\n**Where**: `path/to/file.ts:42`\n**Mistake**: <the wrong action a person can take>\n**Consequence**: <what happens, and whether it is silent>\n**Today**: <Control | Warning | Detection | None>\n**Device**: <the specific change> → **<Control | Warning | Detection>**\n\n<a short diff or code sketch>\n\n<if not Control: one line on what Control would cost>\n\n### 2. …\n\n## Set aside\n<n low-priority hazards, one line each, or \"none\">\n```\n\nWrite it to `docs/poka-yoke/audit-YYYY-MM-DD.md` in the user's repo. If they'd rather not\nhave a file, keep it in the conversation, ask if it isn't obvious.\n\n## 6. Propose, then apply\n\nPresent the findings and wait. Do not edit files yet. These changes alter interface shapes\nand ripple through call sites; people reasonably want to see the plan first.\n\nWhen they approve some or all of it: apply each device, leave a `poka-yoke:` marker comment\nat it saying which mistake it blocks, and run the tests.\n\n## Recording what a device is for\n\nDevices only stay valuable if people know they are load-bearing. Without a record, the next\nperson deletes the \"redundant\" check or relaxes the \"annoying\" constraint, and the mistake\ncomes back. A device that has never fired looks like dead weight precisely because it is\nworking.\n\nThe obvious answer, keep a registry file listing every device, is **wrong, by this skill's\nown argument.** A Markdown file someone must remember to update is training, not a device. It\ngoes stale exactly when it matters: the moment someone removes a constraint without touching\nthe doc. Do not ask anyone to maintain one.\n\n**Put the reason where the device is.** A marker comment at the constraint travels with it,\ngets read by the person about to delete it, and cannot drift out of sync because it is not a\nseparate thing:\n\n```python\n# poka-yoke: rejects a second charge for the same idempotency key   [control]\nUNIQUE (account_id, idempotency_key)\n```\n\n```ts\n// poka-yoke: forgetting to await this write would lose it silently [warning]\n\"@typescript-eslint/no-floating-promises\": \"error\",\n```\n\nThe bracketed rung is optional. What earns its place is the clause after the colon: the\n*mistake*, stated as something a person could do. \"Uniqueness constraint\" tells a future\nengineer nothing; \"rejects a second charge for the same key\" tells them what breaks if they\ndrop it.\n\n**If someone wants an index, generate it.** Never hand-maintain it:\n\n```bash\npython3 ../../scripts/device_registry.py --write docs/poka-yoke/registry.md\npython3 ../../scripts/device_registry.py --check   # CI: fails if stale\n```\n\nDelete a device and its row disappears; move it and the row follows. That is the difference\nbetween a record that is a device and a record that is a chore.\n\n## Staying useful\n\nThe failure mode of this audit is turning into a generic style review. Style findings, naming, formatting, structure, \"this could be more readable\", do not belong here unless the\nunreadability is itself the hazard. If you cannot name a specific wrong action a person could\ntake, it is not a poka-yoke finding, and including it dilutes the ones that are.\n\nRead `../../references/hazard-catalog.md` for the recurring hazard shapes and their standard\ndevices, and the matching `../../references/lang-*.md` for what the language can actually\nenforce.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}