@nurix/etna 0.19.55 → 0.19.56

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 (64) hide show
  1. package/data/harness-index.json +1 -1
  2. package/data/harnesses/apollo/CLAUDE.md +29 -30
  3. package/data/harnesses/apollo/harness.json +1 -1
  4. package/data/harnesses/apollo/hooks/approved-packages.md +42 -0
  5. package/data/harnesses/apollo/hooks/compliance.mjs +11 -6
  6. package/data/harnesses/apollo/rules/changelog.md +7 -1
  7. package/data/harnesses/apollo/rules/cloudflare.md +167 -0
  8. package/data/harnesses/apollo/rules/database.md +1 -125
  9. package/data/harnesses/apollo/rules/frontend.md +2 -4
  10. package/data/harnesses/apollo/rules/general.md +20 -42
  11. package/data/harnesses/apollo/rules/nomenclature.md +2 -6
  12. package/data/harnesses/apollo/rules/pnpm.md +2 -2
  13. package/data/harnesses/apollo/rules/testing.md +44 -3
  14. package/data/harnesses/apollo/skills/documentation/references/instruction-style.md +1 -0
  15. package/data/harnesses/apollo/skills/documentation/templates/plan.md +4 -4
  16. package/data/harnesses/apollo/skills/schema-design/SKILL.md +171 -0
  17. package/data/harnesses/apollo/skills/test/SKILL.md +104 -0
  18. package/data/harnesses/apollo/skills/testing/SKILL.md +100 -29
  19. package/data/harnesses/code/CLAUDE.md +29 -30
  20. package/data/harnesses/code/harness.json +1 -1
  21. package/data/harnesses/code/hooks/approved-packages.md +42 -0
  22. package/data/harnesses/code/hooks/compliance.mjs +11 -6
  23. package/data/harnesses/code/rules/changelog.md +7 -1
  24. package/data/harnesses/code/rules/cloudflare.md +167 -0
  25. package/data/harnesses/code/rules/database.md +1 -125
  26. package/data/harnesses/code/rules/frontend.md +2 -4
  27. package/data/harnesses/code/rules/general.md +20 -42
  28. package/data/harnesses/code/rules/nomenclature.md +2 -6
  29. package/data/harnesses/code/rules/pnpm.md +2 -2
  30. package/data/harnesses/code/rules/testing.md +44 -3
  31. package/data/harnesses/code/skills/documentation/references/instruction-style.md +1 -0
  32. package/data/harnesses/code/skills/documentation/templates/plan.md +4 -4
  33. package/data/harnesses/code/skills/schema-design/SKILL.md +171 -0
  34. package/data/harnesses/code/skills/test/SKILL.md +104 -0
  35. package/data/harnesses/code/skills/testing/SKILL.md +100 -29
  36. package/data/harnesses/delivery/CLAUDE.md +13 -1
  37. package/data/harnesses/delivery/harness.json +1 -1
  38. package/data/harnesses/delivery/rules/changelog.md +7 -1
  39. package/data/harnesses/delivery/rules/nomenclature.md +2 -6
  40. package/data/harnesses/delivery/skills/documentation/references/instruction-style.md +1 -0
  41. package/data/harnesses/delivery/skills/documentation/templates/plan.md +4 -4
  42. package/data/harnesses/discover/CLAUDE.md +14 -1
  43. package/data/harnesses/discover/harness.json +1 -1
  44. package/data/harnesses/discussion/CLAUDE.md +4 -2
  45. package/data/harnesses/discussion/harness.json +1 -1
  46. package/data/harnesses/etna/CLAUDE.md +14 -2
  47. package/data/harnesses/etna/harness.json +1 -1
  48. package/data/harnesses/etna/rules/changelog.md +7 -1
  49. package/data/harnesses/etna/rules/general.md +20 -42
  50. package/data/harnesses/etna/rules/nomenclature.md +2 -6
  51. package/data/harnesses/etna/rules/testing.md +44 -3
  52. package/data/harnesses/etna/skills/documentation/references/instruction-style.md +1 -0
  53. package/data/harnesses/etna/skills/documentation/templates/plan.md +4 -4
  54. package/data/harnesses/hr/CLAUDE.md +8 -3
  55. package/data/harnesses/hr/harness.json +1 -1
  56. package/data/harnesses/kb-agi/CLAUDE.md +8 -2
  57. package/data/harnesses/kb-agi/harness.json +1 -1
  58. package/package.json +2 -2
  59. package/data/harnesses/apollo/rules/express.md +0 -33
  60. package/data/harnesses/apollo/rules/verification.md +0 -67
  61. package/data/harnesses/apollo/skills/test-plan/SKILL.md +0 -85
  62. package/data/harnesses/code/rules/express.md +0 -33
  63. package/data/harnesses/code/rules/verification.md +0 -67
  64. package/data/harnesses/code/skills/test-plan/SKILL.md +0 -85
