@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.
- package/.agents/plugins/marketplace.json +2 -2
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/build-loop-surface.mdc +12 -11
- package/.cursor/rules/skill-index.mdc +33 -0
- package/AGENTS.md +213 -34
- package/README.md +99 -31
- package/agents/advisor.md +4 -4
- package/agents/alignment-checker.md +2 -2
- package/agents/architecture-scout.md +4 -4
- package/agents/build-orchestrator.md +38 -36
- package/agents/database-assessor.md +11 -5
- package/agents/design-contract-specialist.md +8 -8
- package/agents/fact-checker.md +13 -3
- package/agents/fix-critique.md +2 -2
- package/agents/independent-auditor.md +60 -7
- package/agents/leak-scanner.md +82 -0
- package/agents/overfitting-reviewer.md +2 -2
- package/agents/plan-critic.md +1 -1
- package/agents/promotion-reviewer.md +5 -5
- package/agents/retrospective-synthesizer.md +138 -35
- package/agents/scope-auditor.md +82 -11
- package/agents/security-reviewer.md +56 -2
- package/agents/self-improvement-architect.md +17 -3
- package/agents/transcript-pattern-miner.md +5 -5
- package/agents/ui-validator.md +1 -1
- package/bin/build-loop-debugger.js +143 -0
- package/bin/build-loop-install.js +1 -4
- package/bin/build-loop-load-probe.js +345 -0
- package/codex-skills/build-loop/SKILL.md +28 -6
- package/commands/feedback.md +37 -0
- package/dist/src/interactive-verifier.d.ts +1 -14
- package/dist/src/interactive-verifier.d.ts.map +1 -1
- package/dist/src/interactive-verifier.js +6 -113
- package/dist/src/interactive-verifier.js.map +1 -1
- package/dist/src/quality.d.ts +5 -0
- package/dist/src/quality.d.ts.map +1 -0
- package/dist/src/quality.js +81 -0
- package/dist/src/quality.js.map +1 -0
- package/dist/src/storage.d.ts.map +1 -1
- package/dist/src/storage.js +37 -3
- package/dist/src/storage.js.map +1 -1
- package/docs/agent-surface-policy.md +35 -31
- package/docs/memory-setup.md +19 -0
- package/hooks/git/pre-push +65 -4
- package/hooks/hooks.json +95 -38
- package/hooks/pre-commit +20 -1
- package/hooks/pre-edit-rally-point.sh +10 -3
- package/hooks/session-start-codex-hook-trust.sh +30 -0
- package/hooks/session-start-git-hooks.sh +3 -1
- package/hooks/session-start-rally-point.sh +52 -4
- package/hooks/session-start-worktree-gc.sh +47 -94
- package/hooks/stop-transcript-sweep.sh +173 -0
- package/hooks/test_closeout.sh +14 -2
- package/package.json +8 -7
- package/scripts/README.md +1 -1
- package/scripts/_paths.py +65 -0
- package/scripts/groundwork_exchange.py +1012 -0
- package/scripts/install_memory.py +33 -1
- package/scripts/lessons_index/ingest.py +13 -2
- package/scripts/lessons_index/query.py +36 -13
- package/scripts/memory_context/__init__.py +108 -14
- package/scripts/memory_graph/__init__.py +5 -1
- package/scripts/project_resolver.py +42 -36
- package/scripts/sync_plugin_cache.py +37 -2
- package/skills/agent-rally-point/SKILL.md +46 -0
- package/skills/api-registry-bridge/SKILL.md +1 -1
- package/skills/architecture/dead/SKILL.md +1 -1
- package/skills/architecture/impact/SKILL.md +1 -1
- package/skills/architecture/review/SKILL.md +1 -1
- package/skills/architecture/rules/SKILL.md +3 -3
- package/skills/architecture/scan/SKILL.md +1 -1
- package/skills/architecture/trace/SKILL.md +1 -1
- package/skills/attribution-standard/SKILL.md +6 -6
- package/skills/auto-decision-capture/SKILL.md +31 -2
- package/skills/auto-finding-capture/SKILL.md +28 -1
- package/skills/build-loop/SKILL.md +131 -23
- package/skills/build-loop/fallbacks.md +16 -21
- package/skills/build-loop/phases/ui-validation.md +2 -2
- package/skills/build-loop/references/advisor-dispatch-ladder.md +1 -1
- package/skills/build-loop/references/apple-native-planning.md +1 -1
- package/skills/build-loop/references/autonomous-and-per-commit-modes.md +11 -5
- package/skills/build-loop/references/autonomy-dashboard.md +115 -0
- package/skills/build-loop/references/capability-routing.md +24 -2
- package/skills/build-loop/references/coordination.md +24 -6
- package/skills/build-loop/references/experiment-results-template.md +15 -3
- package/skills/build-loop/references/leadership.md +1 -1
- package/skills/build-loop/references/memory.md +14 -3
- package/skills/build-loop/references/modular-systems-pack.md +8 -0
- package/skills/build-loop/references/output-style.md +86 -0
- package/skills/build-loop/references/phase-1-assess.md +102 -2
- package/skills/build-loop/references/phase-2-plan.md +9 -1
- package/skills/build-loop/references/phase-3-execute.md +5 -2
- package/skills/build-loop/references/phase-4-review.md +85 -8
- package/skills/build-loop/references/phase-5-iterate.md +76 -8
- package/skills/build-loop/references/phase-6-learn.md +10 -17
- package/skills/build-loop/references/privileged-request-broker.md +254 -0
- package/skills/build-loop/references/resource-aware-execution.md +183 -0
- package/skills/build-loop/references/self-recursive-dev.md +2 -2
- package/skills/build-loop/references/status-output-format.md +207 -0
- package/skills/build-loop/references/verify-dispatch.md +56 -2
- package/skills/building-with-deepagents/SKILL.md +1 -1
- package/skills/claim-scope/SKILL.md +185 -0
- package/skills/color-engine/SKILL.md +103 -0
- package/skills/color-engine/_core.py +464 -0
- package/skills/color-engine/color_engine.py +175 -0
- package/skills/cost-rca/SKILL.md +61 -0
- package/skills/data-plane-worktrees/SKILL.md +139 -0
- package/skills/data-plane-worktrees/agents/openai.yaml +4 -0
- package/skills/database-practice/SKILL.md +200 -0
- package/skills/database-practice/references/diagnostic-queries.sql +126 -0
- package/skills/database-practice/references/vector-and-graph-tuning.md +208 -0
- package/skills/database-practice/scripts/db_table_map.py +1244 -0
- package/skills/database-practice/scripts/test_db_table_map.py +514 -0
- package/skills/debug-loop/SKILL.md +36 -6
- package/skills/debugging-memory/SKILL.md +32 -430
- package/skills/debugging-memory/references/pattern-extraction.md +4 -4
- package/skills/debugging-memory/references/search.md +32 -120
- package/skills/debugging-memory/references/store.md +32 -126
- package/skills/debugging-memory/references/subagent-integration.md +1 -1
- package/skills/decision-queue/SKILL.md +251 -0
- package/skills/decision-queue/assets/template.html +1242 -0
- package/skills/decision-queue/references/example-large-queue-batching.md +164 -0
- package/skills/decision-queue/scripts/regen_template_constants.py +160 -0
- package/skills/defenseclaw-bridge/SKILL.md +2 -2
- package/skills/defenseclaw-bridge/references/dc-config-mapping.md +2 -9
- package/skills/drain-proposals/SKILL.md +53 -0
- package/skills/focused-loop-builder/SKILL.md +31 -0
- package/skills/focused-loop-builder/references/spec-format.md +27 -0
- package/skills/handoff/SKILL.md +169 -8
- package/skills/ibr-bridge/SKILL.md +4 -1
- package/skills/knowledge/SKILL.md +26 -14
- package/skills/knowledge/references/review-mode.md +2 -3
- package/skills/knowledge/templates/madr-minimal.md +1 -1
- package/skills/mcp-builder/SKILL.md +1 -1
- package/skills/model-bakeoff/SKILL.md +48 -10
- package/skills/model-tiering/SKILL.md +92 -31
- package/skills/native-ax-driver/SKILL.md +38 -5
- package/skills/native-ax-driver/scripts/native_driver.py +278 -22
- package/skills/native-ax-driver/scripts/test_native_driver.py +227 -0
- package/skills/optimize/SKILL.md +1 -1
- package/skills/plugin-builder/SKILL.md +48 -1
- package/skills/plugin-builder/references/build-loop-phase-guidance.md +3 -4
- package/skills/plugin-builder/references/distribution.md +13 -2
- package/skills/plugin-builder/references/plugin-hygiene-lessons.md +2 -2
- package/skills/plugin-tests/SKILL.md +2 -2
- package/skills/recursive-retrospective/SKILL.md +1 -1
- package/skills/repo-closeout/SKILL.md +17 -0
- package/skills/repo-closeout/agents/openai.yaml +4 -0
- package/skills/repo-maintenance/SKILL.md +179 -0
- package/skills/repo-maintenance/agents/openai.yaml +4 -0
- package/skills/repo-maintenance/references/pre-public-hygiene.md +134 -0
- package/skills/repo-maintenance/references/repository-taxonomy.md +161 -0
- package/skills/repo-maintenance/references/safety-protocol.md +106 -0
- package/skills/repo-maintenance/references/stack-profiles.md +138 -0
- package/skills/repo-maintenance/scripts/audit_repo_maintenance.py +1198 -0
- package/skills/repo-maintenance/scripts/test_audit_repo_maintenance.py +506 -0
- package/skills/repository-intelligence/SKILL.md +189 -0
- package/skills/repository-intelligence/agents/openai.yaml +4 -0
- package/skills/repository-intelligence/references/assessment-rubric.md +88 -0
- package/skills/repository-intelligence/scripts/repository_inventory.py +347 -0
- package/skills/research/SKILL.md +12 -2
- package/skills/root-cause-analysis/SKILL.md +1 -1
- package/skills/runtime-parity-verification/SKILL.md +36 -1
- package/skills/security-methodology/SKILL.md +23 -10
- package/skills/security-methodology/references/agentic-handoff-templates.md +220 -0
- package/skills/security-methodology/references/cross-source-matrix.md +1 -1
- package/skills/security-methodology/references/owasp-agentic-top-10.md +1 -1
- package/skills/security-scan/SKILL.md +55 -15
- package/skills/self-improve/SKILL.md +70 -50
- package/skills/silent-assumptions/SKILL.md +341 -0
- package/skills/silent-assumptions/references/elicitation-detectors.md +342 -0
- package/skills/spec-writing/SKILL.md +128 -24
- package/skills/spec-writing/scripts/check_checklist.py +114 -15
- package/skills/ui-design/SKILL.md +6 -4
- package/skills/ui-design/references/color-engine.md +132 -0
- package/skills/ui-design/references/design-preferences-from-owned-apps.md +8 -8
- package/skills/ui-design/references/ui-guidance-sources.md +1 -1
- package/skills/ui-design/references/universal-design-principles.alt.md +2 -2
- package/plugin-artifacts/codex/.codex-plugin/plugin.json +0 -41
- package/plugin-artifacts/codex/AGENTS.md +0 -560
- package/plugin-artifacts/codex/BUILD-ARTIFACT.md +0 -5
- package/plugin-artifacts/codex/LICENSE +0 -202
- package/plugin-artifacts/codex/README.md +0 -313
- package/plugin-artifacts/codex/assets/build-loop-plugin-icon.png +0 -0
- package/plugin-artifacts/codex/docs/agent-surface-policy.md +0 -63
- package/plugin-artifacts/codex/references/advisor-dispatch-ladder.md +0 -62
- package/plugin-artifacts/codex/references/agent-role-taxonomy.md +0 -135
- package/plugin-artifacts/codex/references/autonomous-and-per-commit-modes.md +0 -161
- package/plugin-artifacts/codex/references/autonomy-config.md +0 -231
- package/plugin-artifacts/codex/references/backlog-system.md +0 -285
- package/plugin-artifacts/codex/references/capability-routing.md +0 -231
- package/plugin-artifacts/codex/references/codex-subagents.md +0 -106
- package/plugin-artifacts/codex/references/coordination-file-template.md +0 -181
- package/plugin-artifacts/codex/references/coordination-rules.md +0 -552
- package/plugin-artifacts/codex/references/dogfood-reload-checkpoint.md +0 -112
- package/plugin-artifacts/codex/references/halt-and-ask-protocol.md +0 -102
- package/plugin-artifacts/codex/references/implementer-envelope-schema.md +0 -302
- package/plugin-artifacts/codex/references/intent-capability-pack.md +0 -257
- package/plugin-artifacts/codex/references/intent-exploration-prompts.md +0 -96
- package/plugin-artifacts/codex/references/leadership.md +0 -72
- package/plugin-artifacts/codex/references/memory-systems.md +0 -261
- package/plugin-artifacts/codex/references/memory.md +0 -313
- package/plugin-artifacts/codex/references/model-tier-mapping.md +0 -296
- package/plugin-artifacts/codex/references/modular-systems-pack.md +0 -96
- package/plugin-artifacts/codex/references/phase-1-assess.md +0 -249
- package/plugin-artifacts/codex/references/phase-2-plan.md +0 -86
- package/plugin-artifacts/codex/references/phase-3-execute.md +0 -49
- package/plugin-artifacts/codex/references/phase-4-review.md +0 -341
- package/plugin-artifacts/codex/references/phase-5-iterate.md +0 -72
- package/plugin-artifacts/codex/references/phase-6-learn.md +0 -58
- package/plugin-artifacts/codex/references/recent-design-structures.md +0 -274
- package/plugin-artifacts/codex/references/research-trigger-policy.md +0 -140
- package/plugin-artifacts/codex/references/runtime-smoke-triggers.md +0 -42
- package/plugin-artifacts/codex/references/self-review.md +0 -234
- package/plugin-artifacts/codex/references/single-writer-commit-protocol.md +0 -90
- package/plugin-artifacts/codex/references/task-capture-policy.md +0 -68
- package/plugin-artifacts/codex/references/ui-io-contract.md +0 -116
- package/plugin-artifacts/codex/references/ui-spotcheck-protocol.md +0 -65
- package/plugin-artifacts/codex/references/verify-dispatch.md +0 -85
- package/plugin-artifacts/codex/skills/build-loop/SKILL.md +0 -381
- package/plugin-artifacts/codex/skills/build-loop/detect-plugins.mjs +0 -82
- package/plugin-artifacts/codex/skills/build-loop/eval-guide.md +0 -65
- package/plugin-artifacts/codex/skills/build-loop/fallbacks.md +0 -549
- package/plugin-artifacts/codex/skills/build-loop/phases/fact-check.md +0 -42
- package/plugin-artifacts/codex/skills/build-loop/phases/ui-validation.md +0 -267
- package/plugin-artifacts/codex/skills/build-loop/references/advisor-dispatch-ladder.md +0 -62
- package/plugin-artifacts/codex/skills/build-loop/references/apple-native-planning.md +0 -439
- package/plugin-artifacts/codex/skills/build-loop/references/autonomous-and-per-commit-modes.md +0 -161
- package/plugin-artifacts/codex/skills/build-loop/references/capability-routing.md +0 -231
- package/plugin-artifacts/codex/skills/build-loop/references/codex-subagents.md +0 -106
- package/plugin-artifacts/codex/skills/build-loop/references/coordination.md +0 -161
- package/plugin-artifacts/codex/skills/build-loop/references/correction-aware-capture.md +0 -177
- package/plugin-artifacts/codex/skills/build-loop/references/experiment-results-template.md +0 -101
- package/plugin-artifacts/codex/skills/build-loop/references/independent-auditor.md +0 -72
- package/plugin-artifacts/codex/skills/build-loop/references/intent-capability-pack.md +0 -257
- package/plugin-artifacts/codex/skills/build-loop/references/intent-exploration-prompts.md +0 -96
- package/plugin-artifacts/codex/skills/build-loop/references/leadership.md +0 -72
- package/plugin-artifacts/codex/skills/build-loop/references/memory.md +0 -313
- package/plugin-artifacts/codex/skills/build-loop/references/modular-systems-pack.md +0 -96
- package/plugin-artifacts/codex/skills/build-loop/references/output-style.md +0 -222
- package/plugin-artifacts/codex/skills/build-loop/references/pay-it-forward-arch.md +0 -98
- package/plugin-artifacts/codex/skills/build-loop/references/phase-1-assess.md +0 -249
- package/plugin-artifacts/codex/skills/build-loop/references/phase-2-plan.md +0 -86
- package/plugin-artifacts/codex/skills/build-loop/references/phase-3-execute.md +0 -49
- package/plugin-artifacts/codex/skills/build-loop/references/phase-4-review.md +0 -341
- package/plugin-artifacts/codex/skills/build-loop/references/phase-5-iterate.md +0 -72
- package/plugin-artifacts/codex/skills/build-loop/references/phase-6-learn.md +0 -58
- package/plugin-artifacts/codex/skills/build-loop/references/recent-design-structures.md +0 -274
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/ASSESSMENT.md +0 -85
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +0 -149
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +0 -32
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +0 -48
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +0 -60
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +0 -51
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +0 -71
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +0 -52
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/trace-comparison.md +0 -202
- package/plugin-artifacts/codex/skills/build-loop/references/self-recursive-dev.md +0 -77
- package/plugin-artifacts/codex/skills/build-loop/references/self-review.md +0 -234
- package/plugin-artifacts/codex/skills/build-loop/references/ui-io-contract.md +0 -116
- package/plugin-artifacts/codex/skills/build-loop/references/verify-dispatch.md +0 -85
- package/plugin-artifacts/codex/skills/build-loop/scanners/audit-design-rules.mjs +0 -476
- package/plugin-artifacts/codex/skills/build-loop/scanners/require-visual-evidence.mjs +0 -239
- package/plugin-artifacts/codex/skills/build-loop/templates/backlog-item.md +0 -35
- package/plugin-artifacts/codex/skills/build-loop/templates/codex-worker-prompt.md +0 -100
- package/plugin-artifacts/codex/skills/build-loop/templates/ui-subagent-prompt.md +0 -179
- package/plugin-artifacts/codex/skills/build-loop/templates/ux-fix-plan.md +0 -40
- 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,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;
|