@kenkaiiii/ggcoder 5.44.3 → 5.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +3 -1
  2. package/assets/skills/durable/SKILL.md +111 -0
  3. package/assets/skills/durable/references/backups-and-runtime.md +86 -0
  4. package/assets/skills/durable/references/migrations-and-schema.md +79 -0
  5. package/assets/skills/lean/SKILL.md +110 -0
  6. package/assets/skills/lean/references/memory-and-processes.md +97 -0
  7. package/assets/skills/lean/references/playbooks.md +107 -0
  8. package/dist/core/agent-session-verification-gate.test.d.ts +2 -0
  9. package/dist/core/agent-session-verification-gate.test.d.ts.map +1 -0
  10. package/dist/core/agent-session-verification-gate.test.js +135 -0
  11. package/dist/core/agent-session-verification-gate.test.js.map +1 -0
  12. package/dist/core/agent-session.d.ts +2 -0
  13. package/dist/core/agent-session.d.ts.map +1 -1
  14. package/dist/core/agent-session.js +41 -0
  15. package/dist/core/agent-session.js.map +1 -1
  16. package/dist/core/process-manager-wake.test.d.ts +2 -0
  17. package/dist/core/process-manager-wake.test.d.ts.map +1 -0
  18. package/dist/core/process-manager-wake.test.js +96 -0
  19. package/dist/core/process-manager-wake.test.js.map +1 -0
  20. package/dist/core/process-manager.d.ts +39 -1
  21. package/dist/core/process-manager.d.ts.map +1 -1
  22. package/dist/core/process-manager.js +146 -2
  23. package/dist/core/process-manager.js.map +1 -1
  24. package/dist/core/settings-manager.d.ts +1 -0
  25. package/dist/core/settings-manager.d.ts.map +1 -1
  26. package/dist/core/settings-manager.js +5 -0
  27. package/dist/core/settings-manager.js.map +1 -1
  28. package/dist/core/skills.js +1 -1
  29. package/dist/core/skills.js.map +1 -1
  30. package/dist/core/verification-gate.d.ts +55 -0
  31. package/dist/core/verification-gate.d.ts.map +1 -0
  32. package/dist/core/verification-gate.js +188 -0
  33. package/dist/core/verification-gate.js.map +1 -0
  34. package/dist/core/verification-gate.test.d.ts +2 -0
  35. package/dist/core/verification-gate.test.d.ts.map +1 -0
  36. package/dist/core/verification-gate.test.js +102 -0
  37. package/dist/core/verification-gate.test.js.map +1 -0
  38. package/dist/tools/bash.d.ts +4 -0
  39. package/dist/tools/bash.d.ts.map +1 -1
  40. package/dist/tools/bash.js +57 -3
  41. package/dist/tools/bash.js.map +1 -1
  42. package/dist/tools/bash.test.js +37 -0
  43. package/dist/tools/bash.test.js.map +1 -1
  44. package/dist/tools/read-only-bash.d.ts +6 -0
  45. package/dist/tools/read-only-bash.d.ts.map +1 -1
  46. package/dist/tools/read-only-bash.js +168 -9
  47. package/dist/tools/read-only-bash.js.map +1 -1
  48. package/dist/tools/read-only-bash.test.js +49 -0
  49. package/dist/tools/read-only-bash.test.js.map +1 -1
  50. package/dist/tools/skill.js +1 -0
  51. package/dist/tools/skill.js.map +1 -1
  52. package/dist/ui/App.d.ts +3 -0
  53. package/dist/ui/App.d.ts.map +1 -1
  54. package/dist/ui/App.js +35 -1
  55. package/dist/ui/App.js.map +1 -1
  56. package/package.json +4 -4
package/README.md CHANGED
@@ -204,13 +204,15 @@ Reusable behaviors across projects. Drop `.md` files in:
204
204
 
205
205
  They get loaded into the system prompt automatically. The agent knows what it can do without you explaining it each session. <kbd>Ctrl+S</kbd> opens a pane to browse and toggle them.
206
206
 
207
- Three ship built in, and route themselves when the work matches:
207
+ Five ship built in, and route themselves when the work matches:
208
208
 
209
209
  | Skill | Fires on |
210
210
  | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
211
211
  | `bulletproof` | Code an attacker will reach — auth, untrusted input, secrets, dependencies, CI/release, agent/MCP tool surfaces — and "is this safe to ship" reviews. Works on any target: web, API, CLI, desktop, mobile, embedded, contracts, ML. |
212
212
  | `compliance-guard` | Legal exposure — personal data, payments, UGC, email/SMS, minors, or a licensed/regulated feature. |
213
+ | `durable` | User data must not be lost — first database/table, migrations, backfills/imports, destructive operations, backups and recovery; any store (Postgres, MySQL, SQLite, Mongo, serverless). |
213
214
  | `evidence-led-ui` | Broad or design-sensitive UI work — new screens, redesigns, design systems, accessibility passes. |
215
+ | `lean` | Speed and resource efficiency — slow loading/startup, jank, high CPU, memory leaks and hogging, zombie/orphan processes, bundle bloat, dead code/styles, Core Web Vitals; while building anything that should stay fast, or a perf pass on an existing project. Any stack: web, backend, Electron, Tauri, mobile, native, game, ML. |
214
216
 
215
217
  ---
