@nurix/etna 0.19.46 → 0.19.48

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/README.md CHANGED
@@ -67,7 +67,7 @@ Listed by `npx --yes @nurix/etna` (no args) using the bundled `data/harness-inde
67
67
 
68
68
  | Harness | Source in etna | What it ships |
69
69
  | --- | --- | --- |
70
- | `code` | [`harness/code/`](../../harness/code/) | The canonical Nurix code-project harness — 6 governance/session agents, 6 workflow skills, 7 stack rules, the ownership-index `SessionStart` hook |
70
+ | `code` | [`harness/code/`](../../harness/code/) | The canonical Nurix code-project harness — 6 governance/session agents, 7 workflow skills, 7 stack rules, the ownership-index `SessionStart` hook |
71
71
  | `discover` | [`harness/discover/`](../../harness/discover/) | The product-discovery harness — 11 owned dimension-collector agents and 9 owned method rules, plus a `tooling-recommendations.md` at the project root. No skills, no hooks |
72
72
  | `discussion` | [`harness/discussion/`](../../harness/discussion/) | The deliberation-workspace harness — 2 owned agents, 2 owned rules, and the `update-topics` hook that keeps the topic index current. No skills |
73
73
  | `etna` | [`harness/etna/`](../../harness/etna/) | The forge's own governance harness — a deliberate fork of `code` reframed for authoring artifacts: 5 agents, 4 skills, 4 rules, the ownership-index hook |
@@ -1,5 +1,5 @@
1
1
  {
2
- "fingerprint": "efebb48c460d",
2
+ "fingerprint": "6af99ea8c41f",
3
3
  "harnesses": {
4
4
  "apollo": {
5
5
  "plugin": "apollo",
@@ -15,8 +15,8 @@ Ground every edit in this ladder, in order. Never invent content; a fact you can
15
15
 
16
16
  1. **`auto:` rows are recomputable from code + docs on any machine — fix directly.** A `dead-glob` row: read the owning doc, find where the files went (`git log --follow`, glob search), update its `owns:` frontmatter. A `header-missing` row: add the file-path header comment in the file's native comment syntax. A `doc-lint` row: fix the finding, or add `<!-- doc-lint-disable: id -->` with a reason beside it when the flagged text is deliberate. An `index-row-missing` row: add the doc's row to `docs/README.md`. A `doc-implicated` row: read the diff of the named source file (`git diff` / `git log -p`), and update the owning doc **only where the diff contradicts documented behavior**; if nothing documented changed, the row's resolution is deleting it.
17
17
  2. **`note:` rows — expand strictly from the stated context.** The one-line context after the area is everything the session chose to hand you; land it in the destination the row names (`lands-in:` an owning doc, the backlog, or memory), written in that document's genre.
18
- 3. **A thin note, same machine only** — read a timestamp-anchored window of the transcript JSONL at the row's `tp=` path around its `ts=` stamp (never the whole file). The stamp is best-effort and dead on other clones — expected; treat an unreadable or missing transcript as "no window".
19
- 4. **Still ungrounded** — replace the row with a `needs-session:` row (`- needs-session: {date} · {what you could not ground} → main session resolves | refs={original ts}`) and stop working that row.
18
+ 3. **A thin note, same machine only** — derive the transcript from the row's `sid=` tail: the session JSONL lives at `~/.claude/projects/{project-dir-slug}/{sid}.jsonl` (the slug is the project path with `/` → `-`). Search it for the row's file path or area and read only that window — never the whole file. The sid is best-effort and dead on other clones — expected; treat an unreadable or missing transcript as "no window".
19
+ 4. **Still ungrounded** — replace the row with a `needs-session:` row (`- needs-session: {date} · {what you could not ground} → main session resolves | refs={original row}`) and stop working that row.
20
20
 
21
21
  ## Rules
22
22
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "apollo",
3
3
  "name": "apollo",
4
- "version": "1.0.3",
4
+ "version": "1.1.0",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -14,5 +14,5 @@
14
14
  "docs/history/backlog.md",
15
15
  "docs/memory/main.md"
16
16
  ],
17
- "digest": "25bb37bef74c"
17
+ "digest": "5d04f43486c8"
18
18
  }
@@ -0,0 +1,73 @@
1
+ ---
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'."
4
+ ---
5
+
6
+ # Layered Test Strategy
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.
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.
11
+
12
+ ## The stack
13
+
14
+ 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
+
16
+ | # | Layer | Owns (defects only it can originate) | Oracle | Technology class |
17
+ | --- | --- | --- | --- | --- |
18
+ | 1 | **Persistence schema** | Invariants the storage engine enforces against any code: uniqueness, checks, cascades, append-only triggers | The engine's refusal of a forbidden write | The real engine, in-memory — never a mock of storage you own |
19
+ | 2 | **Domain** | Business rules and state transitions, via direct calls | Resulting state in real storage | Real datastore + modeled fakes for external ports |
20
+ | 3 | **Service contract** | The published wire: authn/authz at the door, envelope shape, status mapping, field projection, stream/event grammar. For a CLI this is argv/stdout/exit codes; for a library, its exported API | Requests against the real app instance, in-process | `supertest` / `SELF.fetch` class — never a spawned server, never naked handler functions |
21
+ | 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 |
22
+ | 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 |
23
+ | 6 | **View logic** | Pure presentation derivations: grouping, ordering, status vocabularies, formatting | Table-driven equality | Plain runner |
24
+ | 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
+ | 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
+
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.
28
+
29
+ ## How many layers — derived, never chosen
30
+
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.
32
+
33
+ | Application shape | Strata present |
34
+ | --- | --- |
35
+ | Full-stack web app | all eight |
36
+ | Backend-only service | 1, 2, 3, 8 |
37
+ | CLI | 2, 3 (the CLI's argv/stdout contract), 8 |
38
+ | Frontend on a third-party API | 4 (derived from the API's published spec), 5, 6, 7, 8 |
39
+ | Library | 2 only — its exported API is the contract, its tests are its consumers |
40
+
41
+ Derive the stack first, then place tests: a placement argument that starts "this feels like an integration test" has skipped the derivation.
42
+
43
+ ## Placement discipline
44
+
45
+ - **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.
50
+
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.
52
+
53
+ ## Bridge to the obligation ledger
54
+
55
+ 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:
56
+
57
+ | Ledger altitude | Strata it covers |
58
+ | --- | --- |
59
+ | unit | 2 (pure domain), 5, 6 |
60
+ | integration | 1, 2 (stateful), 3 |
61
+ | component | 7 |
62
+ | e2e | 8 |
63
+
64
+ 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.
65
+
66
+ ## Run
67
+
68
+ Given a package, a plan, or a "where does this test go?" question:
69
+
70
+ 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.
71
+ 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`.
72
+ 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.
73
+ 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.
@@ -15,8 +15,8 @@ Ground every edit in this ladder, in order. Never invent content; a fact you can
15
15
 