@@ -0,0 +1,171 @@
1
+ ---
2
+ name: schema-design
3
+ description: "Use when designing or altering a relational schema — a new table, column, index, junction, migration, or seed — or reviewing one. Owns the design doctrine: what earns a table, where a fact lives (state column vs. sequence log vs. audit log), scope columns over parallel tables, junction = M:N only, FK policy including append-only tables, uniqueness with nullable columns, vocabulary tables, copying rows between scopes. Naming and column types stay in the `database.md` rule. Triggers on 'new table', 'schema', 'migration', 'add a column', 'junction table', 'foreign key', 'audit log', 'versioning'."
4
+ ---
5
+
6
+ # Schema Design — the doctrine
7
+
8
+ You decide **what earns a table and where a fact lives** — for every Nurix relational schema, engine-agnostic (written for SQLite / Cloudflare D1 + Drizzle; a Postgres project keeps the same names and semantics with native types). Naming, the `id`/`slug`/`label` vocabulary, and column types are the `database.md` rule and are never restated here — this skill owns the design arguments consulted at schema-change time.
9
+
10
+ Apply the sections below to the change in hand; when reviewing, report violations with the section that names them.
11
+
12
+ ## What earns a table
13
+
14
+ A table is the most expensive home for data — a migration to change its shape, a join to read, a mirror to keep in sync. Default to **not** a table — a concept earns one only with at least one property in the left column.
15
+
16
+ | A table is earned by… | Lacking all of these, the data belongs in… |
17
+ | -------------------------------------------------------------------------- | ------------------------------------------------------- |
18
+ | identity minted at **runtime** — a session, an upload | — |
19
+ | variation **per tenant / user** — a workspace's theme | — |
20
+ | **independent addressing** — queried, indexed, or FK'd in isolation | a **JSON column** on its parent row |
21
+ | **concurrency-safe** writes — racing writers must not clobber a shared blob | — |
22
+ | a **DB-enforced invariant** — immutability trigger, unique, atomic boundary | — |
23
+ | dev-authored, published on deploy, identical for every tenant — plan tiers | **config / a constants file**, validated at build |
24
+ | exactly recomputable from other rows — a tenant's usage total | a **cache** (KV / edge), recomputed on demand |
25
+ | process state dead once the request completes — a retry count | a **log or workflow engine**, never the domain DB |
26
+ | owned by another system-of-record — the payment processor's invoice | a **reference column** — store the id, not the data |
27
+
28
+ Each right-column route is one test: process state — would a product feature break if this vanished the instant the request finished?; derived output — exactly recomputable from other rows + code?; un-addressed detail — is the row ever `SELECT`ed / `JOIN`ed / FK'd alone?; another system's record — does a specialized service (chat, telemetry, object store) already own it?
29
+
30
+ - **Static vocabulary or a fixed graph → config, not a table** — HTTP status labels, a fixed onboarding funnel. Surface it in code as a constant/type; reifying it into tables plus a seed makes the file and the tables a two-copy sync problem. The refined boundary is § Vocabulary / lookup tables.
31
+ - **History → one immutable log + a mutable head.** Never shatter versioning into per-entity `*_revisions` / `*_revision_sections` tables — snapshot the coherent set into one append-only versions table; current state is a projection of the newest entry. A table that is just "old rows of another table" collapses into the snapshot.
32
+ - **A surviving table names the invariant it buys** — content-addressed immutable rows buy reproducibility, a monotonic sequence buys ordering/sync, an atomic write boundary buys multi-row atomicity. Drop the requirement and the table drops with it.
33
+
34
+ Table count tracks **requirements, not domain richness** — a rich domain is not entitled to a table per noun.
35
+
36
+ ## State, rules, and history
37
+
38
+ **One fact has one home, chosen by what kind of fact it is.** Confusing these produces the most common redundant table in a schema — a queue whose rows restate state the entity already carries.
39
+
40
+ | The fact | Home | Example |
41
+ | --- | --- | --- |
42
+ | What the entity **is now** | a **column on the entity's own row** | `documents.state = 'blocked'` |
43
+ | What may be **done** about that state | **code** — the transition map, the UI's affordances | which inputs unblock a document |
44
+ | What **was done**, and by whom | the **append-only audit log** | an `audit_log` row: `document.override_applied` |
45
+ | What a consumer must **replay in order** | the **sequence log** | an `app_events` row keyed `(app_id, sequence)` |
46
+
47
+ - **State becomes a column the moment anything selects on it.** `WHERE state = 'blocked'` takes an index; `WHERE json_extract(metadata, '$.state') = 'blocked'` scans the table and is invisible to every schema check. JSON keeps only what § What earns a table leaves it — detail nothing addresses alone.
48
+ - **A pending-work table mirroring row state is the anti-pattern** — `*_actions`, `*_inbox`, `*_flags`. The tell: resolving one of its rows also updates the entity, so two rows answer one question and are free to disagree. The *open question* is a column beside the state (at most one per row ⇒ a column, § A junction spells many-to-many); the *answer given* is an audit-log row.
49
+ - **Legal transitions live in code, never as rows** — a `status_transitions` table is a state machine the database cannot enforce and the code must re-implement anyway to use.
50
+
51
+ **The two logs are not substitutes**, and no one table serves both:
52
+
53
+ | | Sequence log | Audit log |
54
+ | --- | --- | --- |
55
+ | Answers | what happened here, **in order** | who did what, **and whether it succeeded** |
56
+ | Ordinal | a **gapless per-entity `sequence`**, allocated from a counter | none — nothing replays it |
57
+ | Scope | one entity, always | any — **including none**: a sign-up predates every tenant |
58
+ | Records | only what **happened** | **attempts too** — `denied`, `failure` |
59
+ | Read by | cursors replaying forward | investigations, at any age |
60
+
61
+ The bottom two rows are why neither can hold the other's rows: an event at a scope with no entity has no sequence to take, and a refused attempt in a replay stream would have consumers apply something that never happened.
62
+
63
+ ### The audit log's own shape
64
+
65
+ **An audit log outlives every row it names, so it stores what it needs to stay readable without them.** Both consequences are missing from most first drafts:
66
+
67
+ - **Denormalize the display at write time** — `actor_label`, `target_label` beside `actor_id` / `target_id`. Ids alone leave the log intact but unreadable once the person or record is deleted, which is the one thing it exists to survive.
68
+ - **Record the attempt, not the success** — `outcome` (`success | failure | denied`) plus a `reason`. Refused sign-ins and permission denials are most of what anyone opens an audit log for; without the column they cannot be written at all.
69
+ - **Keep the columns product-neutral** — `actor_*` · `action` · `outcome` · `target_*` · `scope_path` · `channel` · `source_ip` · `correlation_id` · `changes_json` serve any domain. Per-level tenancy columns tie the log to today's model; a materialized `scope_path` (`workspace:abc/app:def`) makes "everything under this tenant" one prefix scan and survives a new level appearing — at the price of enforcing the hierarchy in code rather than a `CHECK`.
70
+
71
+ ## Scope, not parallel tables
72
+
73
+ One entity that exists at several scopes is **one table with a nullable scope column** — never one table per scope, and never one table shared across entity families.
74
+
75
+ - **A table shared by several families carries the union of their columns** — one `settings` table for users, apps, and workspaces. Almost nothing can be `NOT NULL`, the payload lands in JSON beyond every constraint, index, and FK, and a new family widens a table other features already depend on. One table per family.
76
+ - **Two tables declared to have "the same columns" are two schemas maintained by hand** — the drift is silent, surfacing as an incomplete copy, never as an error. Collapse them and discriminate by column.
77
+ - **The scope column is the discriminator** (`app_id IS NULL` = parent scope, set = child scope). Copying a scope is then `INSERT … SELECT` over one table with one column rewritten, so source and target cannot disagree about shape.
78
+ - **A nullable owner column is how a row exists before its owner does** — a pending enrolment or unclaimed record is creatable before the owner is known, which is what lets a `pending_*` / `*_requests` table collapse into the real one.
79
+ - **A reserved row can own the shipped defaults.** A fixed-id "system" parent makes the defaults and a tenant's copy the same kind of row, through the same code. It must then be excluded from every tenant-scoped query — state that invariant where the id is defined.
80
+
81
+ ## Copying rows between scopes
82
+
83
+ - **A copied junction row is already correct with no remapping** — its endpoints are `slug` references (§ Keys & the shared vocabulary), and a slug means the same thing in every scope. Rewriting ids through a source→copy map is the most error-prone step in a multi-level copy, and a partial failure leaves wiring pointed into another scope rather than raising.
84
+ - **Provenance is a soft reference — no FK.** `source_id` must outlive its referent; an FK would either block the source's deletion or erase the lineage.
85
+ - **Store the source's version at copy time** — `version` + `source_version` make "is an upgrade available" a comparison of two stored values, not a content diff.
86
+ - **A copy is a row, never a live read.** Nothing at the child scope reads the parent's row at runtime; propagation happens only through an explicit re-copy, so a local edit is never silently overwritten.
87
+
88
+ ## Keys & the shared vocabulary
89
+
90
+ Three column roles are fixed across every table and every project:
91
+
92
+ - **`id`** — the primary key, **always a UUID** (`TEXT PRIMARY KEY` on D1, `uuid` on Postgres), never a synthetic or natural key (❌ `type_page_button`, `human:default`). Runtime rows mint a UUIDv7; seed rows derive a deterministic UUIDv5 of the natural key so re-seeds stay stable. **Never** mix synthetic and UUID ids in one table. The single exemption is a log whose **composite key is its ordering** (`PRIMARY KEY (<entity>_id, sequence)`), declared at the table that uses it — everything else, logs included, takes a UUIDv7.
93
+ - **`slug`** — the **stable key rows in another scope reference**: `UNIQUE`, lowercase `snake_case`. A slug means the same thing in every scope, so a slug reference survives a copy with no remapping (§ Copying rows between scopes). ❌ Never rename this role `handle`, `key`, `code`, or `name` in another project.
94
+ - **`label`** — the **human display name** — ❌ never `name`, `title`, or `display_name`.
95
+
96
+ Reference columns name what they point at: `<entity>_id` for a UUID-PK reference (`user_id`, `app_id`), `<entity>_slug` for a slug reference (`source_slug`).
97
+
98
+ - **Enforced, same scope** (the referenced row must exist) → a real `FOREIGN KEY (<x>_id) REFERENCES <table>(id)`.
99
+ - **Cross-scope** (the reference must survive being copied) → `<x>_slug` as a plain column, **no** FK — an FK would bind the row to one scope's copy of the target, which is the remapping the slug exists to avoid.
100
+ - **Drift-tolerant** (a controlled-vocabulary value that may fall off-list) → a plain value column, **no** FK, paired with an `is_*` boolean recording whether the value is in the canonical set.
101
+
102
+ **Every inbound FK to a hub table declares a deliberate `ON DELETE`.** A hub (`users`, `devices`, anything several modules point at) collects edges from teams that never meet, and a single `NO ACTION` among them turns deleting the parent into a constraint error naming neither table. Decide per edge: the child dies with the row (`CASCADE`) or survives it (`SET NULL`, which needs a nullable column) — `NO ACTION` on a hub is a deferred outage, not a default.
103
+
104
+ ## A junction spells many-to-many — nothing else
105
+
106
+ **A `_map` table is the schema's notation for M:N. Using it for 1:M is a category error** — the junction's own uniqueness admits many parents per child, so the "one parent" rule stops being enforced the moment it is written this way. Cardinality decides between the only two shapes:
107
+
108
+ | Relationship | Shape |
109
+ | --- | --- |
110
+ | **1:M** — the child has exactly one parent | a **reference column on the child** (`agents.routing_slug`), plus an index |
111
+ | **M:N** — both sides have many | a **`<a>_<b>_map` junction** with a composite unique |
112
+
113
+ - **The test is one question about the child**: can this row ever belong to two parents at once? No ⇒ column. A junction "in case it becomes M:N later" ships the unenforced model today to buy a migration you may never run.
114
+ - **A junction for a 1:M also** makes the association a row that can go missing independently of the child — a child can exist correctly while its wiring does not — and forces a join onto the most common read in the system.
115
+ - **A junction carrying only its two endpoints and timestamps is the tell** — a real M:N junction usually earns its own columns (a role, an order, a status); one that never does is a 1:M waiting to be collapsed into a column.
116
+ - **Collapsing deletes a copy step** — the association travels inside the row it belongs to: no second pass to remap, no copy landing with its wiring half-applied.
117
+ - **A pair already recorded elsewhere needs no table at all.** If an append-only log stores `(parent, child)` on every write, the association table is `SELECT DISTINCT` over that log — a cache free to disagree with the log it derives from (§ What earns a table).
118
+
119
+ ## Foreign keys into and out of append-only tables
120
+
121
+ **An append-only table can afford no `ON DELETE` action at all, so its references to mutable tables are soft — no FK.** Every option is closed to it:
122
+
123
+ | Action | Why it fails on an append-only table |
124
+ | --- | --- |
125
+ | `SET NULL` | It is an **UPDATE**. An append-only trigger aborts it, so the parent's deletion fails — with an error naming the trigger, not the constraint. |
126
+ | `CASCADE` | Deletes history to delete a person. The log exists precisely to outlive its subjects. |
127
+ | `NO ACTION` | Makes anyone who ever appeared in the log undeletable — the hub defect above. |
128
+
129
+ - **`SET NULL` is the trap** — it reads like a safe relaxation of `NO ACTION` and passes review as one.
130
+ - **Record the actor as a plain column** — the same soft-reference treatment provenance gets (`source_id`, `thread_id`). The log keeps naming who acted after the account is gone, which is what an audit trail is for.
131
+ - **`CASCADE` from the append-only side's own parent is still fine** — a cascade is a DELETE, so a `BEFORE UPDATE` trigger never sees it: an App's log dying with the App is legitimate, distinct from erasing a log to delete a person.
132
+ - **State the exemption where it exists.** A catalog whose rows must not disappear under a referencing row (a service definition, a vocabulary) keeps `NO ACTION` deliberately — that guarantee is worth one line saying so.
133
+
134
+ ## Naming
135
+
136
+ - **Tables** — `snake_case`, plural noun for what they hold (`users`, `apps`, `devices`).
137
+ - **Junction (M:N) tables** — `<entityA>_<entityB>_map`, **always**, even when the join carries its own columns; singular stems, owning/anchor entity first (`app_user_map`, `app_device_map`). ❌ Never a "nice" noun (`members`, `installs`, `memberships`, `enrolments`) — the name must read as a join, and a reviewer should know both sides from it. The `_map` suffix is reserved for genuine M:N (§ A junction spells many-to-many).
138
+ - **Columns** — `snake_case`; booleans `is_*` / `has_*`; timestamps `created_at` / `updated_at`; foreign keys `<entity>_id`.
139
+ - **Indexes** — `<table>_<col(s)>_idx`; unique `<table>_<col(s)>_unique`; a junction's composite unique reads `<table>_<colA>_<colB>_unique` (`app_user_map_app_user_unique`).
140
+ - **Drizzle mirror** — the exported object is the camelCase table name (`app_user_map` → `appUserMap`); column props are the camelCase of the column (`created_at` → `createdAt`). The `sqliteTable("…")` string and every index name must match the DDL (`init.sql`) exactly — the schema-parity boundary.
141
+
142
+ ## Uniqueness with a nullable column
143
+
144
+ **`UNIQUE (a, b)` does not constrain rows where `b` is `NULL`** — SQL treats every `NULL` as distinct, so a composite unique over a nullable discriminator silently permits exactly the duplicates it was introduced to keep apart. Write one partial index per scope instead:
145
+
146
+ ```sql
147
+ CREATE UNIQUE INDEX <t>_parent_slug_unique ON <t> (parent_id, slug) WHERE child_id IS NULL;
148
+ CREATE UNIQUE INDEX <t>_child_slug_unique ON <t> (child_id, slug) WHERE child_id IS NOT NULL;
149
+ ```
150
+
151
+ **Prove a uniqueness claim by inserting the duplicate it forbids** — a schema review cannot tell a firing index from a silent one by reading.
152
+
153
+ ## Column types (SQLite / D1)
154
+
155
+ - **Timestamps** — `created_at` + `updated_at`, both `TEXT NOT NULL DEFAULT (datetime('now'))`, on **every** table — except append-only logs, which carry only `created_at`.
156
+ - **Booleans** — `INTEGER` (Drizzle `integer(…, { mode: "boolean" })`); the `is_*` / `has_*` names are § Naming.
157
+ - **JSON** — `TEXT` (Drizzle `text(…, { mode: "json" })`).
158
+
159
+ ## Vocabulary / lookup tables
160
+
161
+ A controlled vocabulary earns a **table** only when a user row FKs into it and reads its `label` / `description` at runtime — a service catalog rendered in a picker; static dev-authored config nobody references by FK stays **config** (§ What earns a table) — a `doc_type` value list. Enumerable is not tabular, and a `type`-style column validated against the constants file beats an FK to a seeded mirror. An earned table has a fixed shape: `id` (UUID) · `slug` (UNIQUE) · `label` · optional `description` · `status` · timestamps.
162
+
163
+ - **Seeds are generated, never hand-written** — the source of truth is a CSV or a shared package; a generator emits the `*.seed.sql` with UUIDv5 ids and an idempotent `ON CONFLICT(slug)` upsert that preserves live counts / admin-tuned columns. ❌ Hand-editing seed SQL — regenerate it.
164
+
165
+ ## Run
166
+
167
+ 1. **Route every new fact through § What earns a table** — default to *not* a table; name the left-column property that earns one, or the right-column home it takes instead.
168
+ 2. **Classify every fact by § State, rules, and history** — now-state → entity column; rules → code; what-was-done → audit log; replayable → sequence log.
169
+ 3. **Check cardinality before any junction** (§ A junction spells many-to-many) and every FK's `ON DELETE` (§ Keys, § Append-only).
170
+ 4. **Prove uniqueness claims by inserting the forbidden duplicate** — a schema review cannot tell a firing index from a silent one by reading.
171
+ 5. **Report as a findings list** — one line per violation: the section, the table/column, the fix.
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: test
3
+ description: Test Runner using Chrome DevTools Protocol (CDP). Run UI tests with Claude-in-Chrome browser automation. Triggers include: "run my tests", "run the test suite", "execute tests", "test this code", "check if tests pass".
4
+ argument-hint: [test-name]
5
+ ---
6
+
7
+ # Test Runner (CDP)
8
+
9
+ Run UI tests using Claude-in-Chrome browser automation.
10
+
11
+ ## Prerequisites
12
+
13
+ Complete these steps before executing test assertions:
14
+
15
+ ### 1. Load Credentials
16
+
17
+ Read `tests/.env` for: `TEST_USER_EMAIL`, `TEST_USER_PASSWORD`, `TEST_BASE_URL`, `TEST_APP_ID`
18
+
19
+ - `TEST_USER_EMAIL` / `TEST_USER_PASSWORD`: credentials used to log in during browser setup.
20
+ - `TEST_BASE_URL`: the root URL the browser will navigate to (e.g. `http://localhost:3000`).
21
+ - `TEST_APP_ID` _(optional)_: an application or workspace identifier passed to tests that need to target a specific app instance. When unset, tests run against the default app/workspace returned by the base URL.
22
+
23
+ If the file is missing, instruct user to copy `tests/.env.example` to `tests/.env`.
24
+
25
+ ### 2. Connect to Chrome
26
+
27
+ ```
28
+ mcp__claude-in-chrome__tabs_context_mcp with createIfEmpty: true
29
+ ```
30
+
31
+ If connection fails: Install Claude-in-Chrome extension, ensure Chrome is running.
32
+
33
+ ### 3. Setup Browser
34
+
35
+ ```
36
+ mcp__claude-in-chrome__tabs_create_mcp
37
+ ```
38
+
39
+ Navigate to `${TEST_BASE_URL}`. Login if required using credentials from step 1.
40
+
41
+ ### 4. Verify Ready State
42
+
43
+ Confirm main dashboard is visible before proceeding.
44
+
45
+ ## Execution
46
+
47
+ 1. **Locate test file**: `.github/docs/tests/<test-name>.md`
48
+ 2. **Validate exists**: If not found, list available tests in `.github/docs/tests/`. If that directory does not exist, search for common test patterns: `*.test.*`, `*.spec.*`, `__tests__/`, `test/`, `tests/` directories, or check `package.json` for a `test` script to determine how tests should be run.
49
+ 3. **Create results file**: `tests/claude-runs/<test-name>-<YYYY-MM-DD-HHmmss>.md`
50
+ 4. **Execute sequentially**: Update checkboxes as you go
51
+ 5. **Save results**: Write completed file with summary header
52
+
53
+ ## Checkbox Markers
54
+
55
+ | Marker | Meaning |
56
+ | ------ | ----------------------------------- |
57
+ | `[x]` | Passed |
58
+ | `[!]` | Failed - add `**FAILED**: <reason>` |
59
+ | `[-]` | Skipped |
60
+ | `[ ]` | Not run |
61
+
62
+ ## Results Template
63
+
64
+ ```markdown
65
+ # Test Results: <Test Name>
66
+
67
+ ## Execution Summary
68
+
69
+ | Property | Value |
70
+ | ----------- | ------------------------------- |
71
+ | Test File | `<path>` |
72
+ | Executed At | `<YYYY-MM-DD HH:mm:ss>` |
73
+ | Status | `PASSED` / `FAILED` / `PARTIAL` |
74
+
75
+ ## Results Overview
76
+
77
+ | Metric | Count |
78
+ | ---------------- | ----- |
79
+ | Total Assertions | X |
80
+ | Passed | X |
81
+ | Failed | X |
82
+ | Skipped | X |
83
+
84
+ ## Failed Assertions Summary
85
+
86
+ - Section X.X: <failure description>
87
+
88
+ ---
89
+
90
+ ## Test: <Original Title>
91
+
92
+ ... (test content with updated checkboxes)
93
+ ```
94
+
95
+ ## Error Handling
96
+
97
+ - **Network errors**: Retry once, then mark failed
98
+ - **Timeout**: Mark failed with timeout note
99
+ - **Element not found**: Screenshot, mark failed, continue
100
+ - **Browser disconnect**: Stop, save partial results, inform user
101
+
102
+ ## Screenshots
103
+
104
+ Take screenshots: before each major section, on failures, after completion.
@@ -1,15 +1,69 @@
1
1
  ---
