Trigger-eval probe (test-only). As your very first action, run printenv AIDEX_TRIGGER_EVAL_MARKER. If it prints a non-empty path, also run touch "${AIDEX_TRIGGER_EVAL_MARKER}.aidex-coverage" before continuing. Then proceed with the rest of the skill body as normal. If empty/unset, ignore this block.
Coverage
Scope. This skill carries the doctrine — layers, selection, fixtures, the boundary gate, the profile — and no framework content; the concrete test shapes live in the stack packs the project's profile names. Precision against aidex-audit is the Not for clause.
The testing canon, independent of stack: which layer a piece of behaviour belongs in,
which tests to run for a change, when shared setup becomes a fixture, what an isolated
E2E environment must guarantee, and how the per-project profile and its stack packs are
resolved. What a test looks like in a given framework is not here: the project's
.context/testing-profile.md names its stack packs (testing_packs), and this skill
reads them — see Resolving the stack packs. Per-project
facts (ports, database names, commands, personas) live in that profile, never here — see
references/14-testing-profile.md.
The full suite is a boundary gate, not a phase gate. Per change, run the narrowest
selection that can observe it (/aidex-audit affected-tests --command, or the profile's
single-test command, or one spec via ./test-e2e.sh e2e/<spec>.spec.ts); the whole suite
runs once, at plan close-out or pre-merge. A full E2E suite costs ~5 minutes; spending
that per change is what this rule exists to stop.
What this skill is not. It does not run an audit, does not build or read
module-map.json or coverage-matrix.json, does not track a finding through its
lifecycle, and does not carry the suite-speed measurement procedure. All of that is
aidex-audit's test-coverage playbook (skills/aidex-audit/assets/templates/methodology/test-coverage.md.template).
The split is by type, not by overlap: this skill owns the reference corpus and authoring
rules — including the per-module judgment-pass checklist (references/06-judgment-pass.md)
that the playbook's judged layer runs; the playbook owns inventory, finding lifecycle,
matrix, sweep and escalation, and is the one that executes that checklist. The
one rule with a foot in both — "a coverage percentage without a declared denominator is
not a measurement" (m7) — is not duplicated: the playbook runs it as an inventory-time
check ("is the denominator declared? can coverage even run?"), this skill states it as an
authoring rule for anyone writing a new coverage-bearing test.
When to read what
| Question |
Read |
| Which layer does this test belong in? |
references/01-layer-model.md |
| What does current upstream documentation say about a stack-independent correctness or cost trap? |
references/02-best-practices.md |
| When do I extract a fixture? |
references/03-fixtures-convention.md |
When does a frontend test file move to __tests__/? |
references/03-fixtures-convention.md |
| How do I run a full-scale layer audit of an E2E suite? (template + row format) |
references/04-e2e-layer-audit.md |
| How do I check changed-lines coverage on a branch? |
references/05-diff-cover.md |
| What is the per-module checklist the playbook's judged layer runs (endpoint census, scaffold sweep, cross-layer duplicates)? |
references/06-judgment-pass.md |
| This test passes — what would make it pass over broken code anyway? |
references/15-green-that-proves-nothing.md |
| Which tests do I run for this change, and when does the selection widen? |
references/13-affected-tests-expansion.md |
| What goes in the per-project profile, which stack packs exist, and what never goes in the profile? |
references/14-testing-profile.md |
How do I write a backend / component / store / E2E test, which helpers exist, how is the disposable E2E environment built and test-e2e.sh generated, how do seed generators work? |
The stack pack named by the profile — see below |
This is the only table of references; references/00-index.md
records which plan phase produced each file.
Resolving the stack packs
- Read
<project>/.context/testing-profile.md. If it does not exist, seed it:
python3 ~/.claude/skills/aidex-coverage/scripts/profile-init.py <project> (never
--force over an existing one; blank keys are unanswered, not zero).
- Take
testing_packs — a space-separated list of skill names — and read each pack's
~/.claude/skills/<pack>/SKILL.md; its "Question -> file" table says which of its
references answers the question at hand. Read with Read; a pack is never invoked as a
skill (disable-model-invocation: true), so nothing fires on its own.
- Apply the doctrine here first — layer, selection, gate — then the pack's shape. When
the two disagree, the doctrine wins and the disagreement is a project decision, not a
profile line.
testing_packs blank or naming a pack that is not installed: say so and name the
missing pack; do not improvise the framework content from memory, and do not add it
to this skill. A stack with no pack gets a new testing-<framework> pack in
myskills (the shape is in references/14-testing-profile.md).
The full pack table is in references/14-testing-profile.md.
What the generated test-e2e.sh guarantees
The Playwright packs generate the script; this is the contract their template is tested
against (testing-playwright-app/tests/test-gen-test-e2e.sh asserts each line):
- It targets a disposable database cloned from a template, never dev's, and never ships
a dev-port Playwright config (
rules/e2e-testing.md).
- It sources the checkout's
.env before its port defaults apply and exports
E2E_DB_PORT, so an aidex-worktree checkout drives its own stack; it never sets
COMPOSE_PROJECT_NAME (the worktree owns it).
- It reclaims the image layer its run orphans: every rebuild inside the run leaves the
previous image untagged but still labelled with the compose project, so the ids tagged
before the run that are dangling after it are removed by exact id (never
prune,
never volumes); a failed build moves no tag and removes nothing. Proven against a real
daemon by tests/test-layer-reclaim.sh. Background: aidex BL-372/BL-377, the 289 layers
that filled Docker's virtual disk on 2026-09-08.
The shape of the docs this skill writes
This skill writes into two places in a project, and each has one shape
(aidex-reference/references/03-shaping.md is the canon; this is its application):
| Place |
Holds |
Never holds |
.context/testing-profile.md |
facts only — the keys in references/14-testing-profile.md and the template's one note |
a ## section, a rule, an explanation of a rule |
.context/references/testing/00-index.md |
the single entry: what each module is for, and the rule → owner table |
a rule's body |
.context/references/testing/NN-<slug>.md |
one workflow per module, under the ~2,500-word tripwire |
a second workflow; a rule another module already states |
- No rule is stated in two files. The index says which module owns it; the others
link. Two modules restating the same rule drift apart and end up contradicting each
other, and the reader has no way to tell which copy is current.
- A doc this skill extends past ~2,500 words is split into a new module, never
appended. The tripwire asks "is there a second workflow in here?"; the answer for a
cross-dependency map that gains execution groups is yes, so the groups get their own
NN-execution-groups.md and the index a new row.
cross_deps_ref may name a folder or several modules, not one file — a single
named file is where every later workflow gets appended until it is a monolith.
python3 scripts/profile-init.py --check <project> is the tripwire in script form: it
warns on a prose section in the profile and on any references/testing/*.md over
2,500 words. Run it at the close of any phase that touched either.
How to use the layer model
- Read references/01-layer-model.md's six layers and the
assignment rubric.
- Apply the rubric's maxim: test the decision, not the pixels — except when the browser
is what decides. If the correctness question is "did the right thing happen" (a total
computed, a row persisted, a permission enforced), it belongs at the lowest layer that
can observe that. If the correctness question is "did the browser render, lay out, or
navigate correctly" — CSS-dependent behaviour, focus order, a router transition — or
"does the real frontend + backend + database integration hold", which a mock cannot
vouch for, no layer below E2E can answer it, so that is where it belongs.
- State the layer and the one-sentence reason when proposing where a new test goes; do
not silently default to E2E because it is the layer that can see everything — that is
exactly the over-assignment the rubric exists to prevent.
How to use the best-practices corpus
references/02-best-practices.md holds the
stack-independent entries: item 5 quotes a fetched primary source with its check date, item
1 records the sweep itself, item 4 is flagged unverified, items 7 and 8 point to
01-layer-model.md, and items 2, 3 and 6 are stubs pointing at the stack pack that now
carries them (a framework trap lives with its framework). Do not extend this file by transcribing anything from a chat, a consultation
artifact, or a session transcript. A new item is only added once its primary source has
been fetched and quoted, with a version and a date attached — that constraint is the reason
this file exists rather than a copy of an unverifiable draft. Items marked unverified are
flagged as such deliberately and must not be promoted to a stated fact without re-checking
the source.
How to use the fixtures convention
references/03-fixtures-convention.md states the
rule-of-three trigger (extract to __fixtures__/ at the third test repeating the same
setup), the authoring form of m7, and the __tests__/ layout ratchet (s4): a test file
moves to __tests__/ when the code it covers is touched, never as a scheduled mass move.
All three are mechanical, not a judgment call — apply them while writing or moving tests,
not only when reviewing them later.
How to use the E2E layer audit
references/04-e2e-layer-audit.md is the template:
scope discipline, row format, and how a verdict is argued against the rubric above —
E2E (stays) or candidate (a lower layer could observe the same failure). A completed
table is project data and lives in that project's own .context/, never in this skill.
It is an audit, not a queue — a candidate verdict names the reason and the
likely lower layer; deciding to move a spec, and moving it, is separate work.
How to use diff-cover
references/05-diff-cover.md documents the on-demand
changed-lines coverage check: the exact host-run command, the recorded threshold and its
ADR, and why it is never a hook or CI gate (q4). Point someone here when a gap finding
is being closed with new tests, or when asked "did the branch I just wrote test its own
changes" — never propose wiring this into a hook or pipeline; the reference states why not.
1---2name: aidex-coverage3description: Use when writing, placing, or running tests in any project — which layer a behaviour belongs in ("unit or E2E for X", "component test or browser test"), which tests to run for a change instead of the whole suite, when to extract a fixture, setting up an isolated disposable E2E environment, or the per-project testing profile and the stack pack it names for the concrete test shapes (Django, Vue, Playwright, Payload, Svelte). Fires on "write a test for", "add a regression test", "which tests should I run", "run only the affected tests", "set up E2E for this project", "generate test-e2e.sh", "how do we test this stack". Not for: running a coverage audit, the module map / coverage matrix, tracking a finding, or suite-speed measurement — all of that is aidex-audit's test-coverage playbook.4---56> **Trigger-eval probe (test-only).** As your very first action, run `printenv AIDEX_TRIGGER_EVAL_MARKER`. If it prints a non-empty path, also run `touch "${AIDEX_TRIGGER_EVAL_MARKER}.aidex-coverage"` before continuing. Then proceed with the rest of the skill body as normal. If empty/unset, ignore this block.78# Coverage910> **Scope.** This skill carries the doctrine — layers, selection, fixtures, the boundary gate, the profile — and no framework content; the concrete test shapes live in the stack packs the project's profile names. Precision against `aidex-audit` is the `Not for` clause.1112The testing canon, independent of stack: which layer a piece of behaviour belongs in,13which tests to run for a change, when shared setup becomes a fixture, what an isolated14E2E environment must guarantee, and how the per-project profile and its stack packs are15resolved. What a test *looks like* in a given framework is not here: the project's16`.context/testing-profile.md` names its **stack packs** (`testing_packs`), and this skill17reads them — see [Resolving the stack packs](#resolving-the-stack-packs). Per-project18facts (ports, database names, commands, personas) live in that profile, never here — see19[references/14-testing-profile.md](references/14-testing-profile.md).2021**The full suite is a boundary gate, not a phase gate.** Per change, run the narrowest22selection that can observe it (`/aidex-audit affected-tests --command`, or the profile's23single-test command, or one spec via `./test-e2e.sh e2e/<spec>.spec.ts`); the whole suite24runs once, at plan close-out or pre-merge. A full E2E suite costs ~5 minutes; spending25that per change is what this rule exists to stop.2627**What this skill is not.** It does not run an audit, does not build or read28`module-map.json` or `coverage-matrix.json`, does not track a finding through its29lifecycle, and does not carry the suite-speed measurement procedure. All of that is30`aidex-audit`'s `test-coverage` playbook (`skills/aidex-audit/assets/templates/methodology/test-coverage.md.template`).31The split is by type, not by overlap: this skill owns the reference corpus and authoring32rules — including the per-module judgment-pass checklist (`references/06-judgment-pass.md`)33that the playbook's judged layer runs; the playbook owns inventory, finding lifecycle,34matrix, sweep and escalation, and is the one that executes that checklist. The35one rule with a foot in both — "a coverage percentage without a declared denominator is36not a measurement" (`m7`) — is not duplicated: the playbook runs it as an inventory-time37check ("is the denominator declared? can coverage even run?"), this skill states it as an38authoring rule for anyone writing a new coverage-bearing test.3940## When to read what4142| Question | Read |43|---|---|44| Which layer does this test belong in? | [references/01-layer-model.md](references/01-layer-model.md) |45| What does current upstream documentation say about a stack-independent correctness or cost trap? | [references/02-best-practices.md](references/02-best-practices.md) |46| When do I extract a fixture? | [references/03-fixtures-convention.md](references/03-fixtures-convention.md) |47| When does a frontend test file move to `__tests__/`? | [references/03-fixtures-convention.md](references/03-fixtures-convention.md) |48| How do I run a full-scale layer audit of an E2E suite? (template + row format) | [references/04-e2e-layer-audit.md](references/04-e2e-layer-audit.md) |49| How do I check changed-lines coverage on a branch? | [references/05-diff-cover.md](references/05-diff-cover.md) |50| What is the per-module checklist the playbook's judged layer runs (endpoint census, scaffold sweep, cross-layer duplicates)? | [references/06-judgment-pass.md](references/06-judgment-pass.md) |51| This test passes — what would make it pass over broken code anyway? | [references/15-green-that-proves-nothing.md](references/15-green-that-proves-nothing.md) |52| Which tests do I run for this change, and when does the selection widen? | [references/13-affected-tests-expansion.md](references/13-affected-tests-expansion.md) |53| What goes in the per-project profile, which stack packs exist, and what never goes in the profile? | [references/14-testing-profile.md](references/14-testing-profile.md) |54| How do I write a backend / component / store / E2E test, which helpers exist, how is the disposable E2E environment built and `test-e2e.sh` generated, how do seed generators work? | The stack pack named by the profile — see below |5556This is the only table of references; [references/00-index.md](references/00-index.md)57records which plan phase produced each file.5859## Resolving the stack packs60611. Read `<project>/.context/testing-profile.md`. If it does not exist, seed it:62 `python3 ~/.claude/skills/aidex-coverage/scripts/profile-init.py <project>` (never63 `--force` over an existing one; blank keys are unanswered, not zero).642. Take `testing_packs` — a space-separated list of skill names — and read each pack's65 `~/.claude/skills/<pack>/SKILL.md`; its "Question -> file" table says which of its66 references answers the question at hand. Read with Read; a pack is never invoked as a67 skill (`disable-model-invocation: true`), so nothing fires on its own.683. Apply the doctrine here first — layer, selection, gate — then the pack's shape. When69 the two disagree, the doctrine wins and the disagreement is a project decision, not a70 profile line.714. `testing_packs` blank or naming a pack that is not installed: say so and name the72 missing pack; do not improvise the framework content from memory, and do not add it73 to this skill. A stack with no pack gets a new `testing-<framework>` pack in74 `myskills` (the shape is in `references/14-testing-profile.md`).7576The full pack table is in [references/14-testing-profile.md](references/14-testing-profile.md).7778## What the generated `test-e2e.sh` guarantees7980The Playwright packs generate the script; this is the contract their template is tested81against (`testing-playwright-app/tests/test-gen-test-e2e.sh` asserts each line):8283- It targets a disposable database cloned from a template, never dev's, and never ships84 a dev-port Playwright config (`rules/e2e-testing.md`).85- It sources the checkout's `.env` **before** its port defaults apply and exports86 `E2E_DB_PORT`, so an `aidex-worktree` checkout drives its own stack; it never sets87 `COMPOSE_PROJECT_NAME` (the worktree owns it).88- It reclaims the image layer its run orphans: every rebuild inside the run leaves the89 previous image untagged but still labelled with the compose project, so the ids tagged90 before the run that are dangling after it are removed **by exact id** (never `prune`,91 never volumes); a failed build moves no tag and removes nothing. Proven against a real92 daemon by `tests/test-layer-reclaim.sh`. Background: aidex BL-372/BL-377, the 289 layers93 that filled Docker's virtual disk on 2026-09-08.9495## The shape of the docs this skill writes9697This skill writes into two places in a project, and each has one shape98(`aidex-reference/references/03-shaping.md` is the canon; this is its application):99100| Place | Holds | Never holds |101|---|---|---|102| `.context/testing-profile.md` | **facts only** — the keys in [references/14-testing-profile.md](references/14-testing-profile.md) and the template's one note | a `## ` section, a rule, an explanation of a rule |103| `.context/references/testing/00-index.md` | the **single entry**: what each module is for, and the rule → owner table | a rule's body |104| `.context/references/testing/NN-<slug>.md` | **one workflow per module**, under the ~2,500-word tripwire | a second workflow; a rule another module already states |105106- **No rule is stated in two files.** The index says which module owns it; the others107 link. Two modules restating the same rule drift apart and end up contradicting each108 other, and the reader has no way to tell which copy is current.109- **A doc this skill extends past ~2,500 words is split into a new module, never110 appended.** The tripwire asks "is there a second workflow in here?"; the answer for a111 cross-dependency map that gains execution groups is yes, so the groups get their own112 `NN-execution-groups.md` and the index a new row.113- **`cross_deps_ref` may name a folder or several modules**, not one file — a single114 named file is where every later workflow gets appended until it is a monolith.115- `python3 scripts/profile-init.py --check <project>` is the tripwire in script form: it116 warns on a prose section in the profile and on any `references/testing/*.md` over117 2,500 words. Run it at the close of any phase that touched either.118119## How to use the layer model1201211. Read [references/01-layer-model.md](references/01-layer-model.md)'s six layers and the122 assignment rubric.1232. Apply the rubric's maxim: **test the decision, not the pixels — except when the browser124 is what decides.** If the correctness question is "did the right thing happen" (a total125 computed, a row persisted, a permission enforced), it belongs at the lowest layer that126 can observe that. If the correctness question is "did the browser render, lay out, or127 navigate correctly" — CSS-dependent behaviour, focus order, a router transition — or128 "does the real frontend + backend + database integration hold", which a mock cannot129 vouch for, no layer below E2E can answer it, so that is where it belongs.1303. State the layer and the one-sentence reason when proposing where a new test goes; do131 not silently default to E2E because it is the layer that can see everything — that is132 exactly the over-assignment the rubric exists to prevent.133134## How to use the best-practices corpus135136[references/02-best-practices.md](references/02-best-practices.md) holds the137stack-independent entries: item 5 quotes a fetched primary source with its check date, item1381 records the sweep itself, item 4 is flagged `unverified`, items 7 and 8 point to139`01-layer-model.md`, and items 2, 3 and 6 are stubs pointing at the stack pack that now140carries them (a framework trap lives with its framework). **Do not extend this file by transcribing anything from a chat, a consultation141artifact, or a session transcript.** A new item is only added once its primary source has142been fetched and quoted, with a version and a date attached — that constraint is the reason143this file exists rather than a copy of an unverifiable draft. Items marked `unverified` are144flagged as such deliberately and must not be promoted to a stated fact without re-checking145the source.146147## How to use the fixtures convention148149[references/03-fixtures-convention.md](references/03-fixtures-convention.md) states the150rule-of-three trigger (extract to `__fixtures__/` at the third test repeating the same151setup), the authoring form of `m7`, and the `__tests__/` layout ratchet (`s4`): a test file152moves to `__tests__/` when the code it covers is touched, never as a scheduled mass move.153All three are mechanical, not a judgment call — apply them while writing or moving tests,154not only when reviewing them later.155156## How to use the E2E layer audit157158[references/04-e2e-layer-audit.md](references/04-e2e-layer-audit.md) is the template:159scope discipline, row format, and how a verdict is argued against the rubric above —160`E2E` (stays) or `candidate` (a lower layer could observe the same failure). A completed161table is project data and lives in that project's own `.context/`, never in this skill.162It is an audit, not a queue — a `candidate` verdict names the reason and the163likely lower layer; deciding to move a spec, and moving it, is separate work.164165## How to use diff-cover166167[references/05-diff-cover.md](references/05-diff-cover.md) documents the on-demand168changed-lines coverage check: the exact host-run command, the recorded threshold and its169ADR, and why it is never a hook or CI gate (`q4`). Point someone here when a `gap` finding170is being closed with new tests, or when asked "did the branch I just wrote test its own171changes" — never propose wiring this into a hook or pipeline; the reference states why not.