16
16
  1. **`auto:` rows are recomputable from code + docs on any machine — fix directly.** A `dead-glob` row: read the owning doc, find where the files went (`git log --follow`, glob search), update its `owns:` frontmatter. A `header-missing` row: add the file-path header comment in the file's native comment syntax. A `doc-lint` row: fix the finding, or add `<!-- doc-lint-disable: id -->` with a reason beside it when the flagged text is deliberate. An `index-row-missing` row: add the doc's row to `docs/README.md`. A `doc-implicated` row: read the diff of the named source file (`git diff` / `git log -p`), and update the owning doc **only where the diff contradicts documented behavior**; if nothing documented changed, the row's resolution is deleting it.
17
17
  2. **`note:` rows — expand strictly from the stated context.** The one-line context after the area is everything the session chose to hand you; land it in the destination the row names (`lands-in:` an owning doc, the backlog, or memory), written in that document's genre.
18
- 3. **A thin note, same machine only** — read a timestamp-anchored window of the transcript JSONL at the row's `tp=` path around its `ts=` stamp (never the whole file). The stamp is best-effort and dead on other clones — expected; treat an unreadable or missing transcript as "no window".
19
- 4. **Still ungrounded** — replace the row with a `needs-session:` row (`- needs-session: {date} · {what you could not ground} → main session resolves | refs={original ts}`) and stop working that row.
18
+ 3. **A thin note, same machine only** — derive the transcript from the row's `sid=` tail: the session JSONL lives at `~/.claude/projects/{project-dir-slug}/{sid}.jsonl` (the slug is the project path with `/` → `-`). Search it for the row's file path or area and read only that window — never the whole file. The sid is best-effort and dead on other clones — expected; treat an unreadable or missing transcript as "no window".
19
+ 4. **Still ungrounded** — replace the row with a `needs-session:` row (`- needs-session: {date} · {what you could not ground} → main session resolves | refs={original row}`) and stop working that row.
20
20
 