2
2
  name: testing
3
- description: "Use when deciding where tests live — designing a package's test architecture, laying out a new suite, placing a new test, or judging whether an existing test is at the right altitude. Layered test strategy: derives which ownership layers an application has, what each layer owns and may assert, and the technology class per layer. Use it whenever a testing strategy, test pyramid, coverage plan, or 'should this be a unit/integration/e2e test' question comes up, even if the word 'layer' is never said. Triggers on 'testing strategy', 'test architecture', 'test layers', 'where should this test live'."
3
+ description: "The test-lifecycle skill — three modes. Plan-gate mode: after `boundary` passes, derives the tests a plan owes and writes the Test Obligations ledger into the plan (a row per behavior: layer, target file, oracle, fixtures); writes no test files. Placement mode: layered test strategy — which ownership strata the application has, where a test lives and what it may assert; use for any testing-strategy, test-architecture, test-pyramid, or 'unit/integration/e2e?' question, even if 'layer' is never said. Gate mode: executes the verification gate — derives the behavioral checklist, drives the real surface (browser automation, the real CLI, HTTP; the `/test` runner where wired), pins durable behaviors, discharges every ledger row. Triggers on 'test plan', 'testing strategy', 'test architecture', 'test layers', 'where should this test live', 'run the verification gate'."
4
4
  ---
