@nurix/etna 0.20.2 → 0.20.4
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 +20 -14
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/CLAUDE.md +15 -8
- package/data/harnesses/code/agents/bookkeeper.md +1 -1
- package/data/harnesses/code/agents/issue-bridge.md +16 -0
- package/data/harnesses/code/agents/maintenance.md +36 -3
- package/data/harnesses/code/docs/README.md +1 -1
- package/data/harnesses/code/docs/history/backlog.md +1 -1
- package/data/harnesses/code/docs/structure.md +2 -2
- package/data/harnesses/code/harness.json +12 -2
- package/data/harnesses/code/hooks/awareness.mjs +75 -14
- package/data/harnesses/code/hooks/compliance.mjs +26 -18
- package/data/harnesses/code/hooks/lib/exempt.mjs +7 -5
- package/data/harnesses/code/hooks/lib/frozen.mjs +9 -6
- package/data/harnesses/code/hooks/lib/ledger.mjs +26 -4
- package/data/harnesses/code/hooks/lib/ownership.mjs +73 -2
- package/data/harnesses/code/hooks/lib/plans.mjs +2 -2
- package/data/harnesses/code/hooks/lib/repo.mjs +22 -0
- package/data/harnesses/code/hooks/lib/state.mjs +38 -2
- package/data/harnesses/code/hooks/lib/sweep.mjs +37 -15
- package/data/harnesses/code/rules/testing.md +20 -14
- package/data/harnesses/code/skills/boundary/SKILL.md +2 -2
- package/data/harnesses/code/skills/codegraph/SKILL.md +57 -0
- package/data/harnesses/code/skills/documentation/SKILL.md +12 -5
- package/data/harnesses/code/skills/documentation/references/frontmatter.md +29 -4
- package/data/harnesses/code/skills/documentation/templates/behavioral-checklist.md +18 -1
- package/data/harnesses/code/skills/documentation/templates/feature.md +1 -0
- package/data/harnesses/code/skills/documentation/templates/plan.md +3 -1
- package/data/harnesses/code/skills/documentation/templates/ux-behavior.md +1 -1
- package/data/harnesses/code/skills/issue-bridge/SKILL.md +55 -0
- package/data/harnesses/code/skills/issue-bridge/references/trackers/github.md +37 -0
- package/data/harnesses/code/skills/issue-bridge/references/trackers/jira.md +40 -0
- package/data/harnesses/code/skills/issue-bridge/references/trackers/linear.md +37 -0
- package/data/harnesses/code/skills/testing/SKILL.md +24 -8
- package/data/harnesses/delivery/agents/maintenance.md +36 -3
- package/data/harnesses/delivery/harness.json +4 -2
- package/data/harnesses/delivery/skills/documentation/SKILL.md +12 -5
- package/data/harnesses/delivery/skills/documentation/references/frontmatter.md +29 -4
- package/data/harnesses/delivery/skills/documentation/templates/behavioral-checklist.md +18 -1
- package/data/harnesses/delivery/skills/documentation/templates/feature.md +1 -0
- package/data/harnesses/delivery/skills/documentation/templates/plan.md +3 -1
- package/data/harnesses/delivery/skills/documentation/templates/ux-behavior.md +1 -1
- package/data/harnesses/discover/harness.json +2 -0
- package/data/harnesses/discussion/harness.json +2 -0
- package/data/harnesses/etna/agents/maintenance.md +36 -3
- package/data/harnesses/etna/harness.json +4 -2
- package/data/harnesses/etna/rules/testing.md +20 -14
- package/data/harnesses/etna/skills/boundary/SKILL.md +2 -2
- package/data/harnesses/etna/skills/documentation/SKILL.md +12 -5
- package/data/harnesses/etna/skills/documentation/references/frontmatter.md +29 -4
- package/data/harnesses/etna/skills/documentation/templates/behavioral-checklist.md +18 -1
- package/data/harnesses/etna/skills/documentation/templates/feature.md +1 -0
- package/data/harnesses/etna/skills/documentation/templates/plan.md +3 -1
- package/data/harnesses/etna/skills/documentation/templates/ux-behavior.md +1 -1
- package/data/harnesses/hr/harness.json +2 -0
- package/data/harnesses/kb-agi/harness.json +2 -0
- package/lib/install/plan.mjs +22 -1
- package/package.json +2 -2
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
| Regime | During the work | Proven |
|
|
10
10
|
| --- | --- | --- |
|
|
11
11
|
| **Plan-driven build** | No new test files. Code only — the loop stays fast | At the verification gate, before `Status: Complete` |
|
|
12
|
-
| **Bug fix** — a defect in shipped behavior |
|
|
13
|
-
| **Ad-hoc / non-plan change** |
|
|
12
|
+
| **Bug fix** — a defect in shipped behavior | Fix it, drive it, then write its regression test — in the same unit of work | Before the unit ends — the pin is written once, against the fixed code |
|
|
13
|
+
| **Ad-hoc / non-plan change** | Make the change, drive it, then write its test **when it alters promised behavior** — layout, copy, and config tweaks owe none | Before the unit ends, same discipline as a bug fix |
|
|
14
14
|
|
|
15
|
-
- **A bug fix cannot defer
|
|
15
|
+
- **A bug fix cannot defer past its own unit of work — but its pin comes after the fix is driven, never beside the first attempt.** A test written while the fix is still moving is rewritten on every iteration, and written by the fix's author beside the fix it proves only that the two agree. Write it once, against the fixed code; where the defect was subtle, run it once against the pre-fix code (stash the fix) so the pin is shown to bite — that is one command, not an iteration.
|
|
16
16
|
- **Ad-hoc work cannot defer — there is no ledger to defer into.** A non-plan change that alters no promised behavior — a pure refactor, a rename — owes no test.
|
|
17
17
|
|
|
18
18
|
## During the loop
|
|
@@ -21,18 +21,21 @@ Rules for the plan regime between the plan gate and the verification gate:
|
|
|
21
21
|
|
|
22
22
|
- **No new test files** — obligations wait in the plan's ledger (written at the plan gate by the `testing` skill).
|
|
23
23
|
- **Existing tests broken by the plan's intended behavior change are updated to the new contract before any mid-plan commit** — the shared branch never carries a red suite, and an update is never a weakening (§ Discipline).
|
|
24
|
-
- **A defect in the plan's own in-flux code is the loop iterating** — no test, no ledger row. **A pre-existing bug in shipped behavior is the bug-fix regime even mid-plan**: fix it
|
|
24
|
+
- **A defect in the plan's own in-flux code is the loop iterating** — no test, no ledger row. **A pre-existing bug in shipped behavior is the bug-fix regime even mid-plan**: fix it now and pin it at the build pass's pin step, with the unit's other pins — or send it to the backlog via Out-of-Scope Discovery. Never fix silently.
|
|
25
25
|
- **The ledger is amendable while the plan is open** — the gate discharges whatever it holds at gate time.
|
|
26
26
|
- **A mid-plan commit is a checkpoint, not a verification claim.**
|
|
27
27
|
|
|
28
28
|
## The gate
|
|
29
29
|
|
|
30
|
-
**
|
|
30
|
+
**The tail of every build pass, never a separate ask.** When the unit's code is written the gate runs — drive the real surface, fix what driving finds, pin, discharge — and the session does not stop to ask whether it should. What the user decides is the freeze and the commit: "ship it", "commit", "looks good" close a plan whose gate has already run; they never open one. Never flip `Status: Complete` without the gate. A plan with outstanding obligations stays open, whatever the code does.
|
|
31
|
+
|
|
32
|
+
- **Inside the build pass the order is fixed — code → drive → fix → pin.** Tests written while the code is still moving are rewritten on every iteration, and written by the code's author beside the code they pass by construction; a judged drive is what finds the defects (below). Pinning is the last step and happens once, against code the drive has already shown to be right. The procedure — checklist, drive-then-pin ladder, row discharge, what earns a durable test — is the `testing` skill (gate mode).
|
|
31
33
|
|
|
32
34
|
- **Precondition: the ledger exists.** A plan with no Test Obligations section is blocked at the gate — run the `testing` skill (plan-gate mode) on it first; an empty ledger passes vacuously and the gap surfaces only after the file is frozen.
|
|
33
|
-
- **The verdict is never delegated** (the root policy file § Delegation), and where browser work is main-session-only (its § Runtime Notes), the driving is too.
|
|
35
|
+
- **The verdict is never delegated** (the root policy file § Delegation), and where browser work is main-session-only (its § Runtime Notes), the driving is too. **The pins are written inline, by the agent that drove the surface** — the drive is what tells it which behaviors earned a test and what each one must assert, and a sub-agent briefed from the ledger alone holds neither. Nothing in the gate is dispatched: the checklist, the driving, the pinning, the cold-shell suite run and every discharge stay with one agent, so the state of the gate is never somewhere nobody is looking.
|
|
34
36
|
- **The gate needs no stronger tier than the session default** — a standard-tier session suffices whatever tier built the plan; the plan and its ledger carry all the state the gate needs, so gating in a fresh session loses nothing.
|
|
35
|
-
- **
|
|
37
|
+
- **On a rendered surface, browser driving LEADS the gate — it is not its final rung.** The order inverted deliberately: the defects that reach users through an interface are geometric and compositional, and a simulated DOM computes no layout, so a suite that is entirely green says nothing about whether anything is where it should be. **Reach the browser Chrome-CDP-first and Playwright second** — CDP drives a real browser interactively and needs no script written in advance, which is what discovery requires; Playwright is the fallback where CDP is unavailable and the tool of record for whatever must run unattended afterwards. Cheap oracles run in the same pass and are what the drive's findings get pinned into; they are no longer a gate to pass before looking. A surface with nothing rendered — a CLI, a service, a library — drives its own real binary or wire instead, and this clause does not apply.
|
|
38
|
+
- **A judged drive discovers; a scripted assertion guards.** Only the first finds a defect nobody has seen — a script asserting a control's value passes over a control drawn on top of its own label. Scripts are written after a judged drive names the class of defect, never generated ahead of one, and **a red script is never regenerated** — regeneration re-derives the assertion from the surface as it currently is, which turns a defect into the asserted truth with everything green.
|
|
36
39
|
- **Report what happened, including failures.** A gate that reports green while a suite is red is worse than no gate; state the failing test and its output.
|
|
37
40
|
- **Abandonment does not erase debt.** Before `Status: Abandoned` freezes a plan whose code was committed, convert its undischarged rows into backlog items; uncommitted code owes nothing.
|
|
38
41
|
|
|
@@ -40,13 +43,15 @@ Rules for the plan regime between the plan gate and the verification gate:
|
|
|
40
43
|
|
|
41
44
|
Framework choice is **never** ad hoc and never the user's problem mid-task: detect the stack, use its row; if the repo already has a runner, that runner wins — **never** introduce a second framework beside it.
|
|
42
45
|
|
|
43
|
-
| Stack | Unit / integration | Component | E2E | HTTP mocking | Don't use |
|
|
44
|
-
| --- | --- | --- | --- | --- | --- |
|
|
45
|
-
| Node + Vite / React (web) | `vitest` | `@testing-library/react` (+ `@testing-library/user-event`, `happy-dom`) | `@playwright/test` | `msw` | `jest`, `mocha`, `jasmine`, `cypress`, `enzyme` |
|
|
46
|
-
| Express APIs | `vitest` | — | `supertest` against the app instance | `msw` (outbound calls) | `jest`, `chai-http` |
|
|
47
|
-
| React Native / Expo | `jest` via the `jest-expo` preset | `@testing-library/react-native` | `maestro` | `msw` | `detox`, `enzyme` |
|
|
48
|
-
| Cloudflare Workers | `vitest` + `@cloudflare/vitest-pool-workers` | — | `@playwright/test` against `wrangler dev` | `msw` | `jest` |
|
|
49
|
-
| Rust | `cargo test` (built-in) | — | — | — | — |
|
|
46
|
+
| Stack | Unit / integration | Component | Rendered (7b) | Judged drive | E2E | HTTP mocking | Don't use |
|
|
47
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
48
|
+
| Node + Vite / React (web) | `vitest` | `@testing-library/react` (+ `@testing-library/user-event`, `happy-dom`) | `vitest` browser mode, Playwright provider | Chrome CDP, else `@playwright/test` headed | `@playwright/test` | `msw` | `jest`, `mocha`, `jasmine`, `cypress`, `enzyme` |
|
|
49
|
+
| Express APIs | `vitest` | — | — (nothing rendered) | the real wire — `curl`/HTTP against the running app | `supertest` against the app instance | `msw` (outbound calls) | `jest`, `chai-http` |
|
|
50
|
+
| React Native / Expo | `jest` via the `jest-expo` preset | `@testing-library/react-native` | `maestro` on a device/simulator | the simulator, watched | `maestro` | `msw` | `detox`, `enzyme` |
|
|
51
|
+
| Cloudflare Workers | `vitest` + `@cloudflare/vitest-pool-workers` | — | — (nothing rendered) | the real wire against `wrangler dev` | `@playwright/test` against `wrangler dev` | `msw` | `jest` |
|
|
52
|
+
| Rust | `cargo test` (built-in) | — | the app window, driven by the OS layer where one exists | the running binary, watched | — | — | — |
|
|
53
|
+
|
|
54
|
+
**The two new columns are the inversion made concrete.** *Rendered (7b)* is the unattended real-engine runner — where geometry pins live, and `—` where a stack renders nothing. *Judged drive* is what a person or an agent looks at when a change is finished, and it leads the gate on any stack that renders: Chrome CDP first because it drives a real browser with no script written in advance, Playwright headed where CDP is unavailable. A stack whose judged drive is `—` has no interface to look at, not permission to skip looking.
|
|
50
55
|
|
|
51
56
|
Test packages install at `latest` — outside the approved-packages pin set. Fake data comes from `@ngneat/falso` — **never** `@faker-js/faker` / `chance`.
|
|
52
57
|
|
|
@@ -55,6 +60,7 @@ Test packages install at `latest` — outside the approved-packages pin set. Fak
|
|
|
55
60
|
- **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).
|
|
56
61
|
- **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.
|
|
57
62
|
- **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
|
+
- **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/`.
|
|
58
64
|
- **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).
|
|
59
65
|
- **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).
|
|
60
66
|
|
|
@@ -63,13 +63,13 @@ The packs are a floor, not a fence: report any contract that passes the ships-gr
|
|
|
63
63
|
|
|
64
64
|
**The doc cross-check is free and you are the only one who can make it.** You already hold the touch list; resolving those paths against the ownership index and comparing the result to the plan's **Doc Obligations** rows costs one lookup. The plan's author wrote that ledger, so it is the one gate artifact nobody independently checked — a `None` that contradicts your touch list is the failure this section exists to catch. Report the gap; never edit the ledger yourself.
|
|
65
65
|
|
|
66
|
-
**BLOCK** = any CRITICAL. HIGH findings yield **PASS WITH AMENDMENTS** —
|
|
66
|
+
**BLOCK** = any CRITICAL. HIGH findings yield **PASS WITH AMENDMENTS** — the invoking session folds the amendments into the plan in the same pass, before the plan is shown as finished. Order amendments sequencing-first: many violations dissolve by reordering steps (expand-then-contract, migrate-before-code) rather than changing what ships — prefer that fix, drawn from the entry's safe-change sequence.
|
|
67
67
|
|
|
68
68
|
6. **Record the verdict in the plan's `Gates:` line.** Rewrite only your own segment, to exactly `Boundary: passed YYYY-MM-DD` (PASS, or PASS WITH AMENDMENTS once the amendments are folded) or `Boundary: blocked YYYY-MM-DD` (BLOCK). The compliance hook greps exactly this segment — implementation edits inside the plan's scope stay blocked while it reads `Boundary: pending` (the other gates are enforced by section presence, not segment text). A plan with no `Gates:` line predates the format; add the line rather than skipping the write-back.
|
|
69
69
|
|
|
70
70
|
## Rules
|
|
71
71
|
|
|
72
|
-
- **Read-only, one exception.** You never edit the plan's content, the code, or any doc — the
|
|
72
|
+
- **Read-only, one exception.** You never edit the plan's content, the code, or any doc — the invoking session folds your amendments into the plan in the same pass, then writes the segment to `passed`; a plan is not finished until it does. The single write you own is the plan's `Gates:` line (step 6), your segment only.
|
|
73
73
|
- **Verify in source.** Never flag from plan text alone — read both sides of every finding and cite them: code as `file:line`, doctrine as the doc path.
|
|
74
74
|
- **No other side, no violation.** An intersection with no traced consumer and no doctrine behind it isn't a violation — it's a **new edge**: report it in its own section with the hardening its mechanism prescribes (version the cache key now, make the enum append-only now), so the fragile edge is born safe.
|
|
75
75
|
- **A clean report is valid.** Zero violations means say so — the coverage table is the evidence it was checked, not skipped. Don't pad findings.
|
|
@@ -122,11 +122,18 @@ The two guarantees are complementary and both break on mixing: a reader holding
|
|
|
122
122
|
|
|
123
123
|
**Frontmatter is compulsory on every document this skill authors** — `type:` (the genre) on every
|
|
124
124
|
doc, plus the genre's own keys beside it; a document without its frontmatter fails the skill's
|
|
125
|
-
contract and is not done.
|
|
126
|
-
`module:` (optional), `keywords:` (required), `status:` (required),
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
contract and is not done. **There is no frontmatter-less genre.** A feature or UX doc carries the
|
|
126
|
+
full set — `owns:` (required), `module:` (optional), `keywords:` (required), `status:` (required),
|
|
127
|
+
`applies:` (the architecture docs it obeys; omitted when none), optional `last-reviewed:`. A
|
|
128
|
+
behavioral checklist (`docs/tests/`) carries `type: checklist` and `verifies:` — the doc whose
|
|
129
|
+
promises it proves. An acceptance-criteria doc (`docs/acceptance/`) carries `type:` and `status:`.
|
|
130
|
+
|
|
131
|
+
**The frontmatter is where the docs tree's edges live**, and a doc that declares none is a node
|
|
132
|
+
nothing can reach: no change implicates it, so it rots without the rot being detectable. Deriving
|
|
133
|
+
every edge — `owns:` from code, `verifies:` and `applies:` from the docs they answer to — is
|
|
134
|
+
[`references/frontmatter.md`](references/frontmatter.md), and it carries the rule the whole graph
|
|
135
|
+
rests on: **declare once at the consuming end, generate every inverse.** Every doc create or
|
|
136
|
+
update, and the `maintenance` agent's baseline pass, route there.
|
|
130
137
|
|
|
131
138
|
---
|
|
132
139
|
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
# Frontmatter Derivation —
|
|
1
|
+
# Frontmatter Derivation — the doc graph
|
|
2
2
|
|
|
3
|
-
Load when: creating a feature doc, updating one whose
|
|
3
|
+
Load when: creating a doc that declares an edge (a feature doc, a UX doc, a behavioral checklist), updating one whose scope moved, or running a baseline/reconcile pass over many docs.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**A docs tree is a graph, and every edge in it is declared in frontmatter.** Two kinds of edge exist: **doc → code** (`owns:`), and **doc → doc** (`verifies:`, `applies:`). The SessionStart hook reads them all and injects the generated indexes; there is no hand-maintained map of either kind.
|
|
6
|
+
|
|
7
|
+
## The one rule that keeps the graph honest
|
|
8
|
+
|
|
9
|
+
**An edge is declared once, at the consuming end. Every inverse is generated, never authored.**
|
|
10
|
+
|
|
11
|
+
A parent that hand-lists its children is a second thing to forget, and the moment the two directions can disagree nothing says which is right — you have doubled the staleness surface rather than halving it. So a checklist names the feature it verifies; the feature never lists its checklists. A feature names the architecture doc it obeys; the architecture doc never lists its consumers. Both inverses are derived on demand and injected, exactly as the ownership index already is.
|
|
12
|
+
|
|
13
|
+
The consuming end is also the end that **knows**. A checklist author knows what they are verifying; an architecture doc has no idea who will obey it next year.
|
|
6
14
|
|
|
7
15
|
## When to run
|
|
8
16
|
|
|
@@ -34,7 +42,21 @@ The single bounded-context label this feature belongs to, or omit it for a cross
|
|
|
34
42
|
|
|
35
43
|
`keywords:` are the discovery terms topic→doc lookup rides. `status:` is `active`, `draft`, or `deprecated` — it steers the model toward live docs and away from draft/deprecated ones.
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
## The doc → doc edges
|
|
46
|
+
|
|
47
|
+
A prose link is not an edge. It states that one doc mentions another; it never states **why**, so nothing can be derived from it — not who consumes a doctrine, not what to re-read before an edit, not what falls stale when a surface moves. A tree can carry hundreds of prose links and still have no graph. These two keys carry the relationships that matter; everything else stays prose.
|
|
48
|
+
|
|
49
|
+
**`verifies:`** — on a behavioral checklist (`docs/tests/{feature}.md`), the feature or UX doc(s) whose promises it proves. One value is the norm; several when a checklist spans a capability that two docs decide. A checklist carrying no `verifies:` is an orphan by construction: nothing implicates it when its subject changes, so it rots silently and the rot is undetectable.
|
|
50
|
+
|
|
51
|
+
**`applies:`** — on a feature or UX doc, the architecture docs it obeys. Not "docs I read" and not "docs I mention" — **obligation**: a doctrine this surface is bound by, such that a change to the doctrine implicates this doc. The narrow reading is the load-bearing one; a key that collects everything consulted degrades into a bibliography and stops meaning anything. A feature bound by nothing cross-cutting omits the key.
|
|
52
|
+
|
|
53
|
+
Cross-cutting *contracts* remain diagnosed by the `boundary` skill from source, never persisted — `applies:` records which doctrine a surface answers to, not which code it touches.
|
|
54
|
+
|
|
55
|
+
## What is not frontmatter, deliberately
|
|
56
|
+
|
|
57
|
+
**`Pinned:` stays a section-level line inside a checklist**, naming the suite tests that guard that section. It is already typed and already resolved mechanically by the `maintenance` agent. Hoisting it into frontmatter would flatten per-section detail into a doc-level list and duplicate what the sections already say — the exact double-declaration the one rule above forbids.
|
|
58
|
+
|
|
59
|
+
That edge has a property the others lack, and it is worth naming: **a `Pinned:` reference terminates.** A test file exists or it does not, so the edge is falsifiable by a resolver sweep with no judgement. An `owns:` glob is only falsifiable at the dangling end — a glob that resolves says nothing about whether the doc still describes what the code does. Terminal edges get resolver sweeps; non-terminal edges need a human or an agent to read both ends. Do not build a checker that pretends otherwise.
|
|
38
60
|
|
|
39
61
|
## Rules
|
|
40
62
|
|
|
@@ -42,3 +64,6 @@ A doc's dependencies on other docs are expressed inline in its prose (links); cr
|
|
|
42
64
|
- Use glob patterns for directories, explicit paths for individual files. At `active`, never list paths that don't exist in the codebase; a `draft`'s projected paths are confirmed at `active`.
|
|
43
65
|
- When a doc covers a deep feature with sub-specs (e.g., `editor/`), each sub-spec owns its own globs — don't duplicate a path across siblings.
|
|
44
66
|
- For baseline/reconcile with many docs, use parallel sub-agents to read source and propose `owns:` globs.
|
|
67
|
+
- **Never author an inverse.** No doc lists its verifiers, its consumers, or its dependents — those are generated. A hand-written "consumed by" table is a defect to delete, not a convention to follow.
|
|
68
|
+
- **Every edge value is a repo-relative doc path** (`docs/architecture/common/security.md`), never a bare filename and never a display title — the resolver is a path lookup, and a title changes without the file moving.
|
|
69
|
+
- **A dangling edge is a block, not a warning.** `verifies:` or `applies:` naming a doc that does not exist is a typo or a deletion; either way the graph is lying. Resolve at authoring time.
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
# Behavioral Checklist (docs/tests/{feature}.md)
|
|
2
2
|
|
|
3
|
-
Truth document. Use for: **how to verify one feature's promised behavior by driving the real product** — the browser, the CLI binary, HTTP against the running service. One file per feature, named for the feature doc that owns the code (`docs/features/auth.md` → `docs/tests/auth.md`).
|
|
3
|
+
Truth document. Use for: **how to verify one feature's promised behavior by driving the real product** — the browser, the CLI binary, HTTP against the running service. One file per feature, named for the feature doc that owns the code (`docs/features/auth.md` → `docs/tests/auth.md`).
|
|
4
|
+
|
|
5
|
+
**Frontmatter is two keys, and `verifies:` is the load-bearing one:**
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
---
|
|
9
|
+
type: checklist # required tooling key — genre stamp
|
|
10
|
+
verifies: [docs/features/auth.md] # required — the doc(s) whose promises this proves
|
|
11
|
+
---
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
A checklist owns no code, so it carries no `owns:`. What it carries instead is the edge back to
|
|
15
|
+
its subject. **The filename convention is not the edge** — a same-named file is a coincidence a
|
|
16
|
+
resolver cannot trust, it breaks the moment a checklist spans two docs or a feature is renamed, and
|
|
17
|
+
it leaves nothing to implicate this file when its subject changes. Without `verifies:` a checklist
|
|
18
|
+
is unreachable: no change reaches it, so it goes stale and the staleness is undetectable. Several
|
|
19
|
+
values are correct where one checklist proves promises two docs decide; the inverse — which
|
|
20
|
+
checklists cover a feature — is generated, never listed in the feature doc.
|
|
4
21
|
|
|
5
22
|
**This is the driving script, and it is the source the suite is drawn from.** At the verification gate it does double duty: rung 1 drives the real surface *against these steps*, and rung 2 pins the durable subset *of these assertions* as suite tests. Writing it is therefore not a report written afterwards — it is authored or updated **before** the driving, so the gate has something to execute and the tests have something to be derived from. Which assertions earn a permanent test is decided by the `testing` skill (gate mode) § What earns a suite test, and each section records the answer on its `Pinned:` line.
|
|
6
23
|
|
|
@@ -12,6 +12,7 @@ owns: ["src/feature/**", "src/other/file.ts"] # required — code-path globs t
|
|
|
12
12
|
module: billing # optional — single bounded-context label; omit if cross-cutting
|
|
13
13
|
keywords: [invoice, pdf] # required — discovery terms
|
|
14
14
|
status: active # required — active | draft | deprecated
|
|
15
|
+
applies: [docs/architecture/common/security.md] # optional — architecture docs this surface is BOUND by (obligation, not a bibliography); omit when none
|
|
15
16
|
last-reviewed: YYYY-MM-DD # optional tooling key — stamped on substantive review/update; the maintenance agent ranks audit priority by it (metadata, never narrated in the body)
|
|
16
17
|
type: feature # required tooling key — genre stamp (generator-written or authored; enum = the template names), read by tooling (UI grouping, protected-file mapping); preserve on edits, never narrate
|
|
17
18
|
---
|
|
@@ -1,9 +1,11 @@
|
|
|
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`;
|
|
3
|
+
Record. Use for: every implementation plan. Plans are created here directly with `Status: Draft`; the plan gates (`boundary`, then `testing` plan-gate mode) run in the same pass that writes the plan, against the still-`Draft` file — a plan is not finished, and is not shown as finished, until its `Gates:` line carries every verdict, so what the user approves is the gated plan. 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
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
|
+
**Both ledgers are read by a wall, so their cells obey a table's grammar.** A literal pipe inside a cell is written `\|` — the parser splits on unescaped pipes and unescapes what survives, so an escaped one is safe, and an unescaped one silently adds a column and shifts every later cell left. Prefer rephrasing (`` `install` or `update` ``) over escaping where the sentence allows it: the cell then reads the same to a person and to the wall.
|
|
8
|
+
|
|
7
9
|
**Doc Obligations is the one ledger the plan's author writes.** Deriving tests from behavior is judgment, which is why `testing` owns that section and it is marked never-hand-author; resolving Proposed Changes' file paths to the feature docs that own them is a **lookup** against the ownership index already injected at session start. Different work, different rule — fill it yourself, while the touch list is fresh.
|
|
8
10
|
|
|
9
11
|
The **`Gates:`** line carries one machine-read segment. The compliance hook greps **`Boundary:`** — implementation edits inside the plan's scope are blocked while it reads `Boundary: pending`; the `boundary` skill rewrites it to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`. The other two gates are enforced by **section presence**: a plan with no `## Test Obligations` or no `## Doc Obligations` section blocks the same edits — the sections are the artifacts, and the gate derives from them, not from a claim about them. Never hand-edit the line to skip a gate.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Truth document.** States what a person sees and can do on a surface, and what the surface promises never to do. Present tense, like every truth document.
|
|
4
4
|
|
|
5
|
-
**`type: ux
|
|
5
|
+
**`type: ux`**, plus `applies:` — the architecture docs this surface is bound by (the state vocabulary, the routing doctrine, the information architecture it answers to). Same key and same narrow reading as a feature doc's: obligation, not a reading list.
|
|
6
6
|
|
|
7
7
|
**Why this genre exists.** A feature doc (LLD) states what a feature is, how its data flows, and which contracts it honours — all of which stay plausibly true through a rewrite of the surface. Nothing else in the repo states what a person *observes*. So a change to the wire lands, the interface absorbs it, and the absorption reads as maintenance: no test fails, no lint fires, no document is contradicted. This document is the thing a silent change contradicts.
|
|
8
8
|
|
package/lib/install/plan.mjs
CHANGED
|
@@ -207,6 +207,27 @@ function ruleExcluded(sub, rel, targetDir) {
|
|
|
207
207
|
return !!gate && !gate(targetDir);
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
// The marker is a projection of the bundle with ONE exception, and the exception is load-bearing:
|
|
211
|
+
// `docsRoot` belongs to the CONSUMER, not to us. A repo that already owns `docs/` upstream renames
|
|
212
|
+
// the harness tree (`docs-nurix/`) and declares it here; the manifest only ever supplies the
|
|
213
|
+
// default for a repo that has not. Writing the bundle's value unconditionally would silently revert
|
|
214
|
+
// such a repo to `docs/` on the next roll — the install succeeds, every hook runs, and the ownership
|
|
215
|
+
// index quietly empties, which is exactly the failure the field trial hit by hand.
|
|
216
|
+
//
|
|
217
|
+
// Read from the target, never from the bundle. An absent or unreadable marker returns nothing and
|
|
218
|
+
// the manifest default stands.
|
|
219
|
+
function consumerOwnedMarkerKeys(targetDir) {
|
|
220
|
+
if (!targetDir) return {};
|
|
221
|
+
try {
|
|
222
|
+
const prior = JSON.parse(readFileSync(join(targetDir, '.claude', 'harness.json'), 'utf8'));
|
|
223
|
+
return typeof prior.docsRoot === 'string' && prior.docsRoot.trim()
|
|
224
|
+
? { docsRoot: prior.docsRoot.trim() }
|
|
225
|
+
: {};
|
|
226
|
+
} catch (e) {
|
|
227
|
+
return {};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
210
231
|
export function planHarnessFiles(bundleDir, targetDir = null) {
|
|
211
232
|
const claudeMdPath = join(bundleDir, 'CLAUDE.md');
|
|
212
233
|
if (!existsSync(claudeMdPath)) {
|
|
@@ -288,7 +309,7 @@ export function planHarnessFiles(bundleDir, targetDir = null) {
|
|
|
288
309
|
if (existsSync(markerPath)) {
|
|
289
310
|
files.push({
|
|
290
311
|
path: '.claude/harness.json',
|
|
291
|
-
content: asJson({ ...meta, owned }),
|
|
312
|
+
content: asJson({ ...meta, ...consumerOwnedMarkerKeys(targetDir), owned }),
|
|
292
313
|
policy: 'own',
|
|
293
314
|
});
|
|
294
315
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.4",
|
|
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": "dfcdee7d275f"
|
|
46
46
|
}
|