@mrciphersmith/keryx 0.3.2 → 0.3.3

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 (100) hide show
  1. package/dist/cli.js +4634 -2445
  2. package/dist/core.js +66 -10
  3. package/package.json +1 -1
  4. package/src/gdskills/bundled/install-manifest.json +349 -2
  5. package/src/gdskills/bundled/rules/core/model-selection.mdc +18 -0
  6. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
  7. package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.md +1 -1
  8. package/src/gdskills/bundled/skills/planning/interviewer/SKILL.md +1 -1
  9. package/src/gdskills/bundled/skills/quality/deploy/SKILL.md +1 -1
  10. package/src/gdskills/bundled/skills/review/review-jev-contract/SKILL.md +193 -0
  11. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +81 -21
  12. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +4 -4
  13. package/src/gdskills/bundled/stacks/c-cpp/agent-refs.json +4 -0
  14. package/src/gdskills/bundled/stacks/c-cpp/governance/eval.json +1777 -0
  15. package/src/gdskills/bundled/stacks/c-cpp/governance/scout.json +31 -0
  16. package/src/gdskills/bundled/stacks/c-cpp/pack.json +42 -0
  17. package/src/gdskills/bundled/stacks/c-cpp/rules/coding-style.mdc +80 -0
  18. package/src/gdskills/bundled/stacks/c-cpp/rules/patterns.mdc +87 -0
  19. package/src/gdskills/bundled/stacks/c-cpp/rules/security.mdc +90 -0
  20. package/src/gdskills/bundled/stacks/c-cpp/rules/testing.mdc +83 -0
  21. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/SKILL.md +153 -0
  22. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/evals.json +74 -0
  23. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/SKILL.md +132 -0
  24. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/evals.json +73 -0
  25. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/SKILL.md +151 -0
  26. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/evals.json +74 -0
  27. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/SKILL.md +152 -0
  28. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/evals.json +74 -0
  29. package/src/gdskills/bundled/stacks/ci-github-gitlab/agent-refs.json +4 -0
  30. package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/eval.json +1295 -0
  31. package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/scout.json +26 -0
  32. package/src/gdskills/bundled/stacks/ci-github-gitlab/pack.json +41 -0
  33. package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/patterns.mdc +77 -0
  34. package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/security.mdc +144 -0
  35. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/SKILL.md +121 -0
  36. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/evals.json +73 -0
  37. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/SKILL.md +139 -0
  38. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/evals.json +73 -0
  39. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/SKILL.md +147 -0
  40. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/evals.json +74 -0
  41. package/src/gdskills/bundled/stacks/docker-k8s-terraform/agent-refs.json +4 -0
  42. package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/eval.json +865 -0
  43. package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/scout.json +16 -0
  44. package/src/gdskills/bundled/stacks/docker-k8s-terraform/pack.json +46 -0
  45. package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/coding-style.mdc +74 -0
  46. package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/patterns.mdc +81 -0
  47. package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/security.mdc +146 -0
  48. package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/testing.mdc +61 -0
  49. package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/SKILL.md +151 -0
  50. package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/evals.json +74 -0
  51. package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/SKILL.md +135 -0
  52. package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/evals.json +76 -0
  53. package/src/gdskills/bundled/stacks/php-laravel/agent-refs.json +4 -0
  54. package/src/gdskills/bundled/stacks/php-laravel/governance/eval.json +1829 -0
  55. package/src/gdskills/bundled/stacks/php-laravel/governance/scout.json +33 -0
  56. package/src/gdskills/bundled/stacks/php-laravel/pack.json +41 -0
  57. package/src/gdskills/bundled/stacks/php-laravel/rules/coding-style.mdc +82 -0
  58. package/src/gdskills/bundled/stacks/php-laravel/rules/patterns.mdc +80 -0
  59. package/src/gdskills/bundled/stacks/php-laravel/rules/security.mdc +80 -0
  60. package/src/gdskills/bundled/stacks/php-laravel/rules/testing.mdc +82 -0
  61. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/SKILL.md +143 -0
  62. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/evals.json +74 -0
  63. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/SKILL.md +126 -0
  64. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/evals.json +76 -0
  65. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/SKILL.md +140 -0
  66. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/evals.json +75 -0
  67. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/SKILL.md +124 -0
  68. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/evals.json +74 -0
  69. package/src/gdskills/bundled/stacks/ruby-rails/agent-refs.json +4 -0
  70. package/src/gdskills/bundled/stacks/ruby-rails/governance/eval.json +1673 -0
  71. package/src/gdskills/bundled/stacks/ruby-rails/governance/scout.json +33 -0
  72. package/src/gdskills/bundled/stacks/ruby-rails/pack.json +42 -0
  73. package/src/gdskills/bundled/stacks/ruby-rails/rules/coding-style.mdc +69 -0
  74. package/src/gdskills/bundled/stacks/ruby-rails/rules/patterns.mdc +93 -0
  75. package/src/gdskills/bundled/stacks/ruby-rails/rules/security.mdc +90 -0
  76. package/src/gdskills/bundled/stacks/ruby-rails/rules/testing.mdc +89 -0
  77. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/SKILL.md +143 -0
  78. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/evals.json +73 -0
  79. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/SKILL.md +134 -0
  80. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/evals.json +71 -0
  81. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/SKILL.md +141 -0
  82. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/evals.json +72 -0
  83. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/SKILL.md +125 -0
  84. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/evals.json +72 -0
  85. package/src/gdskills/bundled/stacks/sql-db/agent-refs.json +4 -0
  86. package/src/gdskills/bundled/stacks/sql-db/governance/eval.json +1829 -0
  87. package/src/gdskills/bundled/stacks/sql-db/governance/scout.json +30 -0
  88. package/src/gdskills/bundled/stacks/sql-db/pack.json +40 -0
  89. package/src/gdskills/bundled/stacks/sql-db/rules/coding-style.mdc +69 -0
  90. package/src/gdskills/bundled/stacks/sql-db/rules/patterns.mdc +134 -0
  91. package/src/gdskills/bundled/stacks/sql-db/rules/security.mdc +74 -0
  92. package/src/gdskills/bundled/stacks/sql-db/rules/testing.mdc +83 -0
  93. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/SKILL.md +147 -0
  94. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/evals.json +72 -0
  95. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/SKILL.md +132 -0
  96. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/evals.json +73 -0
  97. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/SKILL.md +153 -0
  98. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/evals.json +77 -0
  99. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/SKILL.md +129 -0
  100. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/evals.json +73 -0