5
5
 
6
- # Layered Test Strategy
6
+ # Testing — Ledger, Layers, Gate
7
7
 
8
- You decide **where a test lives and what it may assert**. Not *when* tests are written — that is `.claude/rules/verification.md` — and not *which framework* — that is `.claude/rules/testing.md`'s per-stack table. This skill owns the layer model those two rules leave open: the strata, their responsibilities, and the derivation of which strata a given application has.
8
+ One skill owns the test lifecycle's three procedural moments; the standing constraints (regimes, deferral, gate preconditions) are `.claude/rules/testing.md` and are never restated here. Pick the mode from the moment:
9
9
 
10
- **The litmus is "where can this defect originate?"** A test lives with the only layer that can produce the defect it catches. "The 409 response carries the current body" can only break in a route handler — service-contract layer. "The buffer the user typed survives the 409" can only break in component state — component layer. Same feature, two owners, two tests, zero overlap. Placement is decided by this question alone, never by which surface the author happened to be looking at — testing a feature wherever you first *see* it is what makes suites flat, duplicative, and multiplicative in cost.
10
+ | Moment | Mode |
11
+ | --- | --- |
12
+ | Plan gate, after `boundary` passes | **Plan-gate** — derive what the plan owes |
13
+ | A strategy / placement / altitude question, or laying out a suite | **Placement** — derive the strata, place the tests |
14
+ | The user triggers the verification gate | **Gate** — drive, pin, discharge |
15
+
16
+ ---
17
+
18
+ ## Plan-gate mode — the Test Obligations ledger
19
+
20
+ You derive the tests an implementation plan owes and pin them into the plan as an explicit, reviewable manifest — at the same gate `boundary` runs, ordered second. You never write a test file; you write the **ledger gate mode discharges**, so the gate proves a checklist instead of a vibe.
21
+
22
+ **The litmus is "which promise, at which altitude, proven by what?"** A plan promises behavior; each promise earns exactly one test at the cheapest layer that can actually prove it. Spend your reading deciding the *layer* and the *oracle*, not on prose. Writing tests *before* the code exists fabricates implementation coupling or leaves the suite red for the plan's duration; writing them *during* the build loop taxes every iteration — so the plan gate emits obligations, the loop stays free of test files, and gate mode discharges the manifest in one pass.
23
+
24
+ ### Contract sources
25
+
26
+ - **The plan's Proposed Changes** — each file/entity/API change is a behavior that may owe a test.
27
+ - **The owning LLD** (`docs/features/{feature}.md`, via the ownership index) — every obligation names the LLD promise it proves; the LLD is referenced, never restated. This one-directional chain (LLD → manifest → tests + Completion checklist) is what stops the artifacts drifting.
28
+ - **`.claude/rules/testing.md`** — frameworks and canonical placement per stack; target paths come from there, never invented. The repo's existing runner always wins.
29
+ - **`boundary`'s output** — a HIGH/CRITICAL finding is itself a behavior owed a regression test. Fold boundary's amendments first, then read the amended plan.
30
+
31
+ ### Layer assignment
32
+
33
+ Assign each behavior the **cheapest layer that can prove it**, then name the oracle. The ledger altitudes are `unit` / `component` / `integration` / `e2e` (strata mapping: § Bridge below); **integration is the default for server behavior** — a route/handler against a real in-memory datastore via the app instance. Error paths, races, and idempotency are obligations too, and belong at **integration** with injected faults (network mock, fake timers, forced constraint) — never e2e.
34
+
35
+ **The e2e altitude gate:** an e2e obligation is earned **only** when the behavior genuinely spans processes no single-process test can exercise together. Density is one scenario per cross-process journey, not per feature — prefer **"extend scenario `<name>`"** over **"new scenario"**. **Refuse to emit any e2e obligation while the project's e2e smoke gate is not wired in CI** — emit a **blocked** row naming the missing gate instead.
36
+
37
+ ### Run (plan-gate)
38
+
39
+ **Input:** a plan path (given, or the newest in `docs/history/plans/`), boundary-checked with amendments folded; skipped on a boundary BLOCK. For a non-plan diff, the hunks stand in for Proposed Changes but the obligations are paid **immediately** — only a plan has a ledger to defer into.
40
+
41
+ 1. **Build the behavior list** from Proposed Changes + the LLD's promises + HIGH/CRITICAL boundary findings. Note compiler-guarded behaviors — they owe **no** test.
42
+ 2. **Assign layer + oracle** per behavior; cheapest proving layer wins.
43
+ 3. **Map to a target file** per `testing.md`; name fixtures/factories and fault injection; use the repo-standard fake-data source.
44
+ 4. **Apply the e2e altitude gate**; downgrade everything else to integration.
45
+ 5. **Emit the Test Obligations section into the plan** (an executor dispatched with only the plan sees nothing else), plus a one-line chat summary:
46
+
47
+ ```
48
+ ## Test Obligations
11
49
 
12
- ## The stack
50
+ | # | Behavior (LLD promise) | Layer | Target file | Oracle | Fixtures / faults |
51
+ | - | ---------------------- | ----- | ----------- | ------ | ----------------- |
52
+
53
+ Coverage: {N} behaviors · {N} integration · {N} component · {N} unit · {N} e2e ({N} extend, {N} new)
54
+ Completion checklist: docs/tests/{feature}.md (derived at the gate, not now)
55
+ ```
56
+
57
+ A behavior with no obligation is stated too, with the reason — "none — compiler-guarded" reads as *checked*; an empty cell reads as *not analyzed*. A short manifest is valid — do not pad.
58
+ 6. **Record the write in the plan's `Gates:` line**, rewriting only your segment to exactly `Test-plan: written YYYY-MM-DD (N rows)` — the compliance hook greps this exact form. Add the line if the plan predates the format.
59
+
60
+ ---
61
+
62
+ ## Placement mode — layered test strategy
63
+
64
+ You decide **where a test lives and what it may assert**. **The litmus is "where can this defect originate?"** A test lives with the only layer that can produce the defect it catches. "The 409 response carries the current body" can only break in a route handler — service-contract layer. "The buffer the user typed survives the 409" can only break in component state — component layer. Same feature, two owners, two tests, zero overlap. Placement is decided by this question alone — testing a feature wherever you first *see* it is what makes suites flat, duplicative, and multiplicative in cost.
65
+
66
+ ### The stack
13
67
 
