@tyroneross/build-loop 0.36.0 → 0.43.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 (270) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/.cursor/rules/build-loop-surface.mdc +12 -11
  6. package/.cursor/rules/skill-index.mdc +33 -0
  7. package/AGENTS.md +213 -34
  8. package/README.md +99 -31
  9. package/agents/advisor.md +4 -4
  10. package/agents/alignment-checker.md +2 -2
  11. package/agents/architecture-scout.md +4 -4
  12. package/agents/build-orchestrator.md +38 -36
  13. package/agents/database-assessor.md +11 -5
  14. package/agents/design-contract-specialist.md +8 -8
  15. package/agents/fact-checker.md +13 -3
  16. package/agents/fix-critique.md +2 -2
  17. package/agents/independent-auditor.md +60 -7
  18. package/agents/leak-scanner.md +82 -0
  19. package/agents/overfitting-reviewer.md +2 -2
  20. package/agents/plan-critic.md +1 -1
  21. package/agents/promotion-reviewer.md +5 -5
  22. package/agents/retrospective-synthesizer.md +138 -35
  23. package/agents/scope-auditor.md +82 -11
  24. package/agents/security-reviewer.md +56 -2
  25. package/agents/self-improvement-architect.md +17 -3
  26. package/agents/transcript-pattern-miner.md +5 -5
  27. package/agents/ui-validator.md +1 -1
  28. package/bin/build-loop-debugger.js +143 -0
  29. package/bin/build-loop-install.js +1 -4
  30. package/bin/build-loop-load-probe.js +345 -0
  31. package/codex-skills/build-loop/SKILL.md +28 -6
  32. package/commands/feedback.md +37 -0
  33. package/dist/src/interactive-verifier.d.ts +1 -14
  34. package/dist/src/interactive-verifier.d.ts.map +1 -1
  35. package/dist/src/interactive-verifier.js +6 -113
  36. package/dist/src/interactive-verifier.js.map +1 -1
  37. package/dist/src/quality.d.ts +5 -0
  38. package/dist/src/quality.d.ts.map +1 -0
  39. package/dist/src/quality.js +81 -0
  40. package/dist/src/quality.js.map +1 -0
  41. package/dist/src/storage.d.ts.map +1 -1
  42. package/dist/src/storage.js +37 -3
  43. package/dist/src/storage.js.map +1 -1
  44. package/docs/agent-surface-policy.md +35 -31
  45. package/docs/memory-setup.md +19 -0
  46. package/hooks/git/pre-push +65 -4
  47. package/hooks/hooks.json +95 -38
  48. package/hooks/pre-commit +20 -1
  49. package/hooks/pre-edit-rally-point.sh +10 -3
  50. package/hooks/session-start-codex-hook-trust.sh +30 -0
  51. package/hooks/session-start-git-hooks.sh +3 -1
  52. package/hooks/session-start-rally-point.sh +52 -4
  53. package/hooks/session-start-worktree-gc.sh +47 -94
  54. package/hooks/stop-transcript-sweep.sh +173 -0
  55. package/hooks/test_closeout.sh +14 -2
  56. package/package.json +8 -7
  57. package/scripts/README.md +1 -1
  58. package/scripts/_paths.py +65 -0
  59. package/scripts/groundwork_exchange.py +1012 -0
  60. package/scripts/install_memory.py +33 -1
  61. package/scripts/lessons_index/ingest.py +13 -2
  62. package/scripts/lessons_index/query.py +36 -13
  63. package/scripts/memory_context/__init__.py +108 -14
  64. package/scripts/memory_graph/__init__.py +5 -1
  65. package/scripts/project_resolver.py +42 -36
  66. package/scripts/sync_plugin_cache.py +37 -2
  67. package/skills/agent-rally-point/SKILL.md +46 -0
  68. package/skills/api-registry-bridge/SKILL.md +1 -1
  69. package/skills/architecture/dead/SKILL.md +1 -1
  70. package/skills/architecture/impact/SKILL.md +1 -1
  71. package/skills/architecture/review/SKILL.md +1 -1
  72. package/skills/architecture/rules/SKILL.md +3 -3
  73. package/skills/architecture/scan/SKILL.md +1 -1
  74. package/skills/architecture/trace/SKILL.md +1 -1
  75. package/skills/attribution-standard/SKILL.md +6 -6
  76. package/skills/auto-decision-capture/SKILL.md +31 -2
  77. package/skills/auto-finding-capture/SKILL.md +28 -1
  78. package/skills/build-loop/SKILL.md +131 -23
  79. package/skills/build-loop/fallbacks.md +16 -21
  80. package/skills/build-loop/phases/ui-validation.md +2 -2
  81. package/skills/build-loop/references/advisor-dispatch-ladder.md +1 -1
  82. package/skills/build-loop/references/apple-native-planning.md +1 -1
  83. package/skills/build-loop/references/autonomous-and-per-commit-modes.md +11 -5
  84. package/skills/build-loop/references/autonomy-dashboard.md +115 -0
  85. package/skills/build-loop/references/capability-routing.md +24 -2
  86. package/skills/build-loop/references/coordination.md +24 -6
  87. package/skills/build-loop/references/experiment-results-template.md +15 -3
  88. package/skills/build-loop/references/leadership.md +1 -1
  89. package/skills/build-loop/references/memory.md +14 -3
  90. package/skills/build-loop/references/modular-systems-pack.md +8 -0
  91. package/skills/build-loop/references/output-style.md +86 -0
  92. package/skills/build-loop/references/phase-1-assess.md +102 -2
  93. package/skills/build-loop/references/phase-2-plan.md +9 -1
  94. package/skills/build-loop/references/phase-3-execute.md +5 -2
  95. package/skills/build-loop/references/phase-4-review.md +85 -8
  96. package/skills/build-loop/references/phase-5-iterate.md +76 -8
  97. package/skills/build-loop/references/phase-6-learn.md +10 -17
  98. package/skills/build-loop/references/privileged-request-broker.md +254 -0
  99. package/skills/build-loop/references/resource-aware-execution.md +183 -0
  100. package/skills/build-loop/references/self-recursive-dev.md +2 -2
  101. package/skills/build-loop/references/status-output-format.md +207 -0
  102. package/skills/build-loop/references/verify-dispatch.md +56 -2
  103. package/skills/building-with-deepagents/SKILL.md +1 -1
  104. package/skills/claim-scope/SKILL.md +185 -0
  105. package/skills/color-engine/SKILL.md +103 -0
  106. package/skills/color-engine/_core.py +464 -0
  107. package/skills/color-engine/color_engine.py +175 -0
  108. package/skills/cost-rca/SKILL.md +61 -0
  109. package/skills/data-plane-worktrees/SKILL.md +139 -0
  110. package/skills/data-plane-worktrees/agents/openai.yaml +4 -0
  111. package/skills/database-practice/SKILL.md +200 -0
  112. package/skills/database-practice/references/diagnostic-queries.sql +126 -0
  113. package/skills/database-practice/references/vector-and-graph-tuning.md +208 -0
  114. package/skills/database-practice/scripts/db_table_map.py +1244 -0
  115. package/skills/database-practice/scripts/test_db_table_map.py +514 -0
  116. package/skills/debug-loop/SKILL.md +36 -6
  117. package/skills/debugging-memory/SKILL.md +32 -430
  118. package/skills/debugging-memory/references/pattern-extraction.md +4 -4
  119. package/skills/debugging-memory/references/search.md +32 -120
  120. package/skills/debugging-memory/references/store.md +32 -126
  121. package/skills/debugging-memory/references/subagent-integration.md +1 -1
  122. package/skills/decision-queue/SKILL.md +251 -0
  123. package/skills/decision-queue/assets/template.html +1242 -0
  124. package/skills/decision-queue/references/example-large-queue-batching.md +164 -0
  125. package/skills/decision-queue/scripts/regen_template_constants.py +160 -0
  126. package/skills/defenseclaw-bridge/SKILL.md +2 -2
  127. package/skills/defenseclaw-bridge/references/dc-config-mapping.md +2 -9
  128. package/skills/drain-proposals/SKILL.md +53 -0
  129. package/skills/focused-loop-builder/SKILL.md +31 -0
  130. package/skills/focused-loop-builder/references/spec-format.md +27 -0
  131. package/skills/handoff/SKILL.md +169 -8
  132. package/skills/ibr-bridge/SKILL.md +4 -1
  133. package/skills/knowledge/SKILL.md +26 -14
  134. package/skills/knowledge/references/review-mode.md +2 -3
  135. package/skills/knowledge/templates/madr-minimal.md +1 -1
  136. package/skills/mcp-builder/SKILL.md +1 -1
  137. package/skills/model-bakeoff/SKILL.md +48 -10
  138. package/skills/model-tiering/SKILL.md +92 -31
  139. package/skills/native-ax-driver/SKILL.md +38 -5
  140. package/skills/native-ax-driver/scripts/native_driver.py +278 -22
  141. package/skills/native-ax-driver/scripts/test_native_driver.py +227 -0
  142. package/skills/optimize/SKILL.md +1 -1
  143. package/skills/plugin-builder/SKILL.md +48 -1
  144. package/skills/plugin-builder/references/build-loop-phase-guidance.md +3 -4
  145. package/skills/plugin-builder/references/distribution.md +13 -2
  146. package/skills/plugin-builder/references/plugin-hygiene-lessons.md +2 -2
  147. package/skills/plugin-tests/SKILL.md +2 -2
  148. package/skills/recursive-retrospective/SKILL.md +1 -1
  149. package/skills/repo-closeout/SKILL.md +17 -0
  150. package/skills/repo-closeout/agents/openai.yaml +4 -0
  151. package/skills/repo-maintenance/SKILL.md +179 -0
  152. package/skills/repo-maintenance/agents/openai.yaml +4 -0
  153. package/skills/repo-maintenance/references/pre-public-hygiene.md +134 -0
  154. package/skills/repo-maintenance/references/repository-taxonomy.md +161 -0
  155. package/skills/repo-maintenance/references/safety-protocol.md +106 -0
  156. package/skills/repo-maintenance/references/stack-profiles.md +138 -0
  157. package/skills/repo-maintenance/scripts/audit_repo_maintenance.py +1198 -0
  158. package/skills/repo-maintenance/scripts/test_audit_repo_maintenance.py +506 -0
  159. package/skills/repository-intelligence/SKILL.md +189 -0
  160. package/skills/repository-intelligence/agents/openai.yaml +4 -0
  161. package/skills/repository-intelligence/references/assessment-rubric.md +88 -0
  162. package/skills/repository-intelligence/scripts/repository_inventory.py +347 -0
  163. package/skills/research/SKILL.md +12 -2
  164. package/skills/root-cause-analysis/SKILL.md +1 -1
  165. package/skills/runtime-parity-verification/SKILL.md +36 -1
  166. package/skills/security-methodology/SKILL.md +23 -10
  167. package/skills/security-methodology/references/agentic-handoff-templates.md +220 -0
  168. package/skills/security-methodology/references/cross-source-matrix.md +1 -1
  169. package/skills/security-methodology/references/owasp-agentic-top-10.md +1 -1
  170. package/skills/security-scan/SKILL.md +55 -15
  171. package/skills/self-improve/SKILL.md +70 -50
  172. package/skills/silent-assumptions/SKILL.md +341 -0
  173. package/skills/silent-assumptions/references/elicitation-detectors.md +342 -0
  174. package/skills/spec-writing/SKILL.md +128 -24
  175. package/skills/spec-writing/scripts/check_checklist.py +114 -15
  176. package/skills/ui-design/SKILL.md +6 -4
  177. package/skills/ui-design/references/color-engine.md +132 -0
  178. package/skills/ui-design/references/design-preferences-from-owned-apps.md +8 -8
  179. package/skills/ui-design/references/ui-guidance-sources.md +1 -1
  180. package/skills/ui-design/references/universal-design-principles.alt.md +2 -2
  181. package/plugin-artifacts/codex/.codex-plugin/plugin.json +0 -41
  182. package/plugin-artifacts/codex/AGENTS.md +0 -560
  183. package/plugin-artifacts/codex/BUILD-ARTIFACT.md +0 -5
  184. package/plugin-artifacts/codex/LICENSE +0 -202
  185. package/plugin-artifacts/codex/README.md +0 -313
  186. package/plugin-artifacts/codex/assets/build-loop-plugin-icon.png +0 -0
  187. package/plugin-artifacts/codex/docs/agent-surface-policy.md +0 -63
  188. package/plugin-artifacts/codex/references/advisor-dispatch-ladder.md +0 -62
  189. package/plugin-artifacts/codex/references/agent-role-taxonomy.md +0 -135
  190. package/plugin-artifacts/codex/references/autonomous-and-per-commit-modes.md +0 -161
  191. package/plugin-artifacts/codex/references/autonomy-config.md +0 -231
  192. package/plugin-artifacts/codex/references/backlog-system.md +0 -285
  193. package/plugin-artifacts/codex/references/capability-routing.md +0 -231
  194. package/plugin-artifacts/codex/references/codex-subagents.md +0 -106
  195. package/plugin-artifacts/codex/references/coordination-file-template.md +0 -181
  196. package/plugin-artifacts/codex/references/coordination-rules.md +0 -552
  197. package/plugin-artifacts/codex/references/dogfood-reload-checkpoint.md +0 -112
  198. package/plugin-artifacts/codex/references/halt-and-ask-protocol.md +0 -102
  199. package/plugin-artifacts/codex/references/implementer-envelope-schema.md +0 -302
  200. package/plugin-artifacts/codex/references/intent-capability-pack.md +0 -257
  201. package/plugin-artifacts/codex/references/intent-exploration-prompts.md +0 -96
  202. package/plugin-artifacts/codex/references/leadership.md +0 -72
  203. package/plugin-artifacts/codex/references/memory-systems.md +0 -261
  204. package/plugin-artifacts/codex/references/memory.md +0 -313
  205. package/plugin-artifacts/codex/references/model-tier-mapping.md +0 -296
  206. package/plugin-artifacts/codex/references/modular-systems-pack.md +0 -96
  207. package/plugin-artifacts/codex/references/phase-1-assess.md +0 -249
  208. package/plugin-artifacts/codex/references/phase-2-plan.md +0 -86
  209. package/plugin-artifacts/codex/references/phase-3-execute.md +0 -49
  210. package/plugin-artifacts/codex/references/phase-4-review.md +0 -341
  211. package/plugin-artifacts/codex/references/phase-5-iterate.md +0 -72
  212. package/plugin-artifacts/codex/references/phase-6-learn.md +0 -58
  213. package/plugin-artifacts/codex/references/recent-design-structures.md +0 -274
  214. package/plugin-artifacts/codex/references/research-trigger-policy.md +0 -140
  215. package/plugin-artifacts/codex/references/runtime-smoke-triggers.md +0 -42
  216. package/plugin-artifacts/codex/references/self-review.md +0 -234
  217. package/plugin-artifacts/codex/references/single-writer-commit-protocol.md +0 -90
  218. package/plugin-artifacts/codex/references/task-capture-policy.md +0 -68
  219. package/plugin-artifacts/codex/references/ui-io-contract.md +0 -116
  220. package/plugin-artifacts/codex/references/ui-spotcheck-protocol.md +0 -65
  221. package/plugin-artifacts/codex/references/verify-dispatch.md +0 -85
  222. package/plugin-artifacts/codex/skills/build-loop/SKILL.md +0 -381
  223. package/plugin-artifacts/codex/skills/build-loop/detect-plugins.mjs +0 -82
  224. package/plugin-artifacts/codex/skills/build-loop/eval-guide.md +0 -65
  225. package/plugin-artifacts/codex/skills/build-loop/fallbacks.md +0 -549
  226. package/plugin-artifacts/codex/skills/build-loop/phases/fact-check.md +0 -42
  227. package/plugin-artifacts/codex/skills/build-loop/phases/ui-validation.md +0 -267
  228. package/plugin-artifacts/codex/skills/build-loop/references/advisor-dispatch-ladder.md +0 -62
  229. package/plugin-artifacts/codex/skills/build-loop/references/apple-native-planning.md +0 -439
  230. package/plugin-artifacts/codex/skills/build-loop/references/autonomous-and-per-commit-modes.md +0 -161
  231. package/plugin-artifacts/codex/skills/build-loop/references/capability-routing.md +0 -231
  232. package/plugin-artifacts/codex/skills/build-loop/references/codex-subagents.md +0 -106
  233. package/plugin-artifacts/codex/skills/build-loop/references/coordination.md +0 -161
  234. package/plugin-artifacts/codex/skills/build-loop/references/correction-aware-capture.md +0 -177
  235. package/plugin-artifacts/codex/skills/build-loop/references/experiment-results-template.md +0 -101
  236. package/plugin-artifacts/codex/skills/build-loop/references/independent-auditor.md +0 -72
  237. package/plugin-artifacts/codex/skills/build-loop/references/intent-capability-pack.md +0 -257
  238. package/plugin-artifacts/codex/skills/build-loop/references/intent-exploration-prompts.md +0 -96
  239. package/plugin-artifacts/codex/skills/build-loop/references/leadership.md +0 -72
  240. package/plugin-artifacts/codex/skills/build-loop/references/memory.md +0 -313
  241. package/plugin-artifacts/codex/skills/build-loop/references/modular-systems-pack.md +0 -96
  242. package/plugin-artifacts/codex/skills/build-loop/references/output-style.md +0 -222
  243. package/plugin-artifacts/codex/skills/build-loop/references/pay-it-forward-arch.md +0 -98
  244. package/plugin-artifacts/codex/skills/build-loop/references/phase-1-assess.md +0 -249
  245. package/plugin-artifacts/codex/skills/build-loop/references/phase-2-plan.md +0 -86
  246. package/plugin-artifacts/codex/skills/build-loop/references/phase-3-execute.md +0 -49
  247. package/plugin-artifacts/codex/skills/build-loop/references/phase-4-review.md +0 -341
  248. package/plugin-artifacts/codex/skills/build-loop/references/phase-5-iterate.md +0 -72
  249. package/plugin-artifacts/codex/skills/build-loop/references/phase-6-learn.md +0 -58
  250. package/plugin-artifacts/codex/skills/build-loop/references/recent-design-structures.md +0 -274
  251. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/ASSESSMENT.md +0 -85
  252. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +0 -149
  253. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +0 -32
  254. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +0 -48
  255. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +0 -60
  256. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +0 -51
  257. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +0 -71
  258. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +0 -52
  259. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/trace-comparison.md +0 -202
  260. package/plugin-artifacts/codex/skills/build-loop/references/self-recursive-dev.md +0 -77
  261. package/plugin-artifacts/codex/skills/build-loop/references/self-review.md +0 -234
  262. package/plugin-artifacts/codex/skills/build-loop/references/ui-io-contract.md +0 -116
  263. package/plugin-artifacts/codex/skills/build-loop/references/verify-dispatch.md +0 -85
  264. package/plugin-artifacts/codex/skills/build-loop/scanners/audit-design-rules.mjs +0 -476
  265. package/plugin-artifacts/codex/skills/build-loop/scanners/require-visual-evidence.mjs +0 -239
  266. package/plugin-artifacts/codex/skills/build-loop/templates/backlog-item.md +0 -35
  267. package/plugin-artifacts/codex/skills/build-loop/templates/codex-worker-prompt.md +0 -100
  268. package/plugin-artifacts/codex/skills/build-loop/templates/ui-subagent-prompt.md +0 -179
  269. package/plugin-artifacts/codex/skills/build-loop/templates/ux-fix-plan.md +0 -40
  270. package/scripts/build_codex_plugin_artifact.py +0 -321
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: cost-rca
3
+ description: Quantify what a context/caching/model change did to token spend and dollars, from measured usage data, never estimates. Use when the user asks "how much did that cost", "did caching save money", "why did my bill spike", or "token delta". Not for choosing which model to use (use `model-tiering`) or comparing models head-to-head (use `model-bakeoff`).
4
+ user-invocable: false
5
+ ---
6
+
7
+ <!-- SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com> | SPDX-License-Identifier: Apache-2.0 -->
8
+
9
+ # Cost-Impact RCA
10
+
11
+ Answer "what did this change do to cost, and why" with MEASURED evidence, not guesses.
12
+ Deterministic-first: the script measures; you narrate and price live.
13
+
14
+ ## Non-negotiables
15
+
16
+ - **Measured tokens only.** Never quote `tokens_estimate` or the ledger's `est_cost_usd`
17
+ as the answer. `scripts/cost_rca.py` already excludes estimate-only rows and surfaces
18
+ them separately as *unmeasured spend* — call that out, do not price it as if measured.
19
+ - **Price LIVE, never from memory.** Rate cards drift. Fetch the current per-MTok price
20
+ for each bucket (input / output / cache-read / cache-write) at analysis time and CITE
21
+ the source. Prices from training data are `[UNVERIFIED]` and must not drive a number.
22
+ - **Per bucket, not a blended rate.** Cache-read is ~10× cheaper than input and cache-write
23
+ is ~1.25× input on Anthropic's card — a blended rate hides the whole point of the RCA.
24
+
25
+ ## Procedure
26
+
27
+ 1. **Aggregate measured tokens** (deterministic):
28
+ ```
29
+ python3 scripts/cost_rca.py --ledger ~/.bookmark/cost-ledger.jsonl [--run-id R] [--since ISO] [--group-by model] --json
30
+ ```
31
+ Output gives, per model and in total: `inbound`, `outbound`, `cache_read`, `cache_write`,
32
+ `total_tokens`, `context_utilization` (peak input-side vs the model's window), and the
33
+ `estimate_only_rows` count. For a before/after RCA, run it twice (two `--since`/`--run-id`
34
+ windows) and diff the buckets — that diff IS the token delta.
35
+
36
+ 2. **Fetch the current rate card LIVE** for each model present. Order:
37
+ `/api-registry:lookup <provider>` → the `claude-api` skill (for Anthropic) →
38
+ WebSearch the provider's official pricing page. Record per-MTok: input, output,
39
+ cache-read (5-min/1-hr as applicable), cache-write. Cite each. Mark confidence
40
+ (✅ official docs / ⚠️ secondary / ❓ unverified).
41
+
42
+ 3. **Price each bucket**: `dollars_bucket = tokens_bucket / 1_000_000 * price_bucket`.
43
+ Report per bucket AND per model, then the total. Show the arithmetic — the reader
44
+ must be able to re-derive it.
45
+
46
+ 4. **Context-window utilization**: report `peak_input_side / context_window`. High
47
+ utilization + high cache-write with low cache-read is the classic "cache thrash"
48
+ signature — a candidate root cause for a cost regression. Confirm the current window
49
+ live (windows change); the script's baked-in windows are advisory.
50
+
51
+ 5. **Root cause + lever** (blameless): name the token-level cause (e.g. "system prompt
52
+ re-sent uncached each call → all inbound, zero cache-read"), then the smallest durable
53
+ lever (stable-prefix ordering for cache hits, prompt trim, tier swap, context offload).
54
+ Quantify the projected saving in tokens AND live dollars.
55
+
56
+ ## Output shape
57
+
58
+ Lead with the headline dollar/token delta and the ✅/⚠️/❓ confidence on the pricing.
59
+ Then the per-bucket table (tokens · live $/MTok · $), then the root cause and the lever
60
+ with its projected saving. Keep the ledger path + pricing citations in a footer so the
61
+ number is auditable.
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: build-loop:data-plane-worktrees
3
+ description: "Decide how a build/worktree safely isolates mutable non-Git state (databases, caches, volumes, indexes) so parallel work doesn't collide. Use when the user asks \"is it safe to run this in parallel\" or \"will this migration collide with the other branch\", or a run touches a database/cache/volume. Covers SQLite and PostgreSQL databases, generated search/vector indexes, Docker volumes, mutable file trees, and external cloud/account namespaces."
4
+ user-invocable: false
5
+ ---
6
+
7
+ <!-- SPDX-FileCopyrightText: 2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com> -->
8
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
9
+
10
+ # Data-Plane Worktrees
11
+
12
+ Treat a Git worktree as source-plane isolation only. Inventory and isolate every
13
+ mutable resource the run can read or write before implementation starts.
14
+
15
+ ## Classify every surface
16
+
17
+ | Isolation | Use for | Required evidence |
18
+ |---|---|---|
19
+ | `per_worktree` | SQLite copies, generated indexes, mutable file stores, disposable local state | `path` under the allocated run data root |
20
+ | `shared_readonly` | Canonical raw inputs, fixtures, large immutable corpora | `writable: false` |
21
+ | `shared_serialized` | A central writer that cannot be cloned, such as a canonical index updater | Stable `writer` key naming the serialization authority |
22
+ | `external_namespaced` | PostgreSQL databases/schemas, Compose projects, buckets, queues, cloud accounts | Stable `namespace` unique to the run |
23
+
24
+ Prefer `per_worktree`. Use shared mutation only when cloning or namespacing is
25
+ not practical and one explicit writer serializes all changes.
26
+
27
+ ## Workflow
28
+
29
+ 1. Read the run identity and baseline manifest from
30
+ `.build-loop/state.json.execution.{build_loop_id,data_manifest_path,data_root}`.
31
+ Fresh isolated runs create these fields automatically.
32
+ 2. Inventory all non-Git state read or written by code, tests, migrations,
33
+ services, hooks, and generated artifacts. Treat an omitted surface as an
34
+ unresolved isolation risk.
35
+ 3. Add each surface through the validator before any adapter provisions or
36
+ mutates it:
37
+
38
+ ```bash
39
+ python3 "${CLAUDE_PLUGIN_ROOT}/scripts/data_plane.py" add \
40
+ --workdir "$PWD" \
41
+ --manifest "$DATA_MANIFEST" \
42
+ --surface-json '<surface-json>'
43
+ ```
44
+
45
+ 4. Run `validate` before the first write. A collision, escaping path, invalid
46
+ shared writer, or malformed peer manifest fails closed.
47
+ 5. Let the repository-specific adapter perform the actual copy, migration,
48
+ service provisioning, or namespace creation. The generic lifecycle never
49
+ guesses credentials or destroys external resources.
50
+ 6. After adapter cleanup or an explicit retain decision, record each writable
51
+ surface as `closed`, `retained`, or `not_owned`:
52
+
53
+ ```bash
54
+ python3 "${CLAUDE_PLUGIN_ROOT}/scripts/data_plane.py" close \
55
+ --manifest "$DATA_MANIFEST" \
56
+ --surface-id '<surface-id>' \
57
+ --status closed
58
+ ```
59
+
60
+ 7. Run `terminal` before branch closeout. The canonical closeout gate repeats
61
+ this check and blocks active, deferred, or errored owned surfaces.
62
+
63
+ ## Surface shape
64
+
65
+ ```json
66
+ {
67
+ "id": "search-index",
68
+ "kind": "generated-index",
69
+ "authority": "derived",
70
+ "isolation": "per_worktree",
71
+ "writable": true,
72
+ "resource_key": "index:search:<run-id>",
73
+ "path": "search-index",
74
+ "status": "active"
75
+ }
76
+ ```
77
+
78
+ Use a stable `resource_key` for the underlying resource, not a display label.
79
+ Two active manifests may share a key only when both use `shared_serialized` and
80
+ declare the same non-empty `writer`.
81
+
82
+ ## Database and file rules
83
+
84
+ - SQLite: snapshot into the run data root; never open the canonical file for
85
+ writes from two worktrees.
86
+ - PostgreSQL: prefer database-per-run for migrations. Schema-per-run is
87
+ acceptable only when extensions, roles, and database-level DDL are out of
88
+ scope.
89
+ - Generated indexes: build per run; merge source changes first, then rebuild the
90
+ canonical index once through its declared writer.
91
+ - Docker/Compose: derive a unique project and volume namespace from the run id.
92
+ - Sensitive or large raw files: mount or reference read-only; keep copied
93
+ derivatives in the run data root.
94
+
95
+ The run data root lives at canonical `.build-loop/data/<run-id>/`, outside the
96
+ linked source worktree. Putting ignored data inside a linked worktree makes
97
+ normal non-force `git worktree remove` fail.
98
+
99
+ ## Migration rehearsal (isolated — production never touched)
100
+
101
+ Run on **Fable** (DB actions pin Frontier tier — see `skills/model-tiering`). Rehearse migration-first deploys against a throwaway **local** DB before any production migration. Hard guard: assert the target URL contains `@127.0.0.1:` (or your local host) AND a `rehearsal` marker before every create/apply/drift/teardown; never read `DATABASE_URL`/`DIRECT_URL` when they point at prod.
102
+
103
+ 1. Create ephemeral DB (`atomize_rehearsal_<run>`), materialize the **base (origin/main) schema** so ALTER targets and FK parents exist.
104
+ 2. Apply each migration in order (`psql -v ON_ERROR_STOP=1 -f`); re-apply to prove **idempotency** (exit 0 both times).
105
+ 3. Drift-check the migrated DB vs the branch datamodel — exit 0 = no missing columns/tables (no runtime `P2022`).
106
+ 4. Functional-test the new CHECK/FK constraints (bad value rejected, good value accepted).
107
+ 5. Drop the ephemeral DB **and any cluster-global roles/extensions you created** (roles are not per-DB — verify 0 remain).
108
+
109
+ **Prisma 7 CLI (verified 2026-07-22 — several v6 flags were removed):**
110
+
111
+ ```bash
112
+ export PRISMA_MIGRATE_URL="postgresql://<user>@127.0.0.1:5432/atomize_rehearsal_<run>" # highest precedence in prisma.config.ts
113
+ # base schema DDL from a datamodel (--from-url REMOVED; --to-schema-datamodel REMOVED → use --to-schema):
114
+ git show origin/main:prisma/schema.prisma > /tmp/base.prisma
115
+ npx prisma migrate diff --from-empty --to-schema /tmp/base.prisma --script -o /tmp/base.sql
116
+ psql -v ON_ERROR_STOP=1 -d atomize_rehearsal_<run> -f /tmp/base.sql
117
+ # drift: migrated live DB vs branch datamodel (-o REQUIRED — env-injection notices pollute stdout):
118
+ npx prisma migrate diff --from-config-datasource prisma.config.ts --to-schema prisma/schema.prisma --exit-code -o /tmp/drift.txt
119
+ ```
120
+
121
+ Supabase-CLI migrations (`supabase/migrations/*.sql`) are plain SQL applied by `supabase migration up`/`db push` — apply them with `psql` in the rehearsal, NOT `prisma migrate deploy` (Prisma's `migrations.path` tracks `prisma/migrations` only). Additive `add column if not exists` (nullable) + `NOT VALID`→`VALIDATE CONSTRAINT` is the safe pattern; `VALIDATE` full-scans the table under SHARE UPDATE EXCLUSIVE (writes continue) — schedule off-peak when large.
122
+
123
+ ## Verification
124
+
125
+ Run:
126
+
127
+ ```bash
128
+ python3 "${CLAUDE_PLUGIN_ROOT}/scripts/data_plane.py" validate \
129
+ --workdir "$PWD" --manifest "$DATA_MANIFEST" --run-id "$BUILD_LOOP_ID"
130
+ python3 "${CLAUDE_PLUGIN_ROOT}/scripts/data_plane.py" terminal \
131
+ --workdir "$PWD" --manifest "$DATA_MANIFEST" --run-id "$BUILD_LOOP_ID"
132
+ ```
133
+
134
+ Validation cost scales with active manifest and surface count; it does not copy
135
+ databases or rebuild indexes. Adapter-specific provisioning cost remains
136
+ explicit in the plan and performance evidence.
137
+
138
+ For the lifecycle contract and closeout integration, read
139
+ `docs/SPEC-run-worktree-isolation.md` and `scripts/data_plane.py`.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Data-Plane Worktrees"
3
+ short_description: "Isolate mutable state across Git worktrees"
4
+ default_prompt: "Use $data-plane-worktrees to isolate this database and generated-state workflow across concurrent Git worktrees."
@@ -0,0 +1,200 @@
1
+ ---
2
+ name: build-loop:database-practice
3
+ description: "Measure a database before changing it, and prove an object is dead before retiring it. Use when a build creates or alters a table, column, or index; when a query is slow or the user asks why the database is slow; or when someone proposes dropping an empty table. NOT for worktree or data-plane isolation (use data-plane-worktrees). Runs the read-only attribution set, reads the runtime counters, and applies the retirement gate. The binding rules live in references/database-agent-constitution.md; this skill is the procedure that produces the evidence those rules require."
4
+ user-invocable: false
5
+ ---
6
+
7
+ <!-- SPDX-FileCopyrightText: 2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com> -->
8
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
9
+
10
+ # Database Practice
11
+
12
+ The rules are in `references/database-agent-constitution.md` — "Object Lifecycle
13
+ And Population Contract" and "Cost Attribution And Retirement". This skill is how
14
+ you generate the evidence they demand.
15
+
16
+ Two things go wrong without it. A plan names a slow thing without naming its
17
+ share, and optimizes the wrong 5%. An audit calls an empty table dead, and drops
18
+ an object something still reads.
19
+
20
+ ## Procedure
21
+
22
+ 1. **Open the counter window.** `pg_postmaster_start_time()` and
23
+ `pg_stat_database.stats_reset`. Every counter below is "since" that timestamp,
24
+ and the window length is part of every claim you make from it. A 200-day
25
+ counter history is a stronger liveness record than the 30-day observation
26
+ period you were about to propose starting.
27
+ 2. **Attribute the time.** Run section 2 of the query set. Rank your proposal
28
+ against what is actually there. If it is not in the top 25, say so in the plan.
29
+ 3. **Read the shape, not the name.** Sections 3, 6, 7, 8, 9 catch the five cost
30
+ shapes below. Each has a fingerprint you can see without an execution plan.
31
+ 4. **Establish liveness before retirement.** Section 4, then the gate.
32
+ 5. **Attach before/after.** `calls`, `mean_exec_time`, `total_exec_time`,
33
+ `rows/calls`, `temp_blks_written`, and `idx_scan` for every index touched.
34
+ "The query looks faster" and "the build is green" are not evidence.
35
+
36
+ Run everything inside `BEGIN READ ONLY` with a statement timeout. Never run the
37
+ attribution set against production from an unbounded session.
38
+
39
+ ## The five shapes, with their fingerprints
40
+
41
+ Ordered by observed share of database execution time in a real production
42
+ instance (12 GB, PostgreSQL 17, 299 hours of tracked execution across 48M calls),
43
+ not by textbook severity.
44
+
45
+ | Share | Shape | Fingerprint | Fix |
46
+ |---:|---|---|---|
47
+ | 49.3% | Insert against a vector index larger than cache | 485,596 `INSERT`s at ~1,000 ms each into two pgvector tables; cost is nearly flat in row count | Split fixed from marginal cost first (below), then batch harder or shrink the index |
48
+ | 14.8% | Predicate on a TOASTed column | mean in the tens of seconds on a table of only tens of thousands of rows; `pg_total_relation_size` far above heap + indexes | Derived scalar column (`content_length`), indexed, filtered on instead |
49
+ | 8.9% | Per-row lookup through jsonb + trigram | high `calls`, mid-hundreds `mean_ms`, no supporting composite index | Composite index on the real filter columns; move the fuzzy match behind an exact one |
50
+ | 4.5% | Vector similarity read | ~1 s mean against an HNSW index larger than `shared_buffers` | Size the graph to cache, or raise the cache |
51
+ | — | Index maintenance charged to writes | index `idx_scan` near zero while its table takes hundreds of thousands of inserts | Drop it, or accept the write cost explicitly |
52
+
53
+ ### Split fixed from marginal before you "batch harder"
54
+
55
+ `rows / calls = 1.00` looks like an unbatched loop and usually is. It was not
56
+ here: the writer already batched, at four rows per statement, and the table
57
+ simply produced one chunk for most articles.
58
+
59
+ Regress `mean_exec_time` against `rows / calls` across the normalized statement
60
+ variants pg_stat_statements already gives you for free. In this instance the
61
+ call-weighted fit was **957 ms fixed per statement + 47 ms per row** — 95% fixed
62
+ at one row per statement, still 84% fixed at the batch size actually in use. The
63
+ fixed part is first-touch random I/O into an HNSW graph that cannot be cached;
64
+ the marginal part is the real per-row index maintenance.
65
+
66
+ The fit is observational — it reads variants the workload happened to produce, so
67
+ batch size may correlate with row width and the projection is a hypothesis, not a
68
+ result. Rehearse the new batch size against a copy before shipping it.
69
+
70
+ That decomposition picks the fix. A high marginal cost means batch. A high fixed
71
+ cost means the batch is too small for the overhead it is paying, or the index
72
+ does not fit in cache — and raising this batch from 4 to 64 moves per-row cost
73
+ from 286 ms to 62 ms without touching the index at all. Guessing which, without
74
+ the split, optimizes the wrong term.
75
+
76
+ Two numbers from the same instance make the index row concrete: a 2.31 GB HNSW
77
+ index recorded **31 lifetime index scans across 214 days** while being maintained
78
+ on 265,000 inserts, and a second HNSW index of 345 MB recorded **5**. Both were
79
+ being paid for on every write. Meanwhile `shared_buffers` was 256 MB against a
80
+ 12 GB database — the graph could never be cached, so every insert was random I/O.
81
+
82
+ Spill in the same instance: 336,253 temp files and 2,053 GB written, against a
83
+ `work_mem` of 3.4 MB, with one CTE writing 41.8 GB across 91 calls.
84
+
85
+ ## Liveness: reading the counters
86
+
87
+ | Signal | Reads as |
88
+ |---|---|
89
+ | `idx_scan > 0` | An application issued a filtered query. Audit scripts issue `count(*)`/`count(col)`, which are sequential scans, so an index scan is not audit noise |
90
+ | `seq_scan` well above the median across peer tables | Real sequential reads on top of the audit/monitor floor |
91
+ | `n_tup_ins > 0` with `n_live_tup = 0` | Written and purged — a working queue or retention job, not a dead table |
92
+ | `n_tup_ins = 0` across the whole window | Never written since the counters started |
93
+ | Query-text match in `pg_stat_statements` | A lower bound only — blind to dynamic SQL, views, routines, and evicted entries |
94
+
95
+ `n_live_tup` is a stale planner estimate and must never decide emptiness. In the
96
+ instance above it read seven populated tables as empty, one of them holding 148
97
+ rows.
98
+
99
+ Static source matching is the weaker method and errs both ways: it counted
100
+ documentation and generated-client references as evidence of life for two tables
101
+ that runtime showed had zero access in 214 days, and it flagged two others for
102
+ retirement that runtime showed were being read.
103
+
104
+ ## Track tables — the map, and the diff against last time
105
+
106
+ The attribution set answers "where does the time go today". The table map
107
+ answers "what changed since the last time we looked", which is the question a
108
+ schema change or a hot query needs. `scripts/db_table_map.py` collects every
109
+ counter this skill reads, in one read-only transaction, and writes a JSON map
110
+ plus a Markdown report with a **Shapes** section that names which of the five
111
+ cost shapes each finding matches.
112
+
113
+ ```bash
114
+ python3 "${CLAUDE_PLUGIN_ROOT}/skills/database-practice/scripts/db_table_map.py" \
115
+ --dsn "$DATABASE_URL" --statement-timeout 25 \
116
+ --out-json docs/10-reports/database-audits/$(date +%F)-database-map.json \
117
+ --out-md docs/10-reports/database-audits/$(date +%F)-database-map.md
118
+ # next time, diff against the previous JSON:
119
+ python3 .../db_table_map.py --dsn "$DATABASE_URL" --prev docs/10-reports/database-audits/<prev>.json ...
120
+ ```
121
+
122
+ The script strips the URI parameters psql rejects (`pgbouncer`, `connection_limit`,
123
+ `pool_timeout`), refuses any DDL or DML, and wraps everything in `BEGIN READ ONLY`
124
+ with a `SET LOCAL statement_timeout`. Commit both files under the project's
125
+ `docs/**/database-audits/` directory; re-run with `--prev` whenever a migration
126
+ lands or a query enters the top 25. A liveness verdict that flips, an index whose
127
+ `idx_scan` stays flat while its table's inserts grow, or a size delta over 10%
128
+ is the diff's job to surface.
129
+
130
+ Verified 2026-09-05 against a 12 GB PostgreSQL 17.4 instance through a
131
+ transaction-mode pooler: 128 tables, 529 indexes, 23 shape findings in one pass.
132
+
133
+ ## Vector and graph shapes
134
+
135
+ pgvector indexes and knowledge graphs stored in plain SQL have their own
136
+ fingerprints — a filtered similarity query that silently under-returns, an HNSW
137
+ graph larger than `shared_buffers` maintained on every insert, a function whose
138
+ `hnsw.*` settings never reached production, an adjacency table without a covering
139
+ index in both directions, a materialized-neighbourhood cache with a hot-row update
140
+ profile. `references/vector-and-graph-tuning.md` carries each fingerprint, the
141
+ fix, and the pgvector settings (`ef_search` default 40; `iterative_scan`
142
+ `relaxed_order` / `strict_order` from 0.8.0; `m` / `ef_construction`; function-scoped
143
+ `ALTER FUNCTION ... SET` because a session `SET` is lost under a transaction-mode
144
+ pooler).
145
+
146
+ ## Fingerprint → first action
147
+
148
+ | Fingerprint (from the map or the query set) | First action |
149
+ |---|---|
150
+ | `INSERT` with `rows/calls` = 1.00 into a table with an HNSW/GIN index | Split fixed from marginal cost (regress `mean_exec_time` on `rows/calls`), then batch or shrink the index |
151
+ | Index `idx_scan` near zero while its table takes ≥ 10⁵ inserts | Retirement gate: rename → observe → drop, or record why it is kept |
152
+ | HNSW index larger than `shared_buffers` | Size the graph to cache (fewer dimensions, halfvec, partial index) or raise the cache; never assume the graph is resident |
153
+ | Filtered vector query returns fewer rows than `LIMIT` | `ALTER FUNCTION ... SET hnsw.iterative_scan = relaxed_order` (function-scoped), then measure recall against an exact scan |
154
+ | Vector function's `proconfig` lacks the `hnsw.*` settings the repo's SQL declares | Drift: restore with a migration and add a check that reads `pg_proc.proconfig` |
155
+ | `pg_total_relation_size` ≫ heap + indexes (TOAST) on a filtered table | Derived scalar column beside the wide one; filter and sort on the scalar |
156
+ | High `n_tup_upd` on a small table (hot rows) | Check HOT-update ratio and fillfactor; move the churn to an append-only side table |
157
+ | `temp_blks_written` on a CTE or sort | Raise `work_mem` for that role or query only, after rehearsing against a copy |
158
+ | `n_live_tup` = 0 but `idx_scan` > 0 | Not empty. `n_live_tup` is an estimate; run the exact `count(*)` from section 5 |
159
+
160
+ ## Sonnet-tier runbook
161
+
162
+ Each step names the command, the output you must have in hand, and the stop
163
+ condition. Do not skip to a fix; the evidence is the deliverable.
164
+
165
+ 1. **Window.** Run section 1 of the query set (or the map's "Counter window"). Have: `pg_postmaster_start_time`, `stats_reset`, window length in days. Stop if the window is under 14 days — say so in the plan; the counters cannot support a retirement claim yet.
166
+ 2. **Map.** Run `db_table_map.py` with `--out-json` and `--out-md` (and `--prev` when a previous map exists). Have: the Markdown report committed under `docs/**/database-audits/`. Stop if the script reports `pg_stat_statements` missing — attribution (step 3) is then a lower bound and the plan must say so.
167
+ 3. **Attribute.** Read the map's "Top statements by total_exec_time". Have: the share of DB time your proposal touches, quoted as a percentage. Stop if it is not in the top 25 — record the proposal as low-leverage and do not spend a migration on it without a stated reason.
168
+ 4. **Shape.** Read the map's "Shapes" section and the fingerprint table above. Have: one named shape per finding you intend to act on, with its evidence line. Stop if a finding matches no shape — escalate to a stronger tier with the evidence.
169
+ 5. **Liveness before retirement.** For any drop or rename, run the retirement gate below. Have: every checkbox with its evidence. Stop at the first unchecked line.
170
+ 6. **Before/after.** For every index or statement you change, record `calls`, `mean_exec_time`, `total_exec_time`, `rows/calls`, `temp_blks_written`, and `idx_scan` from the map before and after, and re-run the map with `--prev`. "The query looks faster" is not evidence.
171
+
172
+ **Escalate to a stronger tier when:** the proposal is DDL on a table over 1 GB; any HNSW or GIN rebuild; any change to a function's `proconfig` or to `shared_buffers` / `work_mem`; attribution is ambiguous between two shapes; or the retirement gate depends on an owner who has not answered. State what was measured so far in the hand-off.
173
+
174
+ ## Retirement gate
175
+
176
+ Every line holds, or the object stays:
177
+
178
+ - [ ] A named owner confirms the feature state and its replacement.
179
+ - [ ] Repository search covers raw SQL, ORM model names, mapped names, generated
180
+ clients, scripts, tests, and documentation.
181
+ - [ ] Database dependencies cover foreign keys, views, materialized views,
182
+ routines, triggers, policies, and publications.
183
+ - [ ] External workers, cron jobs, queues, dashboards, and integrations checked.
184
+ - [ ] Runtime counters show zero reads and zero writes across a stated window,
185
+ and the window's start date is stated with it.
186
+ - [ ] Retention, compliance, backup, and restore requirements resolved.
187
+ - [ ] The change is a staged rename → deny → observe → drop.
188
+ - [ ] Tests pass against the staged change.
189
+
190
+ Stage one is always a rename with the old name left as a view, or a revoke. A
191
+ drop that has not survived a rename has not been tested.
192
+
193
+ ## Query set
194
+
195
+ `references/diagnostic-queries.sql` — counter window, time attribution,
196
+ single-row-insert fingerprint, per-table liveness, exact emptiness, index cost vs
197
+ benefit, duplicate indexes, temp spill, TOAST ratio, and the column population
198
+ check. Verified green against PostgreSQL 17.4 on Supabase.
199
+ `references/vector-and-graph-tuning.md` — pgvector/HNSW and graph-in-SQL
200
+ fingerprints and fixes. `scripts/db_table_map.py` — the table map and diff.
@@ -0,0 +1,126 @@
1
+ -- Read-only PostgreSQL diagnostic set for build-loop:database-practice.
2
+ -- Run inside BEGIN READ ONLY with a statement timeout. Requires pg_stat_statements
3
+ -- for sections 2-3; every other section works on a stock instance.
4
+ --
5
+ -- psql "$DATABASE_URL" -X -A -F $'\t' -v ON_ERROR_STOP=1 \
6
+ -- -c 'set statement_timeout=30000' -f diagnostic-queries.sql
7
+
8
+ -- 1. Counter window. Everything below is "since" these timestamps.
9
+ -- stats_reset NULL means never reset; use postmaster start as the floor.
10
+ select pg_postmaster_start_time() as counters_since,
11
+ now() - pg_postmaster_start_time() as window_length,
12
+ (select stats_reset from pg_stat_database
13
+ where datname = current_database()) as stats_reset;
14
+
15
+ -- 2. Where the time actually goes. Rank every proposal against this.
16
+ select round((total_exec_time / 3600000)::numeric, 2) as hours,
17
+ round((100.0 * total_exec_time
18
+ / sum(total_exec_time) over ())::numeric, 2) as pct_of_db_time,
19
+ calls,
20
+ round(mean_exec_time::numeric, 1) as mean_ms,
21
+ round((rows::numeric / nullif(calls, 0)), 2) as rows_per_call,
22
+ temp_blks_written,
23
+ left(regexp_replace(query, '\s+', ' ', 'g'), 160) as statement
24
+ from pg_stat_statements
25
+ where dbid = (select oid from pg_database where datname = current_database())
26
+ order by total_exec_time desc
27
+ limit 25;
28
+
29
+ -- 3. Single-row-insert fingerprint: rows_per_call = 1.00 on a high-call INSERT.
30
+ select calls, round(mean_exec_time::numeric, 1) as mean_ms,
31
+ round((total_exec_time / 3600000)::numeric, 2) as hours,
32
+ left(regexp_replace(query, '\s+', ' ', 'g'), 100) as statement
33
+ from pg_stat_statements
34
+ where query ~* '^\s*insert' and rows = calls and calls > 1000
35
+ order by total_exec_time desc
36
+ limit 15;
37
+
38
+ -- 4. Per-table liveness. idx_scan > 0 means an application issued a filtered
39
+ -- query; audit scripts only produce seq_scan. Compare seq_scan against the
40
+ -- median across peers to find the audit/monitor floor.
41
+ select relname,
42
+ n_live_tup, n_dead_tup,
43
+ n_tup_ins, n_tup_upd, n_tup_del,
44
+ seq_scan, coalesce(idx_scan, 0) as idx_scan,
45
+ last_autovacuum, last_autoanalyze
46
+ from pg_stat_user_tables
47
+ order by (coalesce(idx_scan, 0) + seq_scan) asc;
48
+
49
+ -- 5. Exact emptiness. NEVER decide this from n_live_tup, which is a stale
50
+ -- planner estimate. Generates the count statements; run the output.
51
+ select format('select %L as tbl, count(*) from %I.%I;', relname, schemaname, relname)
52
+ from pg_stat_user_tables
53
+ where n_live_tup = 0
54
+ order by relname;
55
+
56
+ -- 6. Index cost vs benefit. An index with idx_scan near zero is paid for on
57
+ -- every insert and never read. Check size against shared_buffers.
58
+ select t.relname as tbl,
59
+ i.relname as idx,
60
+ am.amname as method,
61
+ coalesce(s.idx_scan, 0) as scans,
62
+ pg_size_pretty(pg_relation_size(i.oid)) as size,
63
+ pg_get_indexdef(i.oid) as definition
64
+ from pg_class i
65
+ join pg_index x on x.indexrelid = i.oid
66
+ join pg_class t on t.oid = x.indrelid
67
+ join pg_namespace n on n.oid = i.relnamespace and n.nspname = 'public'
68
+ join pg_am am on am.oid = i.relam
69
+ left join pg_stat_user_indexes s on s.indexrelid = i.oid
70
+ order by coalesce(s.idx_scan, 0) asc, pg_relation_size(i.oid) desc;
71
+
72
+ select name, setting, unit from pg_settings
73
+ where name in ('shared_buffers', 'work_mem', 'effective_cache_size',
74
+ 'max_connections', 'max_parallel_workers_per_gather');
75
+
76
+ -- 7. Duplicate indexes on the same expression.
77
+ select indrelid::regclass as tbl, count(*) as copies,
78
+ array_agg(indexrelid::regclass) as indexes
79
+ from pg_index
80
+ group by indrelid, indkey::text, indexprs::text, indpred::text
81
+ having count(*) > 1;
82
+
83
+ -- 8. Work spilling to disk. temp_bytes is a first-class latency signal.
84
+ select temp_files, pg_size_pretty(temp_bytes) as temp_written,
85
+ blks_read, blks_hit,
86
+ round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) as cache_hit_pct,
87
+ deadlocks
88
+ from pg_stat_database
89
+ where datname = current_database();
90
+
91
+ select round((total_exec_time / 1000)::numeric, 0) as sec, calls,
92
+ round((temp_blks_written * 8192 / 1e9)::numeric, 2) as temp_gb,
93
+ left(regexp_replace(query, '\s+', ' ', 'g'), 140) as statement
94
+ from pg_stat_statements
95
+ where temp_blks_written > 0
96
+ order by temp_blks_written desc
97
+ limit 10;
98
+
99
+ -- 9. TOAST ratio. A large gap between total size and heap+index size means the
100
+ -- row is wide, and any predicate on the wide column de-TOASTs on every scan.
101
+ select c.relname,
102
+ pg_size_pretty(pg_total_relation_size(c.oid)) as total,
103
+ pg_size_pretty(pg_relation_size(c.oid)) as heap,
104
+ pg_size_pretty(pg_indexes_size(c.oid)) as indexes,
105
+ pg_size_pretty(pg_total_relation_size(c.oid)
106
+ - pg_relation_size(c.oid)
107
+ - pg_indexes_size(c.oid)) as toast,
108
+ s.seq_scan
109
+ from pg_class c
110
+ join pg_namespace n on n.oid = c.relnamespace and n.nspname = 'public'
111
+ left join pg_stat_user_tables s on s.relid = c.oid
112
+ where c.relkind = 'r'
113
+ order by pg_total_relation_size(c.oid) desc
114
+ limit 15;
115
+
116
+ -- 10. Column population contract check. Any column a reader consumes that comes
117
+ -- back 100% empty on a populated table is a write-contract gap. Generates
118
+ -- the per-column statements. Set the table filter first:
119
+ -- set bl.tbl = 'rss_sources';
120
+ select format(
121
+ 'select %L as col, count(*) as rows, count(%I) as populated from %I.%I;',
122
+ table_name || '.' || column_name, column_name, table_schema, table_name)
123
+ from information_schema.columns
124
+ where table_schema = 'public' and is_nullable = 'YES'
125
+ and table_name = coalesce(current_setting('bl.tbl', true), table_name)
126
+ order by table_name, ordinal_position;