@nurix/etna 0.20.11 → 0.20.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/catalog/rules/testing.md +1 -1
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/CLAUDE.md +1 -1
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/rules/testing.md +1 -1
- package/data/harnesses/code/skills/testing/SKILL.md +18 -17
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/rules/testing.md +1 -1
- package/package.json +2 -2
|
@@ -60,7 +60,7 @@ Test packages install at `latest` — outside the approved-packages pin set. Fak
|
|
|
60
60
|
|
|
61
61
|
- **Suite, not scripts.** Tests live in the canonical test tree — `tests/` mirroring `src/`, or the convention the repo already uses — named `<subject>.test.ts`; shared fixtures, factories, and helpers in `tests/helpers/` (or the suite's existing shared home).
|
|
62
62
|
- **Wire the canonical command.** `"test"` in `package.json` runs the whole unit suite (`vitest run`); e2e under `"test:e2e"`. A repo where `pnpm test` / `npm test` doesn't run the suite is misconfigured — fix the wiring, **never** route around it.
|
|
63
|
-
- **Unit and E2E trees stay separate.** Vitest/Jest suites in `tests/`; Playwright/Maestro suites in `e2e/` with their own config — **never** mixed into the unit runner's glob.
|
|
63
|
+
- **Unit and E2E trees stay separate.** Vitest/Jest suites in `tests/`; Playwright/Maestro suites in `e2e/` with their own config — **never** mixed into the unit runner's glob. The e2e config **owns its world** — it builds, seeds, and boots the app itself (a `webServer` block or equivalent), so a journey passes from a cold shell like any other test and "the world was not running" is never why one did not run.
|
|
64
64
|
- **The real-renderer stratum is a third tree** — `tests/rendered/` under the unit runner's *browser mode*, its own config, kept to geometry regressions that shipped and the composition assertions a component library cannot make alone. It is not a second component suite; anything a simulated DOM can prove stays in `tests/component/`.
|
|
65
65
|
- **No test doubles for what you own.** Prefer real instances — in-memory SQLite for drizzle, the actual Express app via `supertest` — over mocks; mock **only** true externals (network via `msw`, time via the runner's fake timers).
|
|
66
66
|
- **Determinism.** No real network, no real time, no shared mutable state between tests; every test **must** pass alone, in any order, **and from a cold shell** — never against a hand-started dev server (Workers: `@cloudflare/vitest-pool-workers`; Express: `supertest` against the app instance).
|
package/data/harness-index.json
CHANGED
|
@@ -71,7 +71,7 @@ Boundaries are **diagnosed, not documented** — the `boundary` skill derives th
|
|
|
71
71
|
|
|
72
72
|
### Test obligations — specify before implementing
|
|
73
73
|
|
|
74
|
-
**Then, still in the plan pass, invoke the `testing` skill (plan-gate mode)** — after `boundary`'s amendments are folded, ordered second. It derives the tests the plan owes from its Proposed Changes and the owning LLD, and writes the **Test Obligations** ledger into the plan (one row per promised behavior: layer · target file · oracle · fixtures · an empty **Outcome** cell the verification gate fills). It writes no test files — the ledger is what the verification gate discharges, and because it lives in the plan file it survives dispatch to an executor that sees only the plan. On a boundary **BLOCK** it does not run — re-plan first. Layer rules
|
|
74
|
+
**Then, still in the plan pass, invoke the `testing` skill (plan-gate mode)** — after `boundary`'s amendments are folded, ordered second. It derives the tests the plan owes from its Proposed Changes and the owning LLD, and writes the **Test Obligations** ledger into the plan (one row per promised behavior: layer · target file · oracle · fixtures · an empty **Outcome** cell the verification gate fills). It writes no test files — the ledger is what the verification gate discharges, and because it lives in the plan file it survives dispatch to an executor that sees only the plan. On a boundary **BLOCK** it does not run — re-plan first. Layer rules live in the skill — on a rendered surface every user-observable promise is owed as its surface's journey, and component tests are residual. **The Doc Obligations table is the author's, filled in the same pass** — a lookup against the ownership index, not a skill run; the hook blocks implementation edits while either section is missing.
|
|
75
75
|
|
|
76
76
|
### Verification — prove before completing
|
|
77
77
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "code",
|
|
3
3
|
"name": "code",
|
|
4
|
-
"version": "3.15.
|
|
4
|
+
"version": "3.15.2",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
}
|
|
26
26
|
],
|
|
27
27
|
"docsRoot": "docs",
|
|
28
|
-
"digest": "
|
|
28
|
+
"digest": "05a56f90cade"
|
|
29
29
|
}
|
|
@@ -60,7 +60,7 @@ Test packages install at `latest` — outside the approved-packages pin set. Fak
|
|
|
60
60
|
|
|
61
61
|
- **Suite, not scripts.** Tests live in the canonical test tree — `tests/` mirroring `src/`, or the convention the repo already uses — named `<subject>.test.ts`; shared fixtures, factories, and helpers in `tests/helpers/` (or the suite's existing shared home).
|
|
62
62
|
- **Wire the canonical command.** `"test"` in `package.json` runs the whole unit suite (`vitest run`); e2e under `"test:e2e"`. A repo where `pnpm test` / `npm test` doesn't run the suite is misconfigured — fix the wiring, **never** route around it.
|
|
63
|
-
- **Unit and E2E trees stay separate.** Vitest/Jest suites in `tests/`; Playwright/Maestro suites in `e2e/` with their own config — **never** mixed into the unit runner's glob.
|
|
63
|
+
- **Unit and E2E trees stay separate.** Vitest/Jest suites in `tests/`; Playwright/Maestro suites in `e2e/` with their own config — **never** mixed into the unit runner's glob. The e2e config **owns its world** — it builds, seeds, and boots the app itself (a `webServer` block or equivalent), so a journey passes from a cold shell like any other test and "the world was not running" is never why one did not run.
|
|
64
64
|
- **The real-renderer stratum is a third tree** — `tests/rendered/` under the unit runner's *browser mode*, its own config, kept to geometry regressions that shipped and the composition assertions a component library cannot make alone. It is not a second component suite; anything a simulated DOM can prove stays in `tests/component/`.
|
|
65
65
|
- **No test doubles for what you own.** Prefer real instances — in-memory SQLite for drizzle, the actual Express app via `supertest` — over mocks; mock **only** true externals (network via `msw`, time via the runner's fake timers).
|
|
66
66
|
- **Determinism.** No real network, no real time, no shared mutable state between tests; every test **must** pass alone, in any order, **and from a cold shell** — never against a hand-started dev server (Workers: `@cloudflare/vitest-pool-workers`; Express: `supertest` against the app instance).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: testing
|
|
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'."
|
|
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 (on a rendered surface every user-observable promise lives in the surface's journey; component tests are residual); 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
6
|
# Testing — Ledger, Layers, Gate
|
|
@@ -30,9 +30,9 @@ You derive the tests an implementation plan owes and pin them into the plan as a
|
|
|
30
30
|
|
|
31
31
|
### Layer assignment
|
|
32
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)
|
|
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; server-side error paths, races, and idempotency belong there with injected faults (network mock, fake timers, forced constraint). **A journey is the default for a rendered surface** — any promise a user could observe in a real browser (what a screen shows over real rows, what a click changes, where navigation lands and what the URL carries, what a refused or failed request shows) is owed at layer 8 as `extend journey <surface>`, never as a component test; the surface's own fault paths (a 500 on load, a 400 on submit) stay in the journey too, injected at the app's own endpoint from the browser. What a rendered surface still owes below the browser is enumerated in § Placement discipline: a pure derivation with real branches (layer 6), a regression pin for a state the journey world cannot reach (layer 7), the wire's error taxonomy (layer 5), and geometry that shipped wrong (7b).
|
|
34
34
|
|
|
35
|
-
**The
|
|
35
|
+
**The journey world is a precondition, not an excuse.** A rendered surface owes its journeys whether or not the repo's e2e config boots today. If it does not — no config, a world pointed at a deleted package, a job parked in CI — the plan's **first** obligation row is wiring it (`e2e · <config> · the world builds, seeds, boots and one journey passes from a cold shell`), and every journey row names it as a dependency. Emitting `blocked` rows in place of journeys and routing the promises down to component tests is the failure this rule exists to stop: a broken world that nothing obliges anyone to fix stays broken, and every UI promise for months lands in a simulated DOM. Density is one spec file per surface and one `test()` per user journey, carrying as many assertions as the journey has observable steps — prefer **"extend journey `<surface>`"** over a new file.
|
|
36
36
|
|
|
37
37
|
### Run (plan-gate)
|
|
38
38
|
|
|
@@ -41,7 +41,7 @@ Assign each behavior the **cheapest layer that can prove it**, then name the ora
|
|
|
41
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
42
|
2. **Assign layer + oracle** per behavior; cheapest proving layer wins.
|
|
43
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. **
|
|
44
|
+
4. **Route by surface**: a user-observable promise on a rendered surface → `e2e` (its surface's journey, with the world-wiring row first if the world does not boot); server behavior → `integration`; only what § Placement discipline keeps below the browser → `component` / `unit`.
|
|
45
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
46
|
|
|
47
47
|
```
|
|
@@ -76,9 +76,9 @@ The maximum form, for a full-stack application. Each layer names what only it ca
|
|
|
76
76
|
| 4 | **Contract artifact** | Nothing behavioral. Typed fixtures / interception handlers **compiled from the provider's own types**, exported for every consumer-side test | The typechecker — drift from the provider is a compile error | No runner; a module the provider owns |
|
|
77
77
|
| 5 | **Client data** | The consumer's half of the wire: error taxonomy, parsing, byte fidelity, stream reassembly, retry/abort | Intercepted network serving layer-4 artifacts; no DOM | `msw`-class interception |
|
|
78
78
|
| 6 | **View logic** | Pure presentation derivations: grouping, ordering, status vocabularies, formatting | Table-driven equality | Plain runner |
|
|
79
|
-
| 7 | **Component / surface** |
|
|
79
|
+
| 7 | **Component / surface** | **Residual on any stack that has layer 8.** Regression pins for what the journey world cannot reach: a state only a fixture the world lacks can produce, a flow behind an external system (a GitHub installation, an AI reply), an orchestration detail no user step exposes (a debounce, a buffer surviving an abort) | Rendered output a user can observe; network answered by layer 4 | DOM testing-library class + interception — never the default home of a UI promise |
|
|
80
80
|
| 7b | **Rendered composition** | Defects only a real rendering engine produces: geometry (overlap, clipping, a container that grows when a panel opens), the theme cascade where your tokens meet a component library's, focus order and accessible names through a third-party primitive | Measurement in a real browser engine — `getBoundingClientRect`, computed style, the accessibility tree | Real-renderer component runner (the unit runner's browser mode), **never** a simulated DOM |
|
|
81
|
-
| 8 | **
|
|
81
|
+
| 8 | **Journeys** | Everything a user can observe on a rendered surface: what a screen shows over real rows, what an action changes, where navigation lands and what the URL carries, what a refused or failed request shows, a create-then-delete round trip — and composition: the build serves, auth flows end to end | A real browser (or the real binary/wire, for a stack that renders nothing) against the really-running app over a seeded world; faults injected at the app's own endpoints | Browser/process driver class; **one spec file per surface**, one test per user journey, variants wherever a user can see them |
|
|
82
82
|
|
|
83
83
|
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.
|
|
84
84
|
|
|
@@ -88,7 +88,7 @@ Layer 4 is the hinge. Every layer ≥5 runs on the assumption that layers 1–3
|
|
|
88
88
|
|
|
89
89
|
| Application shape | Strata present |
|
|
90
90
|
| --- | --- |
|
|
91
|
-
| Full-stack web app | all eight |
|
|
91
|
+
| Full-stack web app | all eight — 8 is the home of every UI promise, 7 is residual |
|
|
92
92
|
| Backend-only service | 1, 2, 3, 8 |
|
|
93
93
|
| CLI | 2, 3 (the CLI's argv/stdout contract), 8 |
|
|
94
94
|
| Frontend on a third-party API | 4 (derived from the API's published spec), 5, 6, 7, 8 |
|
|
@@ -98,16 +98,16 @@ Derive the stack first, then place tests: a placement argument that starts "this
|
|
|
98
98
|
|
|
99
99
|
### Placement discipline
|
|
100
100
|
|
|
101
|
-
- **
|
|
101
|
+
- **Three conditions put a UI test below a real browser; all must hold.** *(a)* Its assertion names a **product promise**, not a construction detail — litmus: would it survive a total visual redesign that changes no behavior? `not.toHaveProperty("taskMode")` survives; `classList.contains("w-24")` does not. *(b)* The defect it catches can **originate below the layout engine** — a wrong wire path, a dropped field, a state-machine bug, a wrong string. *(c)* **The surface's journey cannot reach it** — the state needs a fixture the seeded world lacks, an external system, or an orchestration detail no user step exposes. Fail (a) or (b) and the honest home is 7b; fail (c) and the home is the journey, as one more assertion.
|
|
102
102
|
- **Geometry may never be asserted in a runner without a layout engine.** There is no valid proxy: a class token is not a width, and a test that pretends otherwise is simultaneously brittle and blind. When the only available runner simulates the DOM, the behavior is driven and pinned at 7b — never approximated.
|
|
103
103
|
- **An assertion provable at two layers is misplaced — it belongs at the lower one.** The duplication check is mechanical, not judgment.
|
|
104
|
-
- **Behavior variants sink to the cheapest layer that owns them** —
|
|
105
|
-
- **One representative path upward.** Layer 3 keeps one happy path per route plus exhaustive coverage of *contract edges only
|
|
104
|
+
- **Behavior variants sink to the cheapest layer that owns them** — a derivation's variants to 6, a rule's to 2. A variant a user can *see* (a second sort order, a filtered roster, a refused form) belongs in the journey that shows it, as one more assertion, not as a component case. A variant at layer 3 pays wire tax on a domain test.
|
|
105
|
+
- **One representative path upward for the server; the whole surface for the browser.** Layer 3 keeps one happy path per route plus exhaustive coverage of *contract edges only*. Layer 8 keeps one spec per surface covering everything a user can reach in the seeded world — the count is set by the surface, never by a cap. A cap on journeys is what pushes UI promises into a simulated DOM, and a simulated DOM talks to a hand-written fake of the wire, so a route rename or an envelope change passes it green.
|
|
106
106
|
- **7b stays small by construction.** It holds geometry regressions that actually shipped and the handful of composition assertions a component library's contract cannot make alone (the theme cascade, the accessible name through its primitive). It is not a second component suite — a behavior that a simulated DOM can prove has no business paying a browser's price.
|
|
107
107
|
- **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.
|
|
108
108
|
- **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.
|
|
109
109
|
|
|
110
|
-
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
|
|
110
|
+
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 more assertion in its surface's journey, zero backend tests. A flat suite pays N×M, because every top-layer path implicitly re-verifies every behavior beneath it.
|
|
111
111
|
|
|
112
112
|
### The tree
|
|
113
113
|
|
|
@@ -119,10 +119,10 @@ tests/
|
|
|
119
119
|
contract/ L4 — no runner; typed fixtures + interception handlers
|
|
120
120
|
client/ L5 — plain unit runner
|
|
121
121
|
view/ L6 — plain unit runner
|
|
122
|
-
component/ L7 — unit runner + DOM environment (per-file opt-in)
|
|
122
|
+
component/ L7 — unit runner + DOM environment (per-file opt-in); residual where e2e/ exists
|
|
123
123
|
rendered/ L7b — the same runner in browser mode; a real engine, kept small
|
|
124
124
|
helpers/ shared seeds, fakes, and builders — owned by no stratum
|
|
125
|
-
e2e/ L8 — its own config, never mixed into the unit runner's glob
|
|
125
|
+
e2e/ L8 — one spec per surface over a seeded world; its own config, never mixed into the unit runner's glob
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
- **A stratum the app lacks gets no directory** — an empty `component/` in a backend service is a lie about the app's shape.
|
|
@@ -147,7 +147,7 @@ When writing or discharging a ledger row, name the stratum, not just the altitud
|
|
|
147
147
|
|
|
148
148
|
1. **Derive the stack** from the application's real boundaries (storage? published wire? separate client? DOM?).
|
|
149
149
|
2. **Place each behavior** by the litmus; name stratum, oracle, technology class; take the concrete framework and file placement from `testing.md`.
|
|
150
|
-
3. **Flag violations, don't just place** — assertions provable lower, variants above
|
|
150
|
+
3. **Flag violations, don't just place** — assertions provable lower, derivation variants above 6, hand-written wire fixtures above layer 4, spies where fakes should model state, and UI promises pinned at 7 that the surface's journey can reach.
|
|
151
151
|
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.
|
|
152
152
|
|
|
153
153
|
---
|
|
@@ -168,7 +168,7 @@ Runs as the tail of every build pass — the session opens it the moment the uni
|
|
|
168
168
|
Cheap oracles have not stopped mattering — they stopped being a gate you pass before looking. Suites, the CLI binary, and HTTP against the running worker run in the same pass and are what the drive's findings get **pinned into** (rung 3). On a surface with no rendering — a CLI, a service, a library — this rung is its own real binary or real wire, and the visual clause simply does not apply.
|
|
169
169
|
|
|
170
170
|
**A failing checklist assertion is never softened to make a run green** — record the defect, send it to the backlog.
|
|
171
|
-
3. **Pin the durable behaviors — once, against the driven code** — as suite tests per `testing.md`, naming each test's stratum (§ Bridge). Pins are written after the drive and the fixes, never beside the first attempt: a pin written earlier is rewritten on every iteration and proves only that its author and the code agree. **Pinning is written inline, by the agent that drove** — it alone knows what the drive found and what each pin must therefore assert; a sub-agent briefed from the ledger row reproduces the row, not the finding. **A pinned test must pass from a cold shell** — `pnpm test` with no dev server hand-started, no seeded state assumed beyond what the suite builds for itself. A test whose oracle is "fetch the server I happen to have running" is a driven check wearing a test's name: it goes red the moment the session ends (on Workers, `@cloudflare/vitest-pool-workers` is the self-contained harness; an Express app is driven via `supertest`, never a port). Before discharging, run the suite once from a clean shell — a suite that needs the session's environment discharges nothing. A test is a permanent maintenance cost, bought deliberately:
|
|
171
|
+
3. **Pin the durable behaviors — once, against the driven code** — as suite tests per `testing.md`, naming each test's stratum (§ Bridge). **On a rendered surface the pin is the surface's journey first**: the drive just performed *is* the journey, so write it down as the test against the seeded world, and reach for a component test only where § Placement discipline's third condition holds. Pins are written after the drive and the fixes, never beside the first attempt: a pin written earlier is rewritten on every iteration and proves only that its author and the code agree. **Pinning is written inline, by the agent that drove** — it alone knows what the drive found and what each pin must therefore assert; a sub-agent briefed from the ledger row reproduces the row, not the finding. **A pinned test must pass from a cold shell** — `pnpm test` with no dev server hand-started, no seeded state assumed beyond what the suite builds for itself. A test whose oracle is "fetch the server I happen to have running" is a driven check wearing a test's name: it goes red the moment the session ends (on Workers, `@cloudflare/vitest-pool-workers` is the self-contained harness; an Express app is driven via `supertest`, never a port). Before discharging, run the suite once from a clean shell — a suite that needs the session's environment discharges nothing. A test is a permanent maintenance cost, bought deliberately:
|
|
172
172
|
|
|
173
173
|
| Earns a suite test | Verified by driving alone |
|
|
174
174
|
| --- | --- |
|
|
@@ -176,9 +176,10 @@ Runs as the tail of every build pass — the session opens it the moment the uni
|
|
|
176
176
|
| A bug that occurred once — its regression test | A one-off migration or backfill's output |
|
|
177
177
|
| **A layout defect that shipped** — its geometry pin, at 7b, measured in a real engine | Layout a judged drive has looked at and found correct — looking is the proof, and a pin buys nothing until something breaks |
|
|
178
178
|
| An error path, race, or idempotency guarantee | Behavior a type or the compiler already makes unshippable |
|
|
179
|
+
| A user-observable step of a surface — as an assertion in its journey | A same-branch table row: an `it.each` earns one row per branch or boundary, never one per literal |
|
|
179
180
|
|
|
180
|
-
A driven check is evidence, not an artifact — it proves the change works today and leaves nothing behind; that is why this rung exists.
|
|
181
|
-
4. **Run the affected suite
|
|
181
|
+
A driven check is evidence, not an artifact — it proves the change works today and leaves nothing behind; that is why this rung exists. Two hygiene rules hold at every pin: **a suite whose subject is no longer reachable from the product's entry points is deleted with the code path**, never repointed to keep it green; and **a case that asserts nothing beyond "it did not throw" is not a pin** — give it an assertion on resulting state or delete it.
|
|
182
|
+
4. **Run the affected suite and the affected surface's journeys from a cold shell** — the e2e config builds, seeds and boots its own world, so "needs the world running" is never a reason a journey did not run.
|
|
182
183
|
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). **Open by re-reading the ledger from the plan file on disk, never from conversation memory** — a context-held copy is how rows get missed. **Write each row's outcome into its `Outcome` cell in the plan file** — exactly `satisfied`, `owes nothing — {reason}`, or `blocked → backlog` — because the Stop wall and CI read that cell, and a row left unexamined blocks completion.
|
|
183
184
|
6. **Report results honestly, including failures**; record which test now guards each checklist section (`Pinned:` traces) and each section's `AC:` line where the repo carries a keyed acceptance-criteria source, closing with the coverage count — `AC coverage: {n}/{m}; uncovered: {keys} → backlog`.
|
|
184
185
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "etna",
|
|
3
3
|
"name": "etna",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.20",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -10,5 +10,5 @@
|
|
|
10
10
|
"seeds": [],
|
|
11
11
|
"tools": [],
|
|
12
12
|
"docsRoot": "docs",
|
|
13
|
-
"digest": "
|
|
13
|
+
"digest": "4c5fe7575b80"
|
|
14
14
|
}
|
|
@@ -60,7 +60,7 @@ Test packages install at `latest` — outside the approved-packages pin set. Fak
|
|
|
60
60
|
|
|
61
61
|
- **Suite, not scripts.** Tests live in the canonical test tree — `tests/` mirroring `src/`, or the convention the repo already uses — named `<subject>.test.ts`; shared fixtures, factories, and helpers in `tests/helpers/` (or the suite's existing shared home).
|
|
62
62
|
- **Wire the canonical command.** `"test"` in `package.json` runs the whole unit suite (`vitest run`); e2e under `"test:e2e"`. A repo where `pnpm test` / `npm test` doesn't run the suite is misconfigured — fix the wiring, **never** route around it.
|
|
63
|
-
- **Unit and E2E trees stay separate.** Vitest/Jest suites in `tests/`; Playwright/Maestro suites in `e2e/` with their own config — **never** mixed into the unit runner's glob.
|
|
63
|
+
- **Unit and E2E trees stay separate.** Vitest/Jest suites in `tests/`; Playwright/Maestro suites in `e2e/` with their own config — **never** mixed into the unit runner's glob. The e2e config **owns its world** — it builds, seeds, and boots the app itself (a `webServer` block or equivalent), so a journey passes from a cold shell like any other test and "the world was not running" is never why one did not run.
|
|
64
64
|
- **The real-renderer stratum is a third tree** — `tests/rendered/` under the unit runner's *browser mode*, its own config, kept to geometry regressions that shipped and the composition assertions a component library cannot make alone. It is not a second component suite; anything a simulated DOM can prove stays in `tests/component/`.
|
|
65
65
|
- **No test doubles for what you own.** Prefer real instances — in-memory SQLite for drizzle, the actual Express app via `supertest` — over mocks; mock **only** true externals (network via `msw`, time via the runner's fake timers).
|
|
66
66
|
- **Determinism.** No real network, no real time, no shared mutable state between tests; every test **must** pass alone, in any order, **and from a cold shell** — never against a hand-started dev server (Workers: `@cloudflare/vitest-pool-workers`; Express: `supertest` against the app instance).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.12",
|
|
4
4
|
"description": "Bootstrap a project with a Nurix harness — copies the harness's CLAUDE.md, skills, agents, rules, and hooks directly into the project from a bundled payload (no marketplace fetch, no network).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"agent",
|
|
43
43
|
"plugin"
|
|
44
44
|
],
|
|
45
|
-
"etnaFingerprint": "
|
|
45
|
+
"etnaFingerprint": "d525a2adfbe5"
|
|
46
46
|
}
|