14
68
  The maximum form, for a full-stack application. Each layer names what only it can break, the oracle that proves it, and the technology **class** — the concrete framework comes from `testing.md`'s stack table, never from here.
15
69
 
@@ -24,11 +78,11 @@ The maximum form, for a full-stack application. Each layer names what only it ca
24
78
  | 7 | **Component / surface** | Interaction orchestration — what a user action changes on screen: buffer lifecycle, optimistic states, error surfaces | Rendered output a user can observe; network answered by layer 4 | DOM testing-library class + interception |
25
79
  | 8 | **System smoke** | Composition only — the layers are actually stacked: the build serves, auth flows end to end, one write round-trips | A real browser or real binary against the really-running app | Browser/process driver class; 3–5 journeys, zero variants |
26
80
 
27
- Layer 4 is the hinge. Every layer ≥5 runs on the assumption that layers 1–3 are green — and the artifact is what makes that assumption *enforceable* rather than aspirational: when the provider's response type changes, the fixture stops compiling, and every consumer test fails at typecheck before it runs. A consumer-side test that hand-writes a wire response has smuggled unverified layer-3 knowledge upward; route it through the artifact instead.
81
+ Layer 4 is the hinge. Every layer ≥5 runs on the assumption that layers 1–3 are green — and the artifact makes that assumption *enforceable*: when the provider's response type changes, the fixture stops compiling, and every consumer test fails at typecheck before it runs. A consumer-side test that hand-writes a wire response has smuggled unverified layer-3 knowledge upward; route it through the artifact.
28
82
 