@@ -0,0 +1,30 @@
1
+ [
2
+ {
3
+ "query": "Use when writing SQL schema, migrations, or queries for Postgres/MySQL -- safe zero-downtime schema changes (adding NOT NULL, backfills), indexing (composite column order, covering/partial indexes), transaction discipline, and parameterized query design.",
4
+ "decision": "create",
5
+ "topMatch": "sql-db/sql-db-testing",
6
+ "recordedAt": "2026-09-25T15:30:40.157Z",
7
+ "skillName": "sql-db-implementation"
8
+ },
9
+ {
10
+ "query": "Use when writing or fixing tests for SQL migrations and queries -- transactional test wrapping with rollback, fixture/seed data design, testing a migration's up/down path on representative row counts, and asserting on actual query results and query counts rather than just 'no error'.",
11
+ "decision": "create",
12
+ "topMatch": "quality/fresh-eyes",
13
+ "recordedAt": "2026-09-25T15:30:44.924Z",
14
+ "skillName": "sql-db-testing"
15
+ },
16
+ {
17
+ "query": "Use when reviewing a migration or query diff for SQL/database-specific risk -- injection (string-built SQL, ORM raw-SQL escape hatches), missing/misordered indexes, an unsafe blocking NOT NULL or index-creation statement, N+1 query shape, and transactions left open across a slow call. Read-only, no edits.",
18
+ "decision": "create",
19
+ "topMatch": "php-laravel/php-laravel-code-review",
20
+ "recordedAt": "2026-09-25T15:30:47.232Z",
21
+ "skillName": "sql-db-code-review"
22
+ },
23
+ {
24
+ "query": "Use when a migration fails to apply, a query planner regresses to a full table scan, or a constraint violation blocks a deploy -- resolves failed/broken migrations, missing-index query regressions, deadlocks, and NOT NULL/unique/foreign-key constraint violations with the smallest root-cause fix.",
25
+ "decision": "create",
26
+ "topMatch": "sql-db/sql-db-implementation",
27
+ "recordedAt": "2026-09-25T15:30:49.254Z",
28
+ "skillName": "sql-db-build-fix"
29
+ }
30
+ ]
@@ -0,0 +1,40 @@
1
+ {
2
+ "id": "sql-db",
3
+ "family": "capability",
4
+ "modules": ["sql-db-rules", "sql-db-skills"],
5
+ "detectionMarkers": ["sql"],
6
+ "provenance": {
7
+ "origin": "authored",
8
+ "sourceRef": "flow 337, Wave 4 batch 5"
9
+ },
10
+ "stability": "experimental",
11
+ "skills": {
12
+ "implement": ["sql-db-implementation"],
13
+ "test": ["sql-db-testing"],
14
+ "review": ["sql-db-code-review"],
15
+ "build-fix": ["sql-db-build-fix"],
16
+ "migrate": []
17
+ },
18
+ "agentProfile": {
19
+ "displayName": "SQL / Database",
20
+ "auditFocus": [
21
+ "a query built with string concatenation or f-string/template interpolation of a user-influenced value instead of a parameterized placeholder or prepared statement, including inside an ORM's raw-SQL/raw-query escape hatch",
22
+ "an ADD COLUMN ... NOT NULL with no default, or a SET NOT NULL with no prior NOT VALID CHECK + VALIDATE CONSTRAINT step, on a table that can have existing rows",
23
+ "a WHERE/JOIN/ORDER BY column with no supporting index, or a composite index whose column order does not match the query's actual equality-then-range filter shape",
24
+ "a loop that issues one query per row (a classic N+1) where a JOIN, an IN-clause batch, or eager loading would do it in one round trip",
25
+ "a migration with no reverse/down operation, or one that is not safe to run twice (missing IF EXISTS/IF NOT EXISTS on a DDL statement that isn't naturally idempotent)",
26
+ "a transaction held open across a slow external call (an HTTP request, a queue publish, a sleep) instead of being committed before or reopened after it"
27
+ ],
28
+ "buildCommands": [
29
+ "EXPLAIN (ANALYZE, BUFFERS) <query> -- Postgres: confirm no unexpected sequential scan on the touched tables",
30
+ "EXPLAIN ANALYZE <query> -- MySQL: confirm the query uses the intended index",
31
+ "the project's own migration runner in dry-run/plan mode when one exists, before applying against a real database"
32
+ ],
33
+ "fixGuardrails": [
34
+ "Never fix a slow query by adding an index without checking its column order against the query's actual WHERE/JOIN/ORDER BY shape first.",
35
+ "Never rewrite a parameterized query into a string-concatenated one to work around a driver quirk; fix the parameter binding instead.",
36
+ "Never resolve a migration failure by dropping the failing constraint or making a NOT NULL column nullable again without saying so explicitly in the report.",
37
+ "Never skip or delete a migration step to reach a green migration run."
38
+ ]
39
+ }
40
+ }
@@ -0,0 +1,69 @@
1
+ ---
2
+ extends: common
3
+ paths: ["**/*.sql"]
4
+ metadata:
5
+ origin: authored
6
+ ---
7
+
8
+ # SQL coding style
9
+
10
+ Narrows `core-common-rules`' stack-agnostic style rules to raw `.sql` files
11
+ (schema definitions, migrations, hand-written query files) across Postgres,
12
+ MySQL, and generic SQL engines. Applies only to `*.sql` files — application
13
+ code that embeds SQL as strings (a Python/Go/JS query builder call) is
14
+ covered by that language's own stack pack, not this one.
15
+
16
+ ## Naming
17
+
18
+ - Tables and columns are `snake_case`, plural for tables (`orders`,
19
+ `order_items`), singular for columns (`user_id`, not `user_ids` on a
20
+ scalar foreign key) — match whatever convention the project's existing
21
+ schema already uses instead of introducing a second one.
22
+ - Name a foreign-key column `<singular_referenced_table>_id`
23
+ (`orders.user_id` referencing `users.id`), so its relationship is
24
+ readable without opening the schema.
25
+ - Name an index `idx_<table>_<column(s)>` and a unique constraint
26
+ `uq_<table>_<column(s)>`; name a foreign key constraint
27
+ `fk_<table>_<referenced_table>` — explicit names are required so a
28
+ failing constraint's error message names something a reader recognizes,
29
+ instead of an engine-generated identifier like `orders_user_id_fkey1`.
30
+
31
+ ## Formatting
32
+
33
+ - One clause per line for anything beyond a trivial single-table query —
34
+ `SELECT`, `FROM`, `WHERE`, `GROUP BY`, `ORDER BY` each start their own
35
+ line, with join conditions immediately under their `JOIN`. A query dense
36
+ enough to need review is dense enough to need this shape.
37
+ - Keywords uppercase (`SELECT`, `FROM`, `WHERE`, `JOIN`), identifiers
38
+ lowercase — do not mix case within one file's own convention.
39
+ - Explicit `JOIN ... ON` conditions, never an implicit comma join
40
+ (`FROM a, b WHERE a.id = b.a_id`) — the join type and its condition
41
+ belong together, not split across `FROM` and `WHERE`.
42
+ - Every migration file's DDL statements are wrapped in the smallest
43
+ transaction the engine allows (Postgres: a single implicit transaction
44
+ per migration is usually safe except around `CREATE INDEX CONCURRENTLY`,
45
+ which cannot run inside a transaction block at all — split it into its
46
+ own migration step rather than wrapping it anyway).
47
+
48
+ ## Column and type choices
49
+
50
+ - Pick the narrowest correct type: an `enum`/`CHECK`-constrained text
51
+ column over a free `VARCHAR` for a closed set of values, `TIMESTAMPTZ`
52
+ (Postgres) / a UTC-normalized `DATETIME` (MySQL) over a naive local-time
53
+ column, `NUMERIC`/`DECIMAL` for money — never `FLOAT`/`DOUBLE` for a
54
+ value that must round exactly.
55
+ - A nullable column means "this can genuinely be absent," not "I didn't
56
+ decide yet" — a column with no legitimate absent case gets `NOT NULL`
57
+ from the migration that introduces it (with a default, or through the
58
+ safe multi-step pattern in `rules/patterns.mdc` when the table already
59
+ has rows).
60
+
61
+ ## Comments and documentation
62
+
63
+ - A non-obvious index (a partial index's predicate, a composite index
64
+ whose column order looks surprising) gets a one-line comment stating
65
+ which query it serves — an index with no stated purpose is a maintenance
66
+ liability the next person can't safely drop or reorder.
67
+ - A migration that is not naturally reversible (a data-destructive
68
+ backfill, a column drop) states its rollback plan or its "no rollback,
69
+ here is why" reasoning in a comment at the top of the file.
@@ -0,0 +1,134 @@
1
+ ---
2
+ extends: common
3
+ paths: ["**/*.sql"]
4
+ metadata:
5
+ origin: authored
6
+ ---
7
+
8
+ # SQL patterns
9
+
10
+ Narrows `core-common-rules`' stack-agnostic design guidance to migration
11
+ safety, query design, and indexing for `.sql` files across Postgres,
12
+ MySQL, and generic SQL engines. Extends the general N+1/index/migration
13
+ guidance in the core `database-patterns.mdc` rule with engine-specific
14
+ mechanics for the zero-downtime cases that rule states only as a general
15
+ three-step pattern. Applies only to `*.sql` files.
16
+
17
+ ## Zero-downtime schema changes
18
+
19
+ - **Adding a nullable column** is always safe as a single migration on
20
+ both engines.
21
+ - **Adding a `NOT NULL` column with a constant default** does not require
22
+ a full table rewrite on modern Postgres (11+) or MySQL (8.0+, via
23
+ `ALGORITHM=INSTANT` for an appended column) — prefer `ADD COLUMN ...
24
+ NOT NULL DEFAULT <constant>` directly over the older three-step
25
+ add-nullable/backfill/constrain dance when every existing and new row
26
+ can share the same default value.
27
+ - **Adding `NOT NULL` to an existing column that needs a real backfill**
28
+ (not a constant default) still needs the multi-step sequence: add the
29
+ column nullable, deploy code that writes it on every insert/update,
30
+ backfill existing rows in batches (not one giant `UPDATE`, which holds
31
+ locks and generates a huge amount of WAL/binlog), then constrain.
32
+ - Postgres: prefer `ALTER TABLE t ADD CONSTRAINT t_col_not_null CHECK
33
+ (col IS NOT NULL) NOT VALID;` followed by a separate `ALTER TABLE t
34
+ VALIDATE CONSTRAINT t_col_not_null;` over a direct `ALTER TABLE t
35
+ ALTER COLUMN col SET NOT NULL` — `NOT VALID` skips the initial
36
+ table scan (applies only to new rows immediately), and
37
+ `VALIDATE CONSTRAINT` then scans under `SHARE UPDATE EXCLUSIVE`,
38
+ which does not block concurrent reads/writes the way the scan inside
39
+ a plain `SET NOT NULL` does.
40
+ - MySQL: `ALTER TABLE t MODIFY COLUMN col <type> NOT NULL` is an
41
+ in-place operation but still requires a full table scan/rebuild on
42
+ most storage engines — schedule it like any table-rewriting DDL
43
+ (off-peak, or through the project's online-migration tool such as
44
+ `gh-ost`/gantry-style tooling if one is already in use).
45
+ - **Adding an index on an existing table**: Postgres —
46
+ `CREATE INDEX CONCURRENTLY` (cannot run inside a transaction block; put
47
+ it in its own migration step) to avoid the `ACCESS EXCLUSIVE`-adjacent
48
+ write lock a plain `CREATE INDEX` takes. MySQL — `CREATE INDEX` on
49
+ InnoDB uses `ALGORITHM=INPLACE` by default for most index additions,
50
+ which permits concurrent DML; verify with `ALGORITHM=INPLACE, LOCK=NONE`
51
+ explicitly if the table is under sustained write load.
52
+ - **Renaming or changing a column's type**: no engine makes this safe as
53
+ one step under a rolling deploy — add the new column, dual-write from
54
+ application code, backfill, cut reads over, then drop the old column in
55
+ a later migration once nothing reads it.
56
+ - **Dropping a column or table**: stop reading/writing it from application
57
+ code first (a full deploy cycle), then drop it in a subsequent
58
+ migration — dropping while old code still reads the column breaks that
59
+ code immediately on deploy overlap.
60
+
61
+ ## Indexing
62
+
63
+ - Index every foreign key column and every column that appears in a
64
+ `WHERE`, `JOIN ON`, or `ORDER BY` clause on a table large enough that a
65
+ sequential/full-table scan would be noticeable — confirm with
66
+ `EXPLAIN ANALYZE` on production-representative data volume, not on an
67
+ empty dev table where every plan looks fast.
68
+ - **Composite index column order**: put equality-filtered columns before
69
+ range-filtered ones, and put the columns a query's `WHERE` actually
70
+ restricts before columns only used for `ORDER BY`. An index on
71
+ `(status, created_at)` serves `WHERE status = 'open' ORDER BY
72
+ created_at` efficiently; the same index in the order `(created_at,
73
+ status)` cannot use the equality filter to narrow the scan first and
74
+ degrades toward a broader range scan.
75
+ - **Covering indexes**: when a query only ever selects a handful of
76
+ columns already present in an index's key or `INCLUDE`/covered columns
77
+ (Postgres `INCLUDE`, or MySQL's implicit covering when all selected
78
+ columns are in the index), the engine can satisfy it as an index-only
79
+ scan without touching the table's heap/pages at all — worth adding
80
+ narrowly for a hot, narrow query, not as a default habit.
81
+ - **An index has a write cost**: every index is maintained on every
82
+ insert/update/delete touching its columns. Do not add a composite index
83
+ for every column combination a report might someday filter by; add the
84
+ ones actual queries use, and periodically check for unused indexes
85
+ (`pg_stat_user_indexes.idx_scan = 0` on Postgres) before a schema
86
+ accumulates dead weight.
87
+ - A partial index (`CREATE INDEX ... WHERE <predicate>`, Postgres) is
88
+ narrower and cheaper to maintain than a full index when a query always
89
+ filters on a fixed condition (`WHERE deleted_at IS NULL`,
90
+ `WHERE status = 'active'`) — prefer it over a full index whose non-
91
+ matching rows the query never touches.
92
+
93
+ ## Query design
94
+
95
+ - A query issued inside an application-code loop, once per row of an
96
+ outer result set, is an N+1 — replace it with a single `JOIN`, an
97
+ `IN (...)` batch, or the ORM's eager-load option before merging. This
98
+ narrows the core `database-patterns.mdc` N+1 rule to the SQL shape
99
+ itself: the fix is a query that returns everything needed in one round
100
+ trip, not a cache layered on top of the repeated queries.
101
+ - Select only the columns a caller actually needs — `SELECT *` in a
102
+ hand-written `.sql` file breaks silently when a column is added later
103
+ and can leak a sensitive column (a password hash, a token) into a
104
+ result set nothing downstream expected to see.
105
+ - Wrap a multi-statement write (anything touching more than one table, or
106
+ more than one row-affecting statement against the same table) in an
107
+ explicit transaction — `BEGIN`/`COMMIT` or the driver's transaction
108
+ API — so a failure partway through rolls back cleanly instead of
109
+ leaving related tables inconsistent.
110
+ - Keep a transaction's lifetime to the database work alone — commit
111
+ before making a slow external call (an HTTP request, a queue publish,
112
+ a `sleep`), or open a fresh transaction after it completes. A
113
+ transaction held open across an external call holds its locks for the
114
+ external call's entire duration, which can stall every other writer
115
+ touching the same rows.
116
+ - Prefer `UPSERT` (Postgres `INSERT ... ON CONFLICT DO UPDATE`, MySQL
117
+ `INSERT ... ON DUPLICATE KEY UPDATE`) over a read-then-decide-insert-or-
118
+ update round trip when the goal is "write this row whether or not it
119
+ already exists" — the read-then-write version races under concurrent
120
+ writers even inside a transaction unless it also takes an explicit lock.
121
+
122
+ ## Migration hygiene
123
+
124
+ - Every migration that mutates existing data (not just schema) states its
125
+ reverse operation, or explicitly states there isn't one and why (a
126
+ destructive backfill, a data transformation that can't be inverted).
127
+ - Make DDL idempotent where the engine allows it (`IF NOT EXISTS` on
128
+ `CREATE TABLE`/`CREATE INDEX`, `IF EXISTS` on `DROP`) so a migration
129
+ runner that re-applies a partially-run migration after a crash does not
130
+ fail on an object that already exists.
131
+ - Never combine a long-running backfill and a blocking schema change in
132
+ the same migration step — split them so the schema change (fast) and
133
+ the backfill (potentially slow, batched) can be reviewed and rolled
134
+ back independently.
@@ -0,0 +1,74 @@
1
+ ---
2
+ extends: common
3
+ paths: ["**/*.sql"]
4
+ metadata:
5
+ origin: authored
6
+ ---
7
+
8
+ # SQL security
9
+
10
+ Narrows `core-common-rules`' stack-agnostic security rules to injection
11
+ safety and access control for `.sql` files across Postgres, MySQL, and
12
+ generic SQL engines. Applies only to `*.sql` files — parameterized-query
13
+ enforcement in application code (the driver/ORM call site) is covered by
14
+ that language's own stack pack; this file covers the `.sql` side (stored
15
+ procedures, functions, views) and the shared vocabulary both sides share.
16
+
17
+ ## Injection
18
+
19
+ - Never build a query by concatenating or string-formatting a
20
+ user-influenced value into SQL text — this applies inside a stored
21
+ procedure or function body exactly as it does in application code: a
22
+ dynamic `EXECUTE`/`EXEC` built from concatenated input is injectable
23
+ even though the vulnerable code lives in the database, not the app.
24
+ - Postgres `EXECUTE`/`format()` inside `PL/pgSQL`: use `format('...%I...',
25
+ identifier)` for identifiers (table/column names that must be dynamic)
26
+ and `format('...%L...', value)` or a parameterized `USING` clause for
27
+ values — `%I`/`%L` quote-escape correctly; plain string concatenation
28
+ of either does not.
29
+ - MySQL stored procedures: build dynamic SQL with `PREPARE stmt FROM
30
+ @sql; EXECUTE stmt USING @param;` — bind user-influenced values through
31
+ `USING`, never interpolate them into `@sql` itself before `PREPARE`.
32
+ - An ORM or query builder's raw-SQL escape hatch (a `.raw()`, `.exec()`,
33
+ or template-literal query method) carries the same rule as a
34
+ hand-written query: pass user-influenced values through its
35
+ placeholder/parameter API, never through string interpolation into the
36
+ raw SQL text, even when the surrounding call looks like it's "just
37
+ building a string for convenience."
38
+ - A dynamic identifier (a table or column name chosen at runtime, common
39
+ in multi-tenant schema-per-tenant designs or admin tooling) cannot go
40
+ through a value placeholder at all — placeholders bind values, not
41
+ identifiers. Validate it against an allowlist of known-safe identifiers
42
+ before using it, never trust it as free text even when it "came from
43
+ our own config."
44
+
45
+ ## Access control
46
+
47
+ - Grant the narrowest role/privilege a connection actually needs — an
48
+ application's runtime database user should not hold `DROP`/`CREATE`/
49
+ superuser privileges it never uses; a migration runner's credentials
50
+ (which do need DDL rights) should be separate from the application's
51
+ runtime connection.
52
+ - Use row-level security (Postgres `CREATE POLICY` on a table with `ROW
53
+ LEVEL SECURITY` enabled) or an equivalent application-enforced tenant
54
+ filter consistently — do not rely on every query remembering to add
55
+ `WHERE tenant_id = ?` by convention alone when the engine can enforce it
56
+ structurally.
57
+ - A view or stored procedure that runs with elevated privileges
58
+ (Postgres `SECURITY DEFINER`, a MySQL `DEFINER`-owned routine) is a
59
+ privilege-escalation surface if it accepts unvalidated input that
60
+ influences what it reads or writes — treat its parameters with the same
61
+ injection discipline as a top-level query, since callers with lower
62
+ privileges can reach the elevated code path through it.
63
+
64
+ ## Data exposure
65
+
66
+ - Never `SELECT *` a table containing a sensitive column (password hash,
67
+ API token, PII) into a result set a caller with less trust than the
68
+ query's own privilege level will see — select named columns so adding a
69
+ sensitive column later can't leak silently into an existing query.
70
+ - Encrypt or hash sensitive columns before they reach storage
71
+ (`pgcrypto`'s `crypt()`/`digest()` on Postgres, an application-layer
72
+ hash before insert) rather than relying on transport encryption alone —
73
+ a compromised backup or replica exposes plaintext columns even when
74
+ every connection to the primary was encrypted.
@@ -0,0 +1,83 @@
1
+ ---
2
+ extends: common
3
+ paths: ["**/*.sql"]
4
+ metadata:
5
+ origin: authored
6
+ ---
7
+
8
+ # SQL testing
9
+
10
+ Narrows `core-common-rules`' stack-agnostic testing rules to testing raw
11
+ `.sql` — migrations, schema, and hand-written query files — across
12
+ Postgres, MySQL, and generic SQL engines. Applies only to `*.sql` files;
13
+ testing a query or migration issued from application code (a language's
14
+ ORM test helpers) is covered by that language's own stack pack, and
15
+ should still follow the transactional-wrapping and fixture discipline
16
+ below where that language's test suite talks to a real database.
17
+
18
+ ## Test isolation
19
+
20
+ - Wrap each test in a transaction that is rolled back at teardown
21
+ (`BEGIN` before the test, `ROLLBACK` after) rather than truncating and
22
+ re-seeding tables between tests — rollback is faster and guarantees no
23
+ leftover state regardless of what the test wrote.
24
+ - When a test must exercise something a transaction can't roll back
25
+ cleanly (`CREATE INDEX CONCURRENTLY`, an advisory-lock scenario, a
26
+ cross-transaction concurrency test), isolate it in its own
27
+ database/schema that is torn down and recreated instead of relying on
28
+ transactional rollback for that one case.
29
+ - Never point a test suite at a shared development or staging database —
30
+ a fixed, disposable test database (a local container, an ephemeral
31
+ schema per CI run) so tests don't observe or clobber each other's data.
32
+
33
+ ## Fixtures and seed data
34
+
35
+ - Seed only the rows a given test actually needs, built through the same
36
+ helper/factory the rest of the suite uses — do not duplicate a large
37
+ shared seed script per test file, and do not depend on a global fixture
38
+ a different test also mutates.
39
+ - Name fixture data so a failure is legible (`user_with_expired_token`,
40
+ not `test_user_3`) — a table-driven or parameterized test case should
41
+ carry a descriptive case name for the same reason.
42
+ - A fixture representing a timestamp or "now"-relative value is
43
+ constructed relative to a fixed, injected clock/reference time in the
44
+ test, not `NOW()`/`CURRENT_TIMESTAMP` evaluated live — a live clock
45
+ value makes boundary cases (rows that just expired, rows created "just
46
+ now") flaky depending on when the test happens to run.
47
+
48
+ ## Testing migrations
49
+
50
+ - Test a migration's `up` path against a database seeded to look like
51
+ production before the change: representative row counts for anything
52
+ the migration scans or backfills, not an empty table where every
53
+ approach looks instant.
54
+ - Test the reverse/`down` path when the migration states one — running
55
+ `up` then `down` should leave the schema equivalent to before, and
56
+ running `up` twice (or `down` twice) should not error when the
57
+ migration was written to be idempotent.
58
+ - For a batched backfill migration, test with a row count large enough to
59
+ exercise at least two batches, and assert every row was updated exactly
60
+ once (no row skipped at a batch boundary, none processed twice in a way
61
+ that would double-apply a non-idempotent update).
62
+
63
+ ## Testing queries
64
+
65
+ - Assert on the query's actual result set (rows, values, ordering when
66
+ the query specifies `ORDER BY`), not merely that it executed without
67
+ error — a query testing only "did not throw" would pass even after a
68
+ regression silently returns zero or the wrong rows.
69
+ - For a query added to fix an N+1, assert the query count too (most
70
+ ORMs/test harnesses expose a query counter or log) — a regression test
71
+ for an N+1 fix should fail again if the fix regresses back to per-row
72
+ queries, not just check the final result shape.
73
+ - Test the empty-result case (a `WHERE` that matches nothing) and the
74
+ boundary case for any range condition (`created_at >= X`) explicitly —
75
+ these are exactly where an off-by-one in a range filter or a `JOIN`
76
+ that should have been a `LEFT JOIN` shows up.
77
+
78
+ ## Verification
79
+
80
+ - Run the full suite against the transactional-rollback isolation
81
+ described above before considering a change to `.sql` test fixtures or
82
+ migration tests done — a test that passes in isolation but leaves state
83
+ for the next test to trip over is not actually isolated.
@@ -0,0 +1,147 @@
1
+ ---
2
+ name: sql-db-build-fix
3
+ description: "Use when a raw SQL migration fails to apply, a query planner regresses to a full table scan, or a constraint violation blocks a deploy -- resolves failed/broken migrations, missing-index query regressions, deadlocks, and NOT NULL/unique/foreign-key constraint violations with the smallest root-cause fix. Not for a Django/Rails/Laravel ORM's own migration file (a .py/.rb/.php migration -- use that stack's own build-fix skill)."
4
+ triggers:
5
+ - "this migration is failing to apply, help me fix it"
6
+ - "this query used to use the index and now does a sequential scan"
7
+ - "I'm getting a unique constraint violation running this migration"
8
+ - "this migration is failing with a foreign key violation"
9
+ - "the query planner stopped using this index after the last deploy"
10
+ - "two migrations conflict and one fails to apply"
11
+ metadata:
12
+ origin: authored
13
+ category: build-fix
14
+ version: "1.0.0"
15
+ compatible_harnesses: "claude,codex,cursor,zed,opencode"
16
+ license: "MIT"
17
+ ---
18
+
19
+ # SQL / database build fix (Postgres, MySQL, generic SQL)
20
+
21
+ Resolve a failed or broken migration, a query planner regression (an
22
+ index that stopped being used), or a constraint violation blocking a
23
+ deploy — with the smallest change that fixes the actual root cause.
24
+ `rules/patterns.mdc` and `rules/security.mdc` govern what a "correct" fix
25
+ looks like; this skill never reaches for a suppression (dropping a
26
+ constraint, skipping a migration step) instead of a fix.
27
+
28
+ ## Workflow
29
+
30
+ ### Step 1: Reproduce and classify
31
+
32
+ Run the migration/query against a local or staging database that mirrors
33
+ the failure, and read the exact error text. Classify it:
34
+
35
+ - **Migration apply failure** — a DDL statement errors (object already
36
+ exists, object doesn't exist, syntax error, a lock timeout).
37
+ - **Constraint violation** — `NOT NULL`, `UNIQUE`, `CHECK`, or foreign-key
38
+ violation raised while running a migration's backfill or while the
39
+ application inserts/updates a row.
40
+ - **Planner regression** — a query that used an index before now runs a
41
+ sequential/full-table scan (`EXPLAIN`/`EXPLAIN ANALYZE` shows the
42
+ changed plan).
43
+ - **Deadlock/lock timeout** — two transactions each holding a lock the
44
+ other needs, or a migration's lock request timing out against live
45
+ traffic.
46
+ - **Migration ordering conflict** — two migrations both claim the same
47
+ version/sequence number, or one depends on schema state a
48
+ not-yet-applied migration would create.
49
+
50
+ ### Step 2: Fix by category
51
+
52
+ **Migration apply failure:** read the exact error. "Object already
53
+ exists" on a re-run usually means the migration isn't idempotent — add
54
+ `IF NOT EXISTS`/`IF EXISTS` where the engine supports it, or check
55
+ whether a previous partial run needs manual cleanup before retrying
56
+ (state which, in the report). Never just delete/skip the migration file
57
+ to make the runner stop complaining.
58
+
59
+ **Constraint violation on backfill or insert:** find the actual row(s)
60
+ violating the constraint — do not disable or drop the constraint to make
61
+ the error go away. A `NOT NULL` violation during backfill usually means
62
+ the backfill's default/derivation logic missed a case (a row with a
63
+ legitimately different history); a `UNIQUE` violation usually means
64
+ duplicate data existed before the constraint was added — decide the
65
+ correct resolution (merge, dedupe, or a documented exception) rather than
66
+ loosening the constraint to accept invalid data.
67
+
68
+ **Planner regression (unused index):** confirm with
69
+ `EXPLAIN (ANALYZE, BUFFERS)` (Postgres) / `EXPLAIN ANALYZE` (MySQL)
70
+ whether the index still exists, whether statistics are stale
71
+ (`ANALYZE <table>` on Postgres, `ANALYZE TABLE` on MySQL, after a large
72
+ data change), or whether the query's own shape changed in a way that no
73
+ longer matches the index's column order (per `rules/patterns.mdc`'s
74
+ composite-order guidance) — fix the actual cause (re-run `ANALYZE`,
75
+ correct the index shape) rather than forcing a plan with a planner hint
76
+ as the first resort.
77
+
78
+ **Deadlock/lock timeout:** read which two operations held conflicting
79
+ locks (Postgres logs both queries and lock modes in a deadlock error;
80
+ MySQL's `SHOW ENGINE INNODB STATUS` shows the last deadlock). Fix by
81
+ making both code paths acquire locks in the same order, shortening the
82
+ transaction that holds the lock too long, or batching a migration's
83
+ backfill into smaller steps that each commit — not by retrying blindly or
84
+ raising the lock timeout to paper over the contention.
85
+
86
+ **Migration ordering conflict:** renumber/rebase the conflicting
87
+ migration against the current head per the project's migration tool's own
88
+ conflict-resolution convention; never force-apply one migration over
89
+ another's recorded state without understanding what schema each expects.
90
+
91
+ ### Step 3: Verify
92
+
93
+ Re-run the migration end-to-end (up, and down if it has one) against a
94
+ representative dataset. Re-run the regressed query with
95
+ `EXPLAIN`/`EXPLAIN ANALYZE` and confirm the expected plan. Confirm no
96
+ constraint violation remains across the full backfill, not just the row
97
+ that originally failed.
98
+
99
+ ### Step 4: Report
100
+
101
+ ```
102
+ Fixed: migrations/2026_09_18_backfill_status.sql
103
+ - Root cause: 340 legacy rows had status = '' (not NULL) from a prior
104
+ import, which the backfill's WHERE status IS NULL clause missed
105
+ - Extended the backfill condition to also match status = ''
106
+ - Migration re-run clean against a full-size copy of the table
107
+ ```
108
+
109
+ State the root cause in one sentence, not just "fixed the error."
110
+
111
+ ## Rules
112
+
113
+ - Find and fix the smallest change that addresses the actual root cause —
114
+ never widen a fix beyond what the failure requires.
115
+ - NEVER drop or loosen a constraint (`NOT NULL`, `UNIQUE`, a foreign key)
116
+ to make a violation stop erroring instead of fixing the data or the
117
+ logic that produced the violation.
118
+ - NEVER delete or skip a failing migration step to reach a "successful"
119
+ migration run.
120
+ - NEVER force a query plan with a planner hint as the first fix for a
121
+ regression before checking whether statistics are stale or the index
122
+ shape no longer matches the query.
123
+ - NEVER raise a lock/statement timeout to make a deadlock or contention
124
+ error stop appearing without addressing the actual lock ordering or
125
+ transaction duration.
126
+
127
+ ## Red Flags
128
+
129
+ | Rationalization | Why it is wrong |
130
+ |---|---|
131
+ | "I'll just drop the NOT NULL constraint so the insert succeeds" | Silences the violation without fixing why a NULL reached this point — the constraint existed to catch exactly this; find and fix the actual bad data or code path instead |
132
+ | "This migration keeps failing on re-run, I'll delete it and start over" | Deleting a migration that already partially applied against some environments leaves them permanently out of sync with a fresh one; add IF EXISTS/IF NOT EXISTS or fix the idempotency issue instead |
133
+ | "The query got slow, I'll just add a planner hint to force the old plan" | A hint papers over the actual cause (stale statistics, a changed index shape) and can go stale itself the next time data distribution shifts; fix the underlying cause first |
134
+ | "I'll bump the lock timeout so this migration stops timing out" | A longer timeout doesn't resolve the contention, it just waits longer before the same conflict occurs; fix the lock ordering or shrink the transaction instead |
135
+
136
+ ## Verification
137
+
138
+ Do not report the fix done until all of the following hold:
139
+
140
+ - The migration re-runs clean end-to-end (up, and down if applicable)
141
+ against a representative dataset, not just the originally failing row.
142
+ - No constraint was dropped or loosened as part of the fix, unless the
143
+ report explicitly states the constraint itself was wrong and why.
144
+ - For a planner regression, `EXPLAIN`/`EXPLAIN ANALYZE` confirms the
145
+ expected plan after the fix, not just that the query no longer errors.
146
+ - The report states the root cause in one sentence, not just "it works
147
+ now."