216
218
 
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: durable
3
+ description: Use when user data must not be lost or corrupted — creating the first database/table/schema, writing migrations, backfilling or importing data, any destructive operation (delete, drop, truncate, overwrite), setting up backups or recovery, or moving data between systems; and for "is my data safe", "will I lose my data", "back up my app" checks on existing projects. Any store — SQL (Postgres, MySQL, SQLite), document (Mongo, Firestore), serverless (Supabase, Neon, Turso), files, queues. Do NOT use for query speed or connection-pool sizing (that is lean), access control over data (that is bulletproof), or privacy/legal deletion regimes (that is compliance-guard).
4
+ license: Data-durability engineering guidance, not a DBA certification. Sources and snapshot date are recorded at the foot of each reference file.
5
+ compatibility: Snapshot dated 17 August 2026. Version behaviors (fast-path ALTERs, pooler modes, tool flags) decay — re-verify with web access before asserting them as current.
6
+ ---
7
+
8
+ # Durable
9
+
10
+ Make user data survive everything: bad migrations, crashed writes, retried webhooks, full disks, dead servers, and the 3am `DELETE` without a `WHERE`. Built for the reality that users forgive slow and ugly; they do not forgive gone.
11
+
12
+ **This skill is on from the first table.** The default mode is the inline gate below — every schema change, import, and destructive path gets the durable treatment as it is written. The full pass is for existing projects and "is my data safe" checks.
13
+
14
+ ## Governing rules
15
+
16
+ 1. **The database is the last line of defense, not the app.** Constraints, foreign keys, uniqueness, and NOT NULL live in the store where enforcement cannot be bypassed. App-level validation is UX, not integrity — a bug, a script, or a direct SQL session walks right past it.
17
+ 2. **Destructive operations are guilty until proven guarded.** Any `DROP`, `TRUNCATE`, `DELETE`, `UPDATE` without a `WHERE`, or overwrite of a column/file gets: a guard (`WHERE` + `LIMIT`), a dry-run count first, a backup or snapshot when anything of value exists, and an undo path (soft delete, staging table, or copy) for user-facing data.
18
+ 3. **Migrations are code that runs on data you cannot recreate.** Checked in from day one, reviewed as SQL before applying (ORM-generated SQL included — generators will happily emit `DROP COLUMN` for a rename), never edited once applied, forward-only in production. `db push`-style sync is for throwaway dev databases only.
19
+ 4. **One logical change, one transaction.** Multi-step writes either all land or none do. Anything a retry can hit twice (webhooks, queue jobs, imports, payment callbacks) is idempotent — a dedup key or upsert, not hope.
20
+ 5. **Backups you have not restored are fiction.** Automated, off-platform (or at least off-instance), on anything with real user data — and the restore is exercised, timed, and recorded. RPO (how much loss is acceptable) and RTO (how long recovery takes) are stated numbers, not vibes.
21
+ 6. **Fail loudly, not corruptly.** Partial imports, half-applied backfills, and crashed jobs leave the system in a state the next run can detect and resume — keyset-resumable batches, recorded checkpoints, no silent skips.
22
+ 7. **Respect the writer.** SQLite has one writer; Postgres connections are processes; serverless poolers multiplex transactions and break session state. Designing against the store's real concurrency model is durability work, not just performance work.
23
+ 8. **Numbers or silence.** A backup claim without a timed restore run is unverified. Label every claim `RUNTIME` (observed), `CODE` (read in source), `DEDUCED` (inferred), `SNAPSHOT` (dated source). Never claim data is "safe" — say what loss is survivable and what is not.
24
+ 9. **Proportionality.** A prototype with test rows needs migrations and little else. The first real user row raises the floor: backups, then tested restore, then PITR-class recovery as the product matters.
25
+
26
+ ## Two modes
27
+
28
+ **Inline gate** — while writing anything that touches stored data: the first table, a schema change, a backfill or import script, a delete/edit endpoint, a webhook that writes, a backup cron. Apply the binding defaults below, say one line about the guard you built, move on. Do not stop the build to lecture, and do not ship the unguarded version intending to "add safety later".
29
+
30
+ **Full pass** — triggered by "is my data safe", "will I lose my data if X", "back up my app", a migration about to run on production, or after any data scare. Run the workflow below. Migration and schema detail lives in `references/migrations-and-schema.md`; backups, recovery, and runtime data-safety detail lives in `references/backups-and-runtime.md`.
31
+
32
+ ## Binding defaults (build mode)
33
+
34
+ Apply on every data-touching change, every store:
35
+
36
+ - **Migration tooling from the first table** — checked-in versioned migrations, generated with `--create-only`-style review when the ORM supports it, reviewed as SQL, applied via the tool's deploy path. Never hand-edit an applied migration; write a new one that corrects it.
37
+ - **Destructive ops carry their guard** — `WHERE` + `LIMIT` on mass changes, count-first dry run (`SELECT` the affected rows before `DELETE`/`UPDATE`), and for user-visible data prefer soft delete (`deleted_at`) with partial unique indexes over hard delete until retention policy says otherwise.
38
+ - **Constraints in the store** — `NOT NULL` on required fields, `UNIQUE` where identity lives (email, external IDs), foreign keys with explicit `ON DELETE` behavior chosen (not defaulted), and `CHECK` where a value has a domain. Orphaned rows and duplicate emails are app bugs the DB should have refused.
39
+ - **Transactions around multi-write invariants** — wrap create-order-plus-items, transfer-out-plus-transfer-in, and every read-modify-write that must not interleave. Where the store lacks multi-document transactions, the default is a single-document design or an outbox pattern, never "should be fine".
40
+ - **Idempotency keys on retried writes** — webhook event IDs, job dedup keys, `INSERT ... ON CONFLICT` upserts. Rule of thumb: if it can run twice, assume it will.
41
+ - **Batched, resumable bulk work** — keyset pagination (`WHERE id > last`), fixed batch size, sleep between batches, checkpoint recorded so a crash resumes rather than restarts or double-applies. Never one unbounded `UPDATE` over a production table.
42
+ - **Backups the moment real data exists** — automated (managed-provider backups, `pg_dump` cron, Litestream for SQLite, scheduled snapshots for document stores), retention of days not one copy, at least one copy off the same machine/account. State RPO/RTO in a comment where the backup is configured.
43
+ - **Connection and session hygiene** — close/release connections in `finally`; on serverless, assume transaction-mode pooling (no session state, no prepared statements unless the pooler supports them, no `LISTEN/NOTIFY`, no session advisory locks); one pool per function instance, not per request.
44
+ - **SQLite as SQLite** — WAL mode on, `busy_timeout` set, one writer (route writes through a single instance or a queue), database on local disk not network storage, Litestream-or-scheduled-backup for continuous protection. Do not pretend it is a client-server DB.
45
+
46
+ ## Full-pass workflow
47
+
48
+ ### 1. Profile the data from the code
49
+
50
+ Before asking anything: store type(s) and version, where data files live, ORM/migration tooling present or absent, what writes exist (endpoints, jobs, webhooks, imports), what deletes exist, whether backups are configured anywhere (deploy config, cron, provider settings), and — decisive — whether real user data exists. A repo with seed scripts only is a different engagement than one with a production URL.
51
+
52
+ ### 2. Establish what loss would mean
53
+
54
+ From the code, answer: what is recreated (cache, derived data), what is user-entered and unrecoverable (posts, uploads, messages, payments), and what links out (files on disk referenced by rows, rows referencing deleted files). The unrecoverable set defines backup urgency; the links define cleanup discipline.
55
+
56
+ ### 3. Sweep the six areas
57
+
58
+ In order of how often each actually loses data. Detection specifics and commands: the two reference files.
59
+
60
+ | # | Area | What you are hunting |
61
+ |---|---|---|
62
+ | 1 | **Backups & recovery** | No backups at all; backups only on the same machine/account; no retention; never-restored backups (the norm); no stated RPO/RTO; single copy of file uploads; managed backups assumed but not enabled |
63
+ | 2 | **Destructive paths** | `DELETE`/`UPDATE` without `WHERE` or `LIMIT`; cascade deletes that sweep further than intended (user → everything they own, intended or not); truncate/drop in scripts; no undo for user-facing deletes; `db push` or sync-style schema changes anywhere near production config |
64
+ | 3 | **Migrations health** | No migration tooling (schema by hand/script); applied migrations edited; pending destructive migration; generated SQL never reviewed; migrations untested against prod-shaped data; drift between schema files and the live DB |
65
+ | 4 | **Transactions & idempotency** | Multi-step writes without a transaction; webhook/job handlers that double-apply on retry; check-then-act races (read, decide, write without a constraint); imports that restart from zero |
66
+ | 5 | **Schema integrity** | Foreign keys absent or off (MySQL engines, SQLite `PRAGMA foreign_keys`); duplicate-prone columns without unique constraints; orphaned rows; `NOT NULL`-in-spirit columns that are nullable in fact; money/IDs stored in lossy types (float money, int IDs near overflow) |
67
+ | 6 | **Runtime data safety** | Transaction-pooled connections using session features; SQLite without WAL/busy_timeout or with concurrent writers across instances; files written non-atomically (no temp-then-rename); jobs that mutate state with no record of having run; queues with no dead-letter path |
68
+
69
+ ### 4. Rank by survivability
70
+
71
+ | Severity | Meaning |
72
+ |---|---|
73
+ | **Critical** | Loss is certain or one common failure away: real user data with no backups; destructive path unguarded; pending migration that drops data; double-charge/double-apply on retry |
74
+ | **High** | Loss on a plausible bad day: backups exist but never restored; single copy on one machine/account; cascade deletes broader than intended; multi-write flows without transactions |
75
+ | **Medium** | Fragility that bites at scale or during recovery: missing constraints, schema drift, non-idempotent jobs, non-atomic file writes |
76
+ | **Low** | Hygiene: naming, unused staging tables, comments. Do these when adjacent to a real fix. |
77
+
78
+ Fix Critical and High first; three to five fixes, each verified. A backup you set up is finished only when a restore from it has run and been timed.
79
+
80
+ ### 5. Verify
81
+
82
+ - **Restore drill for any backup fix**: snapshot state, note the time, make a recognizable change, restore to the noted time into a separate location, verify the change is absent, record the wall-clock duration — that number is the real RTO. `RUNTIME` label or it did not happen.
83
+ - **Guard drill for any destructive fix**: run the guarded path against a copy with a row that must survive and a row that must not; assert both outcomes.
84
+ - **Migration drill**: apply the pending migrations to a copy of production-shaped data (a fresh dump, a seeded volume) before it goes near the real thing.
85
+ - Label what you could not run — no environment, no data copy, managed console you cannot touch — as unverified, and say the exact command the user should run.
86
+
87
+ ### 6. Leave a guard behind
88
+
89
+ A tested restore cron'd into a weekly job; a CI step that applies migrations to a throwaway DB before merge; a test that a retried webhook applies once; a constraint added to the store; `--create-only` review in the project's migration docs. One mechanical check beats a README paragraph.
90
+
91
+ ### 7. Report
92
+
93
+ - Lead with the survivability statement in plain words: "if this server dies right now, you lose everything after [backup point]" — the RPO the user actually has, not the one they think they have.
94
+ - Then findings ranked, each with file/line and the fix.
95
+ - Then what was **not checked** — stores skipped, consoles inaccessible, uploads unexamined.
96
+ - Then what you fixed (with verification labels) vs. what needs the user (provider settings, paid tiers, their call on retention).
97
+ - Label every claim `RUNTIME` / `CODE` / `DEDUCED` / `SNAPSHOT`.
98
+
99
+ ## Honesty rules
100
+
101
+ - Never state or imply data is "safe" or "backed up" without an observed, timed restore. "Backups are configured" is a `CODE` claim; "recoverable" requires a `RUNTIME` one.
102
+ - Never present a retention number, provider tier, or version behavior as current without verifying — provider backup defaults change; mark the snapshot date.
103
+ - Never run a destructive command, however obviously safe, against a database with real data without an explicit backup or the user's go-ahead.
104
+ - "I could not verify this" is a legitimate output. A fabricated restore test is the worst lie this skill could tell.
105
+
106
+ ## Reference map
107
+
108
+ Resolve every path from the installed skill root. Load only what the profile triggered.
109
+
110
+ - `references/migrations-and-schema.md` — the migration discipline: expand-contract with concrete lock behavior, batched resumable backfills, index and FK lock-safety, ORM-specific traps (Prisma, Drizzle, and friends), forward-only production, CI and deploy-time application, and schema integrity checks. Read for any migration or schema finding.
111
+ - `references/backups-and-runtime.md` — backup tiers and the restore drill, RPO/RTO, 2026 managed-provider baselines (Supabase, Neon, RDS, Crunchy), self-hosted tooling (pgBackRest, WAL-G, Litestream, restic), file-upload protection, idempotency and outbox patterns, pooling and serverless session-state pitfalls, SQLite runtime rules, and atomic file writes. Read for any backup, recovery, or runtime finding.
@@ -0,0 +1,86 @@
1
+ # Durable — backups, recovery & runtime data safety
2
+
3
+ Load for any backup, recovery, or runtime finding. `SNAPSHOT` = sourced 17 August 2026 — provider tiers and defaults change often; verify before asserting.
4
+
5
+ ## Part 1 — Backups & recovery
6
+
7
+ ### RPO/RTO first
8
+
9
+ - **RPO** (Recovery Point Objective): how much data loss is acceptable — "5 minutes" vs "a day".
10
+ - **RTO** (Recovery Time Objective): how long until you are back up.
11
+
12
+ Pick both consciously; they dictate the tier. An app where users type for hours wants an RPO near zero; a read-mostly catalog may accept a day. Stating them where the backup is configured turns "we have backups" into an actual contract.
13
+
14
+ ### The tiers, best to weakest
15
+
16
+ 1. **PITR (point-in-time recovery)** — base backup + continuous write-ahead-log archive; restore to any second in the retention window. RPO of seconds-to-minutes. Gold standard for transactional data.
17
+ 2. **Scheduled logical dumps** (`pg_dump`, `mongodump`, Firestore export) — portable, cheap, easy to verify by inspection; but restore time scales with size and everything after the dump starts is lost. Fine as the archive layer or the only layer for small apps.
18
+ 3. **Storage/block snapshots** — fast, near-zero impact; restoring a running DB from a raw snapshot leaves crash recovery to do the rest. Good for staging clones; for disaster recovery prefer PITR.
19
+ 4. **Nothing / a copy on the same machine** — the default state of most small projects, and the finding most often reported after it stops mattering.
20
+
21
+ **3-2-1 floor**: at least 3 copies, 2 different media/systems, 1 off-site (different provider or account is fine). A dump cron writing to the same VPS is one disk failure from zero.
22
+
23
+ ### Managed-provider baselines (`SNAPSHOT` 17 Aug 2026 — verify tiers/retention before quoting)
24
+
25
+ | Provider | What you get by default/on paid tiers |
26
+ |---|---|
27
+ | Supabase | Pro: 7-day PITR included; daily logical backups; restore lands a new project |
28
+ | Neon | Continuous WAL archive; PITR up to 30 days on higher tiers; branching doubles as time-travel |
29
+ | AWS RDS | Automated backups 1–35 days (PITR); manual snapshots on demand |
30
+ | Crunchy Bridge | 14-day PITR by default; longer via S3 archive |
31
+ | MongoDB Atlas | Continuous backup / cloud snapshots by tier |
32
+
33
+ The recurring failure: the free tier's weekly backup or none at all, assumed to be PITR because the marketing page said "backups". Check the project's actual settings, not the provider's homepage.
34
+
35
+ **Self-hosted Postgres**: pgBackRest, Barman, or WAL-G → S3-compatible storage. **Self-hosted/embedded SQLite**: Litestream (continuous WAL replication to object storage, near-zero RPO) or restic/borg on a schedule as the weaker floor. **Firestore/DynamoDB-style**: scheduled exports to storage — PITR is a paid or absent feature; check the project's state.
36
+
37
+ ### The restore drill (the only proof)
38
+
39
+ 1. Note the current time / snapshot point of a known state.
40
+ 2. Make a recognizable change (insert a canary row).
41
+ 3. Restore to the noted point into a *separate* location — never over the live DB.
42
+ 4. Verify the canary is absent. Time the whole operation — that measured duration is the real RTO; write it down.
43
+ 5. Repeat on a schedule (quarterly is the common bar); the drill doc itself is the runbook you'll follow at 3am.
44
+
45
+ **File uploads need their own answer** — DB backups don't cover a disk of user uploads unless the backup includes the volume or the uploads live in object storage with versioning (S3 versioning or equivalent preserves deleted/overwritten objects — turn it on and state the retention). DB row + orphaned-file mismatch is a finding: cleanup discipline (delete file then row, in that order, with the row's file path recorded for resweep) or accept orphans.
46
+
47
+ ### What a backup must exclude/include
48
+
49
+ Include: the data, schema history (migrations), and anything unrecreatable (uploads, generated-but-expensive artifacts). Exclude/rotate: secrets in plaintext dumps (a dump with PII inherits compliance-guard's storage rules — encrypt at rest, restrict access), logs, caches. Test that the restore includes what you think: a backup that skips a table because of a wrong flag is the most humiliating restore failure.
50
+
51
+ ## Part 2 — Runtime data safety
52
+
53
+ ### Idempotency & exactly-once writes
54
+
55
+ Anything retried — webhooks, queue jobs, mobile clients on flaky networks, imports — **will** run twice. Patterns, in order of preference:
56
+
57
+ - **Store-side dedup**: unique constraint on the natural key (webhook event ID, job ID + attempt) and `INSERT ... ON CONFLICT DO NOTHING` returning whether it inserted. The store is the arbiter; no race can beat it.
58
+ - **Upsert by natural key**: `ON CONFLICT ... DO UPDATE` with a deterministic outcome, so replay converges instead of duplicating.
59
+ - **Compare-and-set / optimistic concurrency**: `UPDATE ... WHERE version = :expected`, check affected count — the guard for read-modify-write races (balance updates, seat claims, counter increments).
60
+ - **Outbox pattern**: state changes and the events they trigger written in one transaction to an outbox table, published by a separate relay — eliminates "DB updated but email/queue lost" (and its evil twin, "email sent but DB rolled back"). The default answer where a store lacks cross-service transactions.
61
+
62
+ Check-then-act without a constraint (`if not exists: insert`) is a bug that just hasn't raced yet.
63
+
64
+ ### Connection & session pitfalls (correctness, not speed)
65
+
66
+ - **Transaction-mode poolers** (PgBouncer/Supavisor, Neon pooler, RDS Proxy defaults): each transaction may run on a different connection — session state breaks. Casualties: session-level `SET`/`prepared statements` (named ones), advisory locks, `LISTEN/NOTIFY`, temp tables, `COPY`. Patterns: keep per-transaction state in SQL (`SET LOCAL`), use `pg_advisory_xact_lock` (transaction-scoped), or route state-needing work to a direct/session connection.
67
+ - **Serverless functions**: one pool per *instance* (module scope), never per request; assume the process freezes between invocations — no in-memory "it'll flush later".
68
+ - **Postgres connections are processes** — exhausting them fails every new client; the fix is a pooler, not a bigger `max_connections`. (Sizing the pool for throughput is lean's lane.)
69
+ - **Always release/close in `finally`** — a leaked connection per request is a slow outage and a durability finding.
70
+
71
+ ### SQLite runtime rules
72
+
73
+ - **WAL mode on** (`PRAGMA journal_mode=WAL`) — readers don't block the writer; the default rollback journal serializes everything.
74
+ - **`busy_timeout` set** (e.g. 5000ms) — without it, concurrent access returns `SQLITE_BUSY` instantly instead of waiting.
75
+ - **`PRAGMA foreign_keys = ON` on every connection** — it is per-connection and off by default; constraints silently unenforced is a schema-integrity finding wearing runtime clothes.
76
+ - **One writer** — route writes through a single instance or a write queue; multiple app instances writing one SQLite file on shared/network storage corrupts. Local disk (NVMe), not NFS.
77
+ - **Backup without Litestream**: `VACUUM INTO 'backup.db'` or the `sqlite3 .backup` command — never copy the file mid-write; the WAL file and the checkpoint state are part of the database.
78
+
79
+ ### Atomic file writes and cleanup
80
+
81
+ - **Temp-then-rename** for any file a reader may open: write to `path.tmp`, `fsync`, then `rename` over the target. Atomic on POSIX; a crash mid-write never leaves a truncated JSON the app will happily parse.
82
+ - **Delete in dependency order and resweep**: child rows then parent; file after the row referencing it is gone (or record orphans for a sweep job). Reversible orderings first: soft-delete the row, mark the file, sweep files later — undoability beats tidiness.
83
+
84
+ ---
85
+
86
+ **Provenance:** snapshot 17 August 2026. Sources: Postgres WAL/PITR documentation and pgBackRest/Barman/WAL-G docs, Litestream documentation (SQLite WAL replication), provider documentation for Supabase/Neon/RDS/Crunchy/MongoDB Atlas backup tiers (tier specifics are `SNAPSHOT` — they change often), PgBouncer documentation (transaction-mode feature matrix), SQLite documentation (WAL, busy_timeout, foreign_keys pragma, VACUUM INTO), current disaster-recovery practice guides (3-2-1, RPO/RTO, restore drills). Provider tiers and defaults decay fastest — re-verify before asserting.
@@ -0,0 +1,79 @@
1
+ # Durable — migrations & schema
2
+
3
+ Load for any migration or schema finding. Postgres examples dominate because it is the default; MySQL and SQLite divergences are called out. `SNAPSHOT` = sourced 17 August 2026 — version behaviors decay, verify before asserting.
4
+
5
+ ## The one rule that prevents most downtime
6
+
7
+ **Never deploy a schema change and the code that depends on it in the same step.** Old code must keep working against the new schema, and new code must keep working against the old schema, for at least one deploy cycle. That is what expand-contract buys.
8
+
9
+ ## Expand-contract (parallel change)
10
+
11
+ Every breaking change is two or three separately-deployed steps, each leaving the system fully functional:
12
+
13
+ 1. **Expand** — add the new column/table/index (nullable, no constraints yet). Old code ignores it; nothing breaks.
14
+ 2. **Backfill + dual-write** — copy old data to new in batches; code writes both; code reads new with fallback to old. Deploy, watch, wait.
15
+ 3. **Contract** — after the old path is provably dead (feature flag flipped, traffic at zero), remove the old column and the dual-write.
16
+
17
+ Rollback at any point is "flip back to the old path", not "restore the database".
18
+
19
+ **Rename a column** — never `RENAME COLUMN` on a live system (breaks all in-flight code): add `email`, backfill from `email_addr`, dual-write, switch reads, drop `email_addr` in a later deploy.
20
+
21
+ **Change a type or split a column** — add the new column, backfill with transformation in batches, cut writes over, drop old. Same shape, always.
22
+
23
+ ## Lock-safety table (Postgres)
24
+
25
+ What common DDL actually does to a live table (`SNAPSHOT` — verify per version):
26
+
27
+ | Operation | Behavior | Safe pattern |
28
+ |---|---|---|
29
+ | `ADD COLUMN` (no default) | Fast, brief lock | Fine as-is |
30
+ | `ADD COLUMN ... NOT NULL DEFAULT x` | Fast since Postgres 11 (default not backfilled); table rewrite before 11 | Fine on ≥11; otherwise add nullable → backfill → `SET NOT NULL` |
31
+ | `SET NOT NULL` on existing column | Full-table scan under lock | Backfill first, then set; or add a `CHECK` constraint `NOT VALID` then `VALIDATE`, then switch |
32
+ | `CREATE INDEX` | Blocks writes for the whole build | `CREATE INDEX CONCURRENTLY` (drop with `DROP INDEX CONCURRENTLY`); slower, non-transactional — if it fails, drop the invalid index and retry |
33
+ | `ADD FOREIGN KEY` | Locks while validating all rows | Two-step: `ADD CONSTRAINT ... NOT VALID` then `VALIDATE CONSTRAINT` (weaker lock) |
34
+ | One giant `UPDATE`/`DELETE` | Locks rows, bloats the table, stalls replication | Batch: keyset-select N rows → update → sleep → repeat, resumable from last key |
35
+ | `DROP COLUMN` | Fast (metadata) — but data is gone | Only in the contract phase, after dual-write is verified dead |
36
+
37
+ MySQL has no `CONCURRENTLY`: use `ALGORITHM=INSTANT/INPLACE` where the version supports it, `gh-ost` or `pt-online-schema-change` for big tables (`SNAPSHOT` — both maintained; verify current). Postgres big-table rebuilds (PK change, deep bloat, partitioning): `pg_repack`, which rebuilds online with minimal locking and needs ~2x disk temporarily.
38
+
39
+ ## Resumable backfill skeleton
40
+
41
+ ```sql
42
+ -- keyset, not OFFSET: stable order, restartable from last processed id
43
+ UPDATE users
44
+ SET status = 'active'
45
+ WHERE id IN (
46
+ SELECT id FROM users WHERE id > :last_id AND status IS NULL
47
+ ORDER BY id LIMIT 5000
48
+ )
49
+ RETURNING id; -- record max(id) as the checkpoint; sleep between batches
50
+ ```
51
+
52
+ From application code the same shape applies: select batch by `id > last`, write, record checkpoint durably (a checkpoint table or job state), sleep. A crashed backfill resumes at the checkpoint instead of restarting or double-writing. On stores without `RETURNING`, select the batch first, update by primary key, checkpoint the max selected id.
53
+
54
+ ## Migration tooling discipline
55
+
56
+ - **Versioned, checked-in migrations from the first table** — Alembic (Python), Flyway/Liquibase (JVM), golang-migrate (Go), sqlx/Diesel (Rust), Drizzle Kit / Prisma Migrate (TS). Hand-run SQL files and "schema.sql we run sometimes" are how drift starts.
57
+ - **Review generated SQL before it touches anything real.** ORM migration generators emit what the schema diff implies: renaming a column in the schema file becomes `DROP COLUMN` + `ADD COLUMN` — the data is dropped. Prisma flow: `migrate dev --create-only`, read the SQL, fix it to a safe expand-contract, then apply. Drizzle: generate, then read the SQL before `migrate`. This review is the single highest-value habit in this file.
58
+ - **`db push`/sync-style commands are for throwaway dev databases only.** They bypass migration history; on a database with data they can apply destructive diffs without review. If a deploy script or CI contains `db push` against anything shared or persistent, that is a finding.
59
+ - **Never edit an applied migration.** The hash changes, history diverges, teammates' databases desync. Corrections are new migrations.
60
+ - **Forward-only in production.** Down migrations cannot faithfully reverse a migration that touched data (you cannot un-drop a column). "Rollback" is a new forward migration that reverses the change, written and tested like any other. Down migrations are a dev convenience at most.
61
+ - **Apply migrations as a distinct step before the new code rolls out** (deploy script step or pre-deploy Job), never lazily on first request, never concurrently from every replica. One applier, ordered, recorded.
62
+
63
+ ## CI and testing
64
+
65
+ - A CI job that applies all migrations to a throwaway database (a fresh dump or seed of production shape) on every PR — catches broken SQL and lock surprises before merge, and keeps prod-shaped test data honest.
66
+ - Migration + dependent code in one PR is fine; shipping them as one *deploy step* is not — expand and contract are separate deploys even when they merge as one review.
67
+
68
+ ## Schema integrity sweep
69
+
70
+ - **Foreign keys enforced?** MySQL: check the engine (InnoDB enforces, MyISAM does not). SQLite: `PRAGMA foreign_keys = ON` per connection — it is OFF by default and every connection must set it. Postgres: on by default; hunt instead for `ON DELETE` behavior nobody chose — cascade on a `users` delete that sweeps orders, messages, and uploads is a mass-delete path wearing a constraint's clothing.
71
+ - **Unique constraints where identity lives** — email, username, external IDs (`stripe_customer_id`), webhook event IDs. Without the constraint, every race produces a duplicate; with it, the race becomes a retryable error. Partial unique indexes for soft delete: `CREATE UNIQUE INDEX ... ON users(email) WHERE deleted_at IS NULL` — the live rows stay unique, the deleted ones don't collide.
72
+ - **Nullable-in-fact columns** — every column the code treats as required should be `NOT NULL`, or the store will accept what the code never imagined.
73
+ - **Lossy types** — money in floating point (use integer cents or `NUMERIC`), IDs in 32-bit ints near overflow (YouTube hit this), timestamps without timezone when the product is multi-timezone, enums-as-free-text where a `CHECK` or lookup table belongs.
74
+ - **Orphan check** — rows referencing deleted parents (FKs added late don't clean history): count them before adding the constraint, and expect `VALIDATE` to fail if history is dirty. Decide: clean, archive, or keep the constraint `NOT VALID` deliberately and document why.
75
+ - **Drift** — schema file vs live DB: most ORMs can diff (`prisma migrate diff`, `drizzle-kit check`). Drift on production means migrations were bypassed at some point — a process finding, not just a schema one.
76
+
77
+ ---
78
+
79
+ **Provenance:** snapshot 17 August 2026. Sources: Postgres documentation (DDL locking behavior, `CONCURRENTLY`, `NOT VALID`/`VALIDATE`, ADD COLUMN default fast-path), current zero-downtime migration practice guides (expand-contract/parallel-change, batched keyset backfill, forward-only production, pre-deploy application), Prisma/Drizzle documentation (create-only review workflow, `db push` scope), MySQL online-schema-change tooling (gh-ost, pt-online-schema-change) public docs. Version-specific lock behavior decays fastest — re-verify against the running version before asserting.
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: lean
3
+ description: Use when speed or resource efficiency matters — slow loading or startup, janky interaction, high CPU, memory leaks or RAM that grows over time, zombie/orphan processes, bundle bloat, dead code and dead styles, Core Web Vitals; while building anything that should stay fast and light, running a performance pass over an existing project, or pre-ship "will this run smoothly" checks. Any stack — web, backend/API/CLI, desktop (Electron, Tauri), mobile, native, game, ML pipeline. Do NOT use for copy/docs-only changes, design-direction or aesthetic work (that is evidence-led-ui; this skill's styling scope is payload and consistency), or when the user explicitly deprioritizes performance.
4
+ license: Performance engineering guidance, not a benchmark certification. Sources and snapshot date are recorded at the foot of each reference file.
5
+ compatibility: Snapshot dated 17 August 2026. Thresholds, tool names, and defaults decay — re-verify with web access before asserting them as current. Claims sourced to that date carry a SNAPSHOT marker.
6
+ ---
7
+
8
+ # Lean
9
+
10
+ Make software fast, light, and smooth — loads quick, responds instantly, holds memory flat, leaves nothing running behind it. Built for the reality that users rarely ask for performance: they ask for a feature, then quietly leave when it eats RAM or takes five seconds to open.
11
+
12
+ **This skill is on from the first line of code.** The default mode is the inline gate below — build the lean version while writing the feature, in the main thread. The full pass is for existing projects and pre-ship checks.
13
+
14
+ ## Governing rules
15
+
16
+ 1. **Measure, then cut.** In a pass, never optimize from vibes: baseline, find the bottleneck, fix it, re-measure. Guessing produces premature optimization — complexity without user-visible gain. In build mode the binding defaults below are pre-paid by platform evidence: apply them without benchmarking.
17
+ 2. **Fix the shared cause once.** The N+1 belongs in the query layer, not a memo at each call site. Check every caller of the slow path before patching where it hurt.
18
+ 3. **Optimize user time, not machine time.** What users wait on: startup, first paint, navigation, hot interactions, the nightly job. Micro-tuning code nobody waits for is last, always.
19
+ 4. **Memory should be flat.** After N cycles of the core loop, committed memory should look like after 1. Sustained growth is a leak until proven otherwise; a sawtooth that returns to baseline is GC, not a leak.
20
+ 5. **Nothing outlives its job.** Timers, listeners, observers, subscriptions, watchers, child processes, temp files, locks — everything with a lifetime needs an owner that ends it, on the success path *and* every failure path.
21
+ 6. **Bounded by default.** If it can grow — cache, queue, buffer, retry, log, list render — it gets a cap and an eviction policy at creation, not after the incident.
22
+ 7. **Small is fast.** Dead code, unused dependencies, and duplicate styles are parsed, shipped, and paid for. Removing is the cheapest optimization there is.
23
+ 8. **Numbers or silence.** Verify with before/after on the same machine and data, cold and warm. "Feels faster" is not a result, and never claim "optimized", "leak-free", or "fast" — say what moved, from X to Y, and what you could not measure.
24
+ 9. **No perf theater.** Complexity must pay for itself in measured user time; otherwise revert. Caching that introduces staleness bugs for an unmeasured gain is a regression wearing a costume.
25
+ 10. **Label evidence** on every claim: `RUNTIME` (you measured it), `CODE` (you read it), `DEDUCED` (inferred), `SNAPSHOT` (dated source claim). Never present what you read as what you ran.
26
+
27
+ ## Two modes
28
+
29
+ **Inline gate** — while writing any feature, apply the binding defaults below. One line in the reply about what you did and why it matters, then move on. Do not stop the build to lecture, and do not ship the heavy version intending to "optimize later" — later never comes.
30
+
31
+ **Full pass** — triggered by "make it faster", "why is it slow / eating RAM / lagging", "will this run smoothly", a suspected regression, or a pre-ship check. Run the workflow below. Per-stack sweeps and measurement commands live in `references/playbooks.md`; the memory-leak catalog and process/zombie lifecycle detail live in `references/memory-and-processes.md`.
32
+
33
+ ## Binding defaults (build mode)
34
+
35
+ Apply on every feature, every stack. These are the habits that prevent the pass from ever being needed:
36
+
37
+ - **Teardown ships with the feature.** Whatever you start — timer, listener, observer, subscription, child process, watcher — is torn down in the same module that started it, on success and error paths. Prefer one cleanup handle per owner (an `AbortController` for all of a component's fetches and listeners; a destructor/dispose method; a `finally` block) over scattered manual removes.
38
+ - **Cap every accumulation.** LRU/TTL cache, bounded queue, paginated query, virtualized list, capped retries with backoff, rotating logs. Unbounded is a bug with a delay.
39
+ - **Never block the interactive thread.** Chunk, defer, or offload work that can exceed a frame (web main thread long task threshold: 50ms) — web workers, background threads, task queues, utility processes. I/O stays async on the hot path; no sync fs/crypto/CPU spikes inside request handlers or UI code.
40
+ - **Lazy by default, eager only for the first screen.** Below-the-fold media, rare routes, heavy editors, optional services: load on demand (dynamic `import()`, deferred `require`, on-demand plugin init). Preload/preconnect only what the critical path demonstrably needs.
41
+ - **Right-size media.** AVIF/WebP with JPEG fallback, explicit `width`/`height` (also kills layout shift), `srcset`/`sizes` for density, decode thumbnails instead of full images.
42
+ - **Stream, don't hoard.** Stream files and large responses, paginate/cursor DB queries, chunk large jobs. Loading a whole dataset into memory to process it item by item is the classic hog.
43
+ - **Timeout everything external.** Network calls, subprocesses, locks, queues — no unbounded waits, and teardown on timeout.
44
+ - **Price a dependency before adopting it** in anything user-facing: its load cost is your load cost (`node --cpu-prof --heap-prof -e "require('mod')"` for Node-side; bundle impact for client-side). The most-downloaded module is not the lightest.
45
+ - **Batch I/O and reads-then-writes.** One query for N rows, not N queries; group DOM reads before writes; coalesce events (debounce/throttle) when handlers are expensive.
46
+
47
+ ## Full-pass workflow
48
+
49
+ ### 1. Profile the target from the code
50
+
51
+ Before asking the user anything: shape (web app, API, CLI, desktop, mobile, library, game, ML pipeline — read manifests, lockfiles, build and CI configs), the surfaces users wait on (startup, first screen, navigation, hot interactions, batch jobs), scale (data sizes, traffic, session length), and platform constraints (low-end devices matter more than the dev machine).
52
+
53
+ ### 2. Baseline, or say you cannot
54
+
55
+ Runtime numbers when the project runs: load time, startup time, p50/p99 latency, bundle sizes, RSS/heap at rest and after repeated cycles. Exact commands per stack are in `references/playbooks.md`. If you cannot run it (no environment, no data, no time), do a `CODE`-labeled static pass and say plainly in the report that nothing was measured — do not fabricate a baseline.
56
+
57
+ ### 3. Sweep the six areas
58
+
59
+ Work this list in order — it reflects where user-perceived damage concentrates, not what is most interesting to engineer. Detection specifics per stack: `references/playbooks.md`; areas 3–4: `references/memory-and-processes.md`.
60
+
61
+ | # | Area | What you are hunting |
62
+ |---|---|---|
63
+ | 1 | **Loading & startup** | Slow first paint/open, render-blocking resources, giant bundles, eager imports of rarely-used code, waterfall requests, unoptimized media and fonts, cold-start work that could be deferred |
64
+ | 2 | **Runtime responsiveness** | Long tasks blocking input, layout thrash, expensive re-renders, N+1 queries, sync work on hot paths, missing pagination, unvirtualized long lists, GC pressure from allocation churn |
65
+ | 3 | **Memory** | Growth over time (leaks): forgotten timers/listeners/observers, detached DOM held alive, unbounded caches and maps, closures capturing large scope, cycles in non-GC runtimes, undecoded-or-full-size media, whole-file loads where streaming would do |
66
+ | 4 | **Processes & lifecycle** | Zombie children (spawned, never reaped), orphans surviving parent exit (killed the node, not the tree), missing signal handling and graceful shutdown, leaked ports/fds/locks/temp files, no startup sweep for crash leftovers |
67
+ | 5 | **Payload & dead weight** | Unused dependencies, dead code and dead exports, dead/duplicate CSS, two libraries doing one job (two icon sets, two date libs, two CSS systems), ship-weight of debug/symbol payloads, tree-shaking blockers |
68
+ | 6 | **Styling consistency** | Same visual thing built three ways — repeated rule blocks, one-off spacing/color literals that duplicate tokens, framework utilities fighting handwritten CSS. Consistency here is payload: one way to do X is the only way that stays small. (Design direction itself is evidence-led-ui's call.) |
69
+
70
+ ### 4. Rank by user impact, fix the top, not all
71
+
72
+ | Severity | Meaning |
73
+ |---|---|
74
+ | **Critical** | Blocks or breaks: startup counted in tens of seconds, OOM crashes, multi-second UI freezes, a leak that kills a session in minutes |
75
+ | **High** | User-perceivable degradation: sluggish interactions, fan-spinning idle CPU, RAM that climbs over a workday, zombie processes accumulating across runs, slowest paths 2–10× slower than they should be |
76
+ | **Medium** | Waste with symptoms at scale: bundle bloat, N+1 under load, unbounded caches, missing pagination, duplicate dependencies |
77
+ | **Low** | Hygiene without a measured symptom: dead code, dead styles, micro-tuning. Do these when adjacent to a real fix, never instead of one. |
78
+
79
+ Fix the few that measurement actually implicates — three to five, rarely more. Each fix records its baseline number first. Prefer removing work over hiding it (delete the eager import beats code-splitting it beats deferring it), and prefer the platform primitive over a hand-rolled one.
80
+
81
+ ### 5. Verify the fix
82
+
83
+ Re-measure exactly how you baselined, same machine, same data, cold and warm. A fix you did not measure is a hypothesis. For memory: cycle the changed path many times, force GC where the runtime allows (`--expose-gc`, DevTools), and compare committed memory — flat wins, sawtooth is fine, climbing is not fixed. If the number did not move, revert and say so; a change that adds complexity without moving the number is a regression.
84
+
85
+ ### 6. Leave a guard behind
86
+
87
+ One check so it cannot silently return: bundle-size budget in CI, Lighthouse CI or `size-limit` for web, a slow-query threshold, a repeated-cycle memory assertion where feasible, a test that fails if the eager import returns. One gate beats good intentions.
88
+
89
+ ### 7. Report
90
+
91
+ - Lead with numbers: what moved, X → Y, on which machine/data/date.
92
+ - Then remaining findings, ranked, each with file/line and the fix.
93
+ - Then what was **not checked** — unmeasured areas, stacks skipped, environments unavailable. A report that silently skips the mobile client or the batch job reads as complete coverage.
94
+ - Then what you changed vs. what you recommend and did not do.
95
+ - Label every claim `RUNTIME` / `CODE` / `DEDUCED` / `SNAPSHOT`. Include your false positives — candidates you investigated and dropped — so the survivor list is not hiding its noise.
96
+
97
+ ## Honesty rules
98
+
99
+ - Never state or imply the software is "fast", "optimized", or "leak-free". State what was measured, fixed, and left unchecked.
100
+ - Never present a lint rule, bundle analyzer, or profiler as proof of the absence of problems; tools find a minority of what matters.
101
+ - Never invent a number, threshold, or version. If the claim is date-sensitive and unverified, mark it `SNAPSHOT` or say "verify this".
102
+ - "I could not measure this" is a legitimate and useful output. A fabricated confirmation is not.
103
+ - If the project is a prototype with no users and no load, say that proportionality applies — five real fixes beat forty ignored ones — and stop early.
104
+
105
+ ## Reference map
106
+
107
+ Resolve every path from the installed skill root. Load only what the profile triggered.
108
+
109
+ - `references/playbooks.md` — per-stack sweeps and measurement commands: web frontend (Core Web Vitals with official thresholds), Node/backend/API, Electron, Tauri, mobile, native/compiled (Rust, C/C++, Go, JVM), Python, data & queries, and the payload/dead-weight/styling-consistency tooling. Read the sections the profile triggered.
110
+ - `references/memory-and-processes.md` — the leak catalog by pattern, detection technique per runtime (three-snapshot, allocation timeline, heap dumps, pprof, valgrind, Instruments, LeakCanary), component-framework leak rules, and the process lifecycle playbook: zombies, orphans, signal handling, process-tree kills, container PID 1, startup sweeps, leaked fds/ports/temp files. Read for any memory or process finding.
@@ -0,0 +1,97 @@
1
+ # Lean — memory & processes
2
+
3
+ The deep reference for two of the six sweep areas: memory (leaks and hogging) and process lifecycle (zombies, orphans, shutdown). Read for any finding in those areas.
4
+
5
+ ## Part 1 — Memory
6
+
7
+ ### The leak taxonomy
8
+
9
+ Every memory leak is one of a small number of shapes. Name the shape before hunting the instance:
10
+
11
+ 1. **Forgotten teardown** — a timer, listener, observer, subscription, or callback registered against something long-lived (window, document, a global emitter, an event bus, a message broker) and never removed. The registration itself keeps the owner object — and everything it captures — alive.
12
+ 2. **Detached-but-referenced** — DOM nodes (or view/widget objects) removed from the document but still held by a closure, array, cache, or map. The GC cannot free what a live reference pins; "removed from the screen" is not "freed".
13
+ 3. **Unbounded accumulation** — a cache, map, array, or queue that only grows. Not a leak by accident — a design that forgot limits. Includes "in-memory session store" with no eviction and metrics maps keyed by unbounded cardinality (per-request keys are the classic).
14
+ 4. **Captured scope** — a long-lived closure captures a large object it doesn't need (a handler that uses one field but closes over the whole request/state). Also: promises that never settle, pinning everything their closures captured.
15
+ 5. **Cycles in non-GC runtimes** — Rust `Rc`/`Arc` cycles, C++ shared_ptr loops, Objective-C retain cycles (delegate + strong reference both ways). The reference count never reaches zero. Break the cycle with `Weak` on one edge.
16
+ 6. **Resource handles** — file descriptors, sockets, database connections, locks, temp files. Not heap, but the same symptom ("it dies after running a while") and the same rule: every acquisition has an owner that releases it on all paths.
17
+
18
+ ### Hogging is not leaking
19
+
20
+ Flat-but-too-big is the other half. Hunt: whole files/datasets loaded to process item-by-item (stream instead); images decoded at full resolution for thumbnail-sized display (decode down, cache the small one); the same large data duplicated across processes/IPC boundaries (send references or deltas); logs and metrics buffered in RAM without a cap; caches sized by hope instead of measurement ("it'll fit"). In containers, set memory limits (cgroups, `--memory`, K8s requests/limits) so hogging surfaces as a chosen error instead of silent neighbor-starvation.
21
+
22
+ ### Detection technique
23
+
24
+ **The universal method:** run the suspected loop many times (open/close the view, iterate the endpoint, replay the job), force GC where possible, and compare committed memory between cycles. Flat across cycles = no leak. Sawtooth returning to baseline = GC, not a leak. Climbing staircase = leak. Sample `process.memoryUsage()` (Node), RSS via `ps`/`top`, or the runtime's counter, every N cycles, and log the series — a leak you can graph is a leak you can bisect. Change one thing between runs.
25
+
26
+ **JavaScript/TypeScript (browser and Node):**
27
+
28
+ - **Three-snapshot technique** (Chrome DevTools Memory): snapshot → perform the suspected leaky action several times → snapshot → force GC, act again, force GC → snapshot. Objects present in snapshot 3 that grew in count between snapshots 2 and 3 are your leak; the retainer chain shows what pins them.
29
+ - **Allocation instrumentation on timeline** records every allocation — blue bars that never get collected mark the leak as it happens.
30
+ - `queryObjects(constructor)` in the console counts live instances; Detached nodes filter in heap snapshots for removed-but-pinned DOM.
31
+ - Node: `node --inspect` + DevTools heap snapshots; `clinic heap` for guided diagnosis; `--expose-gc` in tests so assertions can GC before measuring. Production-safe heap snapshot on signal where you control the entrypoint.
32
+ - The usual suspects, in order of frequency: forgotten `setInterval` (worse than `setTimeout` — it never stops on its own), listeners on long-lived objects added per-mount/per-request, `ResizeObserver`/`IntersectionObserver`/`MutationObserver` never `disconnect()`ed, growing `Map`s used as caches, detached nodes held in module-level arrays, per-request closures stored on a global.
33
+
34
+ **The unified cleanup pattern** — register everything against one handle and teardown once:
35
+
36
+ ```ts
37
+ const ac = new AbortController();
38
+ window.addEventListener("resize", onResize, { signal: ac.signal });
39
+ fetch(url, { signal: ac.signal }).then(/* ... */);
40
+ const ro = new ResizeObserver(onResize); ro.observe(el);
41
+ // one teardown path (effect cleanup, session close, SIGTERM handler):
42
+ ac.abort(); ro.disconnect(); clearInterval(id);
43
+ ```
44
+
45
+ Web-platform APIs accept `AbortSignal` directly; the rest get disconnected in the same teardown function. One owner, one path — including the error path.
46
+
47
+ **Component frameworks (React and kin):** every subscription started in an effect is returned-from that effect for cleanup, no exceptions; stores and event buses get unsubscribe on unmount; beware module-level singletons accumulating per-component state (registered callbacks never removed — the leak lives on after the component dies). Long lists get virtualized. Before adding `memo`/`useMemo` anywhere, catch a real re-render in the Profiler — memoization has its own memory cost and is frequently perf theater. Watch `key` misuse: index keys on reorder-prone lists cause remount storms (CPU) and stale-capture bugs (memory).
48
+
49
+ **Rust / C / C++ / Go / JVM / Python:** see the native/compiled and Python sections of `references/playbooks.md` for the per-runtime tools (valgrind/ASan/LSan, heaptrack, pprof, async-profiler/JFR, tracemalloc). The cycle shapes to hunt are listed in the taxonomy above.
50
+
51
+ ### Rules for fixes
52
+
53
+ Fix the lifetime, not the symptom: remove the registration (or disconnect the observer) at the source that created it; bound the cache with an eviction policy (LRU/TTL) instead of deleting entries manually where it keeps growing; break cycles at the design level (`Weak`/`weakref`/unowned edge) rather than sprinkling cleanups. Then re-run the cycle test and show the flat line. A "fix" without the flat line is not a fix.
54
+
55
+ ## Part 2 — Processes & lifecycle
56
+
57
+ ### Zombies vs orphans
58
+
59
+ - A **zombie** is a dead child the parent never reaped — it holds a PID and a kernel exit-status slot. Too many exhaust the process table. On Unix, only the parent's `wait()`/`waitpid()` (or equivalent) clears it. **Rust: dropping `Child` does not reap it — you must `wait()`.** Go: `cmd.Wait()`. C: `waitpid`. Node gets an `exit` event automatically — your job is not to spawn what you can't track. A parent that never reaps is itself the bug; the zombies are the receipt.
60
+ - An **orphan** is a child whose parent died first. It keeps running, reparented to init — nobody is coming to shut it down. Orphans are what users call "why is this still running after I quit?".
61
+
62
+ ### Kill the tree, not the node
63
+
64
+ `kill(pid)` kills one process; its spawned children (MCP servers, LSPs, shells, helpers) survive as orphans. Correct patterns:
65
+
66
+ - **Unix:** start the child in its own process group (`detached: true` in Node, `setsid`/`process_group` elsewhere), then kill the group: `process.kill(-pid, sig)` — the negative PID addresses the whole group. Escalate SIGTERM → grace period → SIGKILL.
67
+ - **Windows:** there are no process groups; `taskkill /PID <pid> /T /F` kills the tree (`/T` = tree, `/F` = force), or use a Job Object assigned to children so closing the handle terminates them all.
68
+ - **Both:** kill children *before* the parent exits, in order — parents can't clean up after they're dead; and children of an interactive app must die when the app does, normal exit *and* crash.
69
+
70
+ ### Graceful shutdown is a feature
71
+
72
+ On SIGTERM/SIGINT (and before-exit hooks): stop accepting new work, let in-flight work finish or checkpoint it, kill the child tree (above), close sockets/DB connections, release locks and port registrations, remove pidfiles/ledgers, flush and close logs — with a deadline, then force-exit. Ctrl+C on a dev server leaving a port busy is this feature missing. Test it: send SIGTERM, assert nothing from your app survives (`ps`, `lsof -i :<port>`).
73
+
74
+ ### Crash leftovers: the startup sweep
75
+
76
+ Processes crash; whatever they left must not outlive the next boot. Pattern: on start, read a ledger of resources you own (pidfiles with PID + start-time to avoid PID reuse, registered ports, lock files under `tmpdir`), verify each is actually one of yours, kill the stale ones, then write your own entry. Flock/`O_EXCL` locks make "is the old one really dead?" checkable rather than guessed. Temp dirs get a sweep of your app's namespaced files on start. This is cheap insurance and the fix for "every crash leaves another zombie until reboot".
77
+
78
+ ### Containers: the PID 1 problem
79
+
80
+ If your process is PID 1 in a container, it inherits orphaned zombies and default signal handlers that ignore SIGTERM. Either run a proper init (`docker run --init`, tini, dumb-init) or handle it yourself: reap children, trap and act on SIGTERM. K8s does not save you from this — exec-form ENTRYPOINT still makes you PID 1.
81
+
82
+ ### Leaked handles and files
83
+
84
+ The slow cousins of zombie processes: file descriptors (`lsof -p <pid>` — a climbing count is a leak), sockets/ports held by dead listeners (`ss -ltnp` / `lsof -i`), lock files, and temp files that outlive their writers. Every open() has a matching close() on all paths — `finally`, defer, RAII, context managers — and temp files are created with your app's namespaced prefix so a startup sweep can find them.
85
+
86
+ ### The verification loop
87
+
88
+ 1. Start the app; record `ps`/RSS/fd count for the process tree.
89
+ 2. Exercise the suspected path N times (open/close, request, job run).
90
+ 3. Quit — normal exit and SIGKILL both.
91
+ 4. Assert: nothing from your tree remains (`ps`, port free, fd count back to baseline, temp dir clean), and RSS on next start matches the first start.
92
+
93
+ Anything that survives step 4 is the finding, and the ledger/sweep/group-kill patterns above are the fix.
94
+
95
+ ---
96
+
97
+ **Provenance:** snapshot 17 August 2026. Sources: Rust std `process::Child` documentation (zombies and reaping), Node.js `child_process` documentation (detached/process-group semantics on Unix and Windows), Docker/tini PID 1 reaping guidance, Chrome DevTools memory documentation (three-snapshot technique, allocation timeline, `queryObjects`), web.dev long-task and lifecycle guidance. OS signal semantics are stable; tool flags and library APIs decay — re-verify specifics with web access before asserting.