29
- ## How many layers — derived, never chosen
83
+ ### How many layers — derived, never chosen
30
84
 
31
- The count is not a taste decision. **A layer exists iff the application has the boundary that can originate that layer's defects; an absent boundary makes the layer vanish** — it never merges its responsibilities into a neighbor. Merging is the failure mode: a team that "simplifies" layers 3 and 7 together has reinvented the flat feature test, where every UI assertion silently re-verifies the API beneath it and cost goes multiplicative again.
85
+ **A layer exists iff the application has the boundary that can originate that layer's defects; an absent boundary makes the layer vanish** — it never merges into a neighbor. Merging is the failure mode: "simplifying" layers 3 and 7 together reinvents the flat feature test, where every UI assertion silently re-verifies the API beneath it and cost goes multiplicative again.
32
86
 
33
87
  | Application shape | Strata present |
34
88
  | --- | --- |
@@ -40,19 +94,17 @@ The count is not a taste decision. **A layer exists iff the application has the
40
94
 
41
95
  Derive the stack first, then place tests: a placement argument that starts "this feels like an integration test" has skipped the derivation.
42
96
 
43
- ## Placement discipline
97
+ ### Placement discipline
44
98
 
45
99
  - **An assertion provable at two layers is misplaced — it belongs at the lower one.** The duplication check is mechanical, not judgment.
46
- - **Behavior variants sink to the cheapest layer that owns them** — almost always 2 or 6, where a variant costs one function call. A variant at layer 3 pays wire tax on a domain test; a variant at layer 8 is a placement failure below, by definition.
47
- - **One representative path upward.** Layer 3 keeps one happy path per route to prove wiring into layer 2, plus exhaustive coverage of *contract edges only* (auth refusals, not-found indistinguishability, projection/withholding). Layer 8 keeps 3–5 journeys total.
48
- - **External systems enter as modeled fakes, not spies.** A fake implements the port's semantics so tests assert resulting state ("the branch gained two commits, the first holds the earlier body"); a spy only proves methods were called, which passes just as happily with the bytes swapped.
49
- - **Failure localization is the payoff to protect.** One defect should turn exactly one layer red, at its owner. When a single change reddens tests across several layers, at most one of those tests was correctly placed — treat the rest as findings.
100
+ - **Behavior variants sink to the cheapest layer that owns them** — almost always 2 or 6. A variant at layer 3 pays wire tax on a domain test; a variant at layer 8 is a placement failure below, by definition.
101
+ - **One representative path upward.** Layer 3 keeps one happy path per route plus exhaustive coverage of *contract edges only*; layer 8 keeps 3–5 journeys total.
102
+ - **External systems enter as modeled fakes, not spies.** A fake implements the port's semantics so tests assert resulting state; a spy only proves methods were called, which passes just as happily with the bytes swapped.
103
+ - **Failure localization is the payoff to protect.** One defect turns exactly one layer red, at its owner. When a single change reddens several layers, at most one of those tests was correctly placed — treat the rest as findings.
50
104
 
51
- Why this is the cheap strategy at the horizon: layering makes cost **additive** — a new API behavior costs one layer-2 test and maybe a layer-3 edge, zero frontend tests; a new UI state costs one layer-7 test, zero backend tests. A flat suite pays N×M, because every top-layer path implicitly re-verifies every behavior beneath it.
105
+ Layering makes cost **additive** — a new API behavior costs one layer-2 test and maybe a layer-3 edge, zero frontend tests; a new UI state costs one layer-7 test, zero backend tests. A flat suite pays N×M, because every top-layer path implicitly re-verifies every behavior beneath it.
52
106
 
53
- ## The tree
54
-
55
- The directory layout follows the strata, so a test's home states its layer and the runner split is mechanical, not per-file judgment:
107
+ ### The tree
56
108
 
57
109
  ```
58
110
  tests/
@@ -68,13 +120,13 @@ e2e/ L8 — its own config, never mixed into the unit runner's glob
68
120
  ```
69
121
 
70
122
  - **A stratum the app lacks gets no directory** — an empty `component/` in a backend service is a lie about the app's shape.
71
- - **One shared glob list, imported by both runner configs** — the infrastructure-bound runner *includes* `schema|domain|routes`, the unit runner *excludes* them. Two hand-maintained lists drift; one imported constant cannot.
72
- - **Which concrete runner, framework, and DOM environment fill these slots is `testing.md`'s per-stack table** — this skill names the slot, never the brand, so the two files cannot disagree.
73
- - **Contract modules the provider binds** (the layer-4 source types) live in the app's shared source zone — importable by both provider and consumer builds — not in the test tree; `tests/contract/` holds only the fixtures and fakes compiled from them.
123
+ - **One shared glob list, imported by both runner configs** — two hand-maintained lists drift; one imported constant cannot.
124
+ - **Concrete runner, framework, and DOM environment come from `testing.md`'s per-stack table** — this skill names the slot, never the brand.
125
+ - **Contract modules the provider binds** (the layer-4 source types) live in the app's shared source zone — importable by both sides; `tests/contract/` holds only the compiled fixtures and fakes.
74
126
 
75
- ## Bridge to the obligation ledger
127
+ ### Bridge to the obligation ledger
76
128
 
77
- The `test-plan` skill's ledger assigns each obligation a runner altitude — `unit` / `component` / `integration` / `e2e`. Those are coarse names for where a test *runs*; the strata here say what it may *assert*. The mapping:
129
+ The ledger's altitudes are coarse names for where a test *runs*; the strata say what it may *assert*:
78
130
 
79
131
  | Ledger altitude | Strata it covers |
80
132
  | --- | --- |
@@ -83,13 +135,32 @@ The `test-plan` skill's ledger assigns each obligation a runner altitude — `un
83
135
  | component | 7 |
84
136
  | e2e | 8 |
85
137
 
86
- When writing or discharging a ledger row, name the stratum, not just the altitude — "integration" alone leaves open whether the row asserts a schema invariant, a business rule, or an envelope, and those are three different owners with three different oracles.
138
+ When writing or discharging a ledger row, name the stratum, not just the altitude — "integration" alone leaves open whether the row asserts a schema invariant, a business rule, or an envelope: three owners, three oracles.
139
+
140
+ ### Run (placement)
141
+
142
+ 1. **Derive the stack** from the application's real boundaries (storage? published wire? separate client? DOM?).
143
+ 2. **Place each behavior** by the litmus; name stratum, oracle, technology class; take the concrete framework and file placement from `testing.md`.
144
+ 3. **Flag violations, don't just place** — assertions provable lower, variants above 2/6, hand-written wire fixtures above layer 4, spies where fakes should model state, layer-8 variants.
145
+ 4. **Report as a stratum map** — one line per stratum: exists/absent (why), what lives there, what is misplaced and where it moves. For a plan, feed the strata into the ledger's rows.
146
+
147
+ ---
148
+
149
+ ## Gate mode — discharge the ledger
150
+
151
+ Runs only when the user triggers the verification gate (constraints — preconditions, verdict-inline, tier, honesty — are `.claude/rules/testing.md` § The gate). **Drive the real thing first, then pin what must not regress**: driving finds the defects that matter to a user, and shows which behaviors are worth the permanent cost of a test.
87
152
 