21
21
  ## Rules
22
22
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "harness": "code",
3
3
  "name": "code",
4
- "version": "3.5.11",
4
+ "version": "3.6.0",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
8
8
  ".vscode/settings.json"
9
9
  ],
10
10
  "seeds": [],
11
- "digest": "9074f75982ad"
11
+ "digest": "d1663c2f882b"
12
12
  }
@@ -0,0 +1,73 @@
1
+ ---
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'."
4
+ ---
5
+
6
+ # Layered Test Strategy
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.
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.
11
+
12
+ ## The stack
13
+
14
+ 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
+
16
+ | # | Layer | Owns (defects only it can originate) | Oracle | Technology class |
17
+ | --- | --- | --- | --- | --- |
18
+ | 1 | **Persistence schema** | Invariants the storage engine enforces against any code: uniqueness, checks, cascades, append-only triggers | The engine's refusal of a forbidden write | The real engine, in-memory — never a mock of storage you own |
19
+ | 2 | **Domain** | Business rules and state transitions, via direct calls | Resulting state in real storage | Real datastore + modeled fakes for external ports |
20
+ | 3 | **Service contract** | The published wire: authn/authz at the door, envelope shape, status mapping, field projection, stream/event grammar. For a CLI this is argv/stdout/exit codes; for a library, its exported API | Requests against the real app instance, in-process | `supertest` / `SELF.fetch` class — never a spawned server, never naked handler functions |
21
+ | 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 |
22
+ | 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 |
23
+ | 6 | **View logic** | Pure presentation derivations: grouping, ordering, status vocabularies, formatting | Table-driven equality | Plain runner |
24
+ | 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
+ | 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
+
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.
28
+
29
+ ## How many layers — derived, never chosen
30
+
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.
32
+
33
+ | Application shape | Strata present |
34
+ | --- | --- |
35
+ | Full-stack web app | all eight |
36
+ | Backend-only service | 1, 2, 3, 8 |
37
+ | CLI | 2, 3 (the CLI's argv/stdout contract), 8 |
38
+ | Frontend on a third-party API | 4 (derived from the API's published spec), 5, 6, 7, 8 |
39
+ | Library | 2 only — its exported API is the contract, its tests are its consumers |
40
+
41
+ Derive the stack first, then place tests: a placement argument that starts "this feels like an integration test" has skipped the derivation.
42
+
43
+ ## Placement discipline
44
+
45
+ - **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.
50
+
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.
52
+
53
+ ## Bridge to the obligation ledger
54
+
55
+ 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:
56
+
57
+ | Ledger altitude | Strata it covers |
58
+ | --- | --- |
59
+ | unit | 2 (pure domain), 5, 6 |
60
+ | integration | 1, 2 (stateful), 3 |
61
+ | component | 7 |
62
+ | e2e | 8 |
63
+
64
+ 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.
65
+
66
+ ## Run
67
+
68
+ Given a package, a plan, or a "where does this test go?" question:
69
+
70
+ 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.
71
+ 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`.
72
+ 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.
73
+ 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/etna",
3
- "version": "0.19.46",
3
+ "version": "0.19.48",
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": "efebb48c460d"
45
+ "etnaFingerprint": "6af99ea8c41f"
46
46
  }