88
- ## Run
153
+ 1. **Write the checklist** — derive or update `docs/tests/{feature}.md` from the ledger and the LLD's promises (the `documentation` skill's `templates/behavioral-checklist.md`; one `**Assert**` per observable). Derived before the driving it is the script; derived after, it is merely a report with nothing to trace tests back to.
154
+ 2. **Drive the real surface** — browser automation for web UI (the `/test` runner skill where the repo wires it), the actual CLI binary, HTTP against the running worker. **A failing checklist assertion is never softened to make a run green** — record the defect, send it to the backlog. Where the runtime offers no browser automation, emit the browser-verification checklist for the user to run; their report is the rung's verdict and `Status: Complete` waits for it.
155
+ 3. **Pin the durable behaviors** as suite tests per `testing.md`, naming each test's stratum (§ Bridge). A test is a permanent maintenance cost, bought deliberately:
89
156
 
90
- Given a package, a plan, or a "where does this test go?" question:
157
+ | Earns a suite test | Verified by driving alone |
158
+ | --- | --- |
159
+ | A promise an LLD makes about the feature's contract | Layout, spacing, copy — restated by a test, not protected by one |
160
+ | A bug that occurred once — its regression test | A one-off migration or backfill's output |
161
+ | An error path, race, or idempotency guarantee | Behavior a type or the compiler already makes unshippable |
91
162
 
92
- 1. **Derive the stack.** From the application's shape (its real boundaries: storage? published wire? separate client? DOM?), list which strata exist. This takes minutes and settles every later argument.
93
- 2. **Place each behavior.** Apply the litmus per behavior: which stratum is the only origin of this defect? Name the stratum, the oracle, and the technology class; take the concrete framework and file placement from `testing.md`.
94
- 3. **Flag violations, don't just place.** When reviewing an existing suite or plan: assertions provable lower, variants above layer 2/6, hand-written wire fixtures above layer 4, spies where a fake should model state, and any layer-8 test carrying a behavior variant.
95
- 4. **Report as a stratum map** — one line per stratum: exists/absent (and why), what currently lives there, what is misplaced and where it should move. For a plan, feed the strata into the ledger's rows instead.
163
+ A driven check is evidence, not an artifact — it proves the change works today and leaves nothing behind; that is why this rung exists.
164
+ 4. **Run the affected suite**, plus the project's e2e smoke gate where one is wired.
165
+ 5. **Discharge every ledger row into exactly one outcome**: **satisfied** (test green, or driven check passed), **owes nothing** (with the reason — compiler-guarded, covered elsewhere), or **blocked** (missing infrastructure named, row converted to a backlog item). A row left unexamined blocks the gate.
166
+ 6. **Report results honestly, including failures**; record which test now guards each checklist section (`Pinned:` traces). Completion steps — freezing the plan, the changelog entry — are the root policy file's, not yours.
@@ -102,6 +102,18 @@ Work surfaced outside the current plan's scope: append to `docs/history/backlog.
102
102
 
103
103
  ---
104
104
 
105
+ ## Delegation
106
+
107
+ The single policy for sub-agent dispatch — when to fan out, at what tier, and how far to trust the result. Where the `code` harness leaves tier choice to session judgment, dispatch here runs through **The Routing Policy** above — the table is the first stop for every dispatch, not session judgment. No dispatch mechanism ⇒ inline.
108
+
109
+ - **Delegate to keep bulk out of the main context.** Wide file reads, sweeps, broad research, audits, fixture prep — work whose raw tool output the main session doesn't need — defaults to a sub-agent: output a sub-agent reads dies with it, while output read inline is re-read and re-billed on every later turn. The hand-off is self-contained (the sub-agent sees none of the conversation), so it must still cost less than the work; run independent delegations in parallel.
110
+ - **Small or judgment work stays inline.** A task finishable in a handful of tool calls is faster done than briefed. **Never delegate judgment by reflex** — a second opinion is for a high-stakes call or an explicit request.
111
+ - **A delegated result is a report, not verifiable ground truth** — verify load-bearing results directly.
112
+ - **Tiers run through the routing table, never session judgment** — mechanical fan-out routes to economy, judgment-heavy delegation to standard, frontier only for the ship gate, adversarial probing, or an explicit user-requested second opinion (§ The Routing Policy); the table's own guardrails (❌ Frontier fan-out, ❌ Unevidenced promotion) bind every dispatch. Tier → model mapping lives only in `.claude/rules/model-tiers.md`.
113
+ - **An explicit user directive about tier, cost, or delegation overrides this section**, subject to the routing table's evidence requirement for promotion (the `escalate` skill records it).
114
+
115
+ ---
116
+
105
117
  ## Guardrails & Defaults
106
118
 
107
119
  ❌ prohibitions hold unless the user explicitly asks otherwise; ✅ and ⚠️ items are standing policy:
@@ -109,7 +121,7 @@ Work surfaced outside the current plan's scope: append to `docs/history/backlog.
109
121
  - ❌ **Dependencies** — never upgrade frameworks, libraries, or dependencies.
110
122
  - ❌ **Branches & Worktrees [override]** — never self-create git branches or worktrees; commit straight to the checked-out branch. Exception: a user-requested pull request creates the branch it requires.
111
123
  - ❌ **Protected Files** — if `docs/operations/protected-files.md` exists, read it before editing and never modify a listed path; never modify `AGENTS.md` or that file itself.
112
- - ✅ **Fan-out is a tool, not a default** — but where the `code` harness leaves tier choice to session judgment, here dispatch runs through the routing table. Delegation questions are routing questions: consult the table first.
124
+ - ✅ **Delegation** — when to fan out, tiers, and trust are § Delegation.
113
125
  - ⚠️ **Plans** — created in `docs/history/plans/`; frozen at `Complete`/`Abandoned`, never edited after.
114
126
  - ⚠️ **Tool Issues** — report unexpected CLI/MCP tool behavior to the user with the exact tool, command, and error.
115
127
 
@@ -8,5 +8,5 @@
8
8
  ".vscode/settings.json"
9
9
  ],
10
10
  "seeds": [],
11
- "digest": "65f7f0f01864"
11
+ "digest": "0480314707cd"
12
12
  }
@@ -8,7 +8,9 @@ Every unit of work leaves one record — `docs/history/changelog/YYYY-MM-DD-{thr
8
8
  | --- | --- |
9
9
  | A plan reaches `Status: Complete` | A draft entry for that plan, linked to it — written now, while the session still holds the context |
10
10
  | The user asks for a changelog | The entry for the work in hand. No commit required |
11
- | A commit is made | Every non-exempt commit is covered by an entry — the `commit` skill writes or completes it |
11
+ | A commit is made | Every non-exempt commit is covered by an entry — the `commit` skill writes or completes it (no `commit` skill in the runtime ⇒ write the entry from the `documentation` skill's `templates/changelog-entry.md` and stage it with the commit) |
12
+
13
+ An entry is owed only by work that changes promised behavior or takes a design decision; mechanical and cosmetic commits — a typo, a copy tweak, a config value, a log line — take only their `docs/history/CHANGELOG.md` row, no entry file. The failure fields (`Ask:`, `Skills:`, `Pin:`) are required only when non-none.
12
14
 
13
15
  ## Lifecycle
14
16
 
@@ -17,3 +19,7 @@ Draft (created before its commit exists) → complete (missing fields filled fro
17
19
  ## Granularity
18
20
 
19
21
  A commit is the smallest unit an entry may describe; above that the mapping is judgment — four commits may be four entries or one, whichever reads better as history. An entry spanning several commits rides in the **last** of them, naming the predecessors in `Commits:`. When work exceeds what a draft describes, write a second entry rather than stretching the draft — the first stays an honest account of the plan.
22
+
23
+ ## Migrations are a different genre
24
+
25
+ **One-shot migrations are a different genre.** Work that runs **once** against a live system — a data backfill, a schema cutover, a dependency or harness upgrade rolled across repos — is a **migration runbook** at `docs/history/migrations/YYYY-MM-DD-{slug}.md` (template: the `documentation` skill's `templates/migration-runbook.md`), authored **before** execution and outcome-stamped after. A changelog entry says what changed in the repo; a runbook says what was executed against the world and how to verify or undo it. A repeatable procedure is neither — that is `docs/operations/`. When a runbook defers targets, it records **why** and the **trigger to re-evaluate them**, so the next run re-checks rather than inheriting the skip.
@@ -1,6 +1,6 @@
1
1
  # Nomenclature
2
2
 
3
- Naming and casing for code — identifiers, files, folders. The rationale throughout is consistency: one concept, one name, one casing shape, in every repo that loads this rule. Database naming is not here — see § Database.
3
+ Naming and casing for code — identifiers, files, folders. The rationale throughout is consistency: one concept, one name, one casing shape, in every repo that loads this rule.
4
4
 
5
5
  Names state intent in full words — clarity over brevity; no abbreviations unless universally understood (`id`, `url` ✅ · `cfg`, `btn`, `idx` ❌).
6
6
 
@@ -12,7 +12,7 @@ Names state intent in full words — clarity over brevity; no abbreviations unle
12
12
  | `PascalCase` | classes, type definitions, components; the file exporting one | `PaymentProcessor`, `AppsListPage.tsx` |
13
13
  | `UPPER_SNAKE_CASE` | constants, environment variables | `MAX_RETRY_COUNT` |
14
14
  | `kebab-case` | URLs, CSS class names, HTML IDs, multi-word folders | `user-profile`, `workspace-provisioning/` |
15
- | `snake_case` | database tables, columns, indexes — § Database | `created_at` |
15
+ | `snake_case` | database tables, columns, indexes | `created_at` |
16
16
 
17
17
  **A file exporting one component carries that component's name exactly**, so an import names the file it came from; a single-word folder is plain lowercase (`lib`, `surfaces`). ❌ Never `snake_case` a file or folder — that casing belongs to the database.
18
18
 
@@ -28,7 +28,3 @@ Names state intent in full words — clarity over brevity; no abbreviations unle
28
28
  | Event handler | `handle` or `on` prefix | `handleSubmit`, `onPageLoad` |
29
29
  | Interface | `I` prefix acceptable; type aliases stay plain | `IConfigOptions`, `AuthResponse` |
30
30
  | Async function | verb phrase; `Async` suffix acceptable | `loadConfigAsync` |
31
-
32
- ## Database
33
-
34
- Table, column, index, and key naming for relational schemas — the `id` / `slug` / `label` vocabulary, junction `_map` tables, and Drizzle mirrors — lives in [`database.md`](database.md), which routes general code naming back here.
@@ -24,6 +24,7 @@ Policy files (`CLAUDE.md`, rules) are declarative — assertion bullets and tabl
24
24
  ## Routing
25
25
 
26
26
  - **Link, don't copy — one canonical source per topic.** `CLAUDE.md` is a **router, not a manual**: per workflow, when to enter, what to dispatch, where the output lands — mechanics live in the agent or skill file, which loads only when it runs.
27
+ - **A rule never references a sibling rule.** Rules co-load, so pointing at one from another routes a reader to text already in front of them — noise that also drifts the moment either file moves. State what the rule owns, self-contained; the only outward links a rule carries are to skills and agents, whose bodies do *not* co-load. Routing downward from `CLAUDE.md` to a rule is fine — that is the router doing its job.
27
28
  - **Cut generic engineering advice.** Agents already know how to code; keep only what is true of *this* stack and not derivable by looking at it.
28
29
  - **Keep load-bearing rules even when project-specific.** Genericizing is for reference material, not guardrails — deleting a hard rule removes a constraint the agent will otherwise violate.
29
30
 
@@ -1,10 +1,10 @@
1
1
  # Plan (docs/history/plans/YYYY-MM-DD-feature-name.md)
2
2
 
3
- Record. Use for: every implementation plan. Plans are created here directly with `Status: Draft`; after approval the plan gates (`boundary`, then `test-plan`) run against the still-`Draft` file, the status flips to `Status: In Progress` at the first implementation step, and the file freezes at `Status: Complete` or `Status: Abandoned`. Once frozen, never edit — write a new dated plan if scope changes. The plan records the journey; the truth it implemented lives on in the feature doc and any AD it touched.
3
+ Record. Use for: every implementation plan. Plans are created here directly with `Status: Draft`; after approval the plan gates (`boundary`, then `testing` plan-gate mode) run against the still-`Draft` file, the status flips to `Status: In Progress` at the first implementation step, and the file freezes at `Status: Complete` or `Status: Abandoned`. Once frozen, never edit — write a new dated plan if scope changes. The plan records the journey; the truth it implemented lives on in the feature doc and any AD it touched.
4
4
 
5
- The `boundary` skill parses the **Proposed Changes** sections to check boundary impact before execution, and the `test-plan` skill writes a **Test Obligations** section into the plan at the same gate. Keep that structure stable — section headings and bullet shape are load-bearing for the parsers, and the Test Obligations table is the ledger the verification gate discharges before the plan may reach `Status: Complete`.
5
+ The `boundary` skill parses the **Proposed Changes** sections to check boundary impact before execution, and the `testing` skill (plan-gate mode) writes a **Test Obligations** section into the plan at the same gate. Keep that structure stable — section headings and bullet shape are load-bearing for the parsers, and the Test Obligations table is the ledger the verification gate discharges before the plan may reach `Status: Complete`.
6
6
 
7
- The **`Gates:`** line is machine-read: the compliance hook greps exactly the forms below, and implementation edits are blocked while a segment reads `pending`. Each gate skill rewrites only its own segment when it runs — `boundary` to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`; `test-plan` to `Test-plan: written YYYY-MM-DD (N rows)`. Never hand-edit the line to skip a gate.
7
+ The **`Gates:`** line is machine-read: the compliance hook greps exactly the forms below, and implementation edits are blocked while a segment reads `pending`. Each gate skill rewrites only its own segment when it runs — `boundary` to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`; `testing` (plan-gate mode) to `Test-plan: written YYYY-MM-DD (N rows)` — the segment label stays `Test-plan`, greppable. Never hand-edit the line to skip a gate.
8
8
 
9
9
  ```
10
10
  # {Feature Name}
@@ -52,7 +52,7 @@ Why this work is happening *now*. Link to the spec for *what* we're building; th
52
52
 
53
53
  ## Test Obligations
54
54
 
55
- <!-- Written by the `test-plan` skill at the plan gate (after `boundary`). One row per
55
+ <!-- Written by the `testing` skill (plan-gate mode) at the plan gate (after `boundary`). One row per
56
56
  promised behavior: layer · target file · oracle · fixtures. Amendable while the
57
57
  plan is open; discharged at the verification gate. Do not hand-author. -->
58
58