@nurix/etna 0.20.2 → 0.20.3
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 +13 -9
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/CLAUDE.md +5 -1
- 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 +25 -17
- 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 +13 -9
- 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 +2 -0
- 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 +20 -4
- 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 +2 -0
- 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 +13 -9
- 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 +2 -0
- 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
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "delivery",
|
|
3
3
|
"name": "delivery",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.22",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
8
8
|
".vscode/settings.json"
|
|
9
9
|
],
|
|
10
10
|
"seeds": [],
|
|
11
|
-
"
|
|
11
|
+
"tools": [],
|
|
12
|
+
"docsRoot": "docs",
|
|
13
|
+
"digest": "784354742e46"
|
|
12
14
|
}
|
|
@@ -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
|
---
|
|
@@ -4,6 +4,8 @@ Record. Use for: every implementation plan. Plans are created here directly with
|
|
|
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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: maintenance
|
|
3
|
-
description: The single docs steward. Default behavior is the docs-vs-code coherence audit — detects and fixes docs drifting from code (stale docs, broken ownership, unowned source paths, architecture/backlog drift). An additive **baseline mode** layers
|
|
3
|
+
description: The single docs steward. Default behavior is the docs-vs-code coherence audit — detects and fixes docs drifting from code (stale docs, broken ownership, unowned source paths, architecture/backlog drift). An additive **baseline mode** layers reconcile on top: when the repo's `harness-version` marker is missing or behind — **or its documents are behind their genre's current schema even though the folder tree is not** — it converges `docs/` to the installed harness (scaffold the standard tree, fold dissolved-shape artifacts forward, converge every truth document to its template's current schema while leaving frozen records alone, stamp the version, propose modules) before running the audit. Mode is auto-selected from the marker. Run weekly, after major changes, or to baseline/upgrade a repo. Cross-cutting contract analysis is the `boundary` skill (`check` for plans, `scan` for diffs) — it derives contracts dynamically from source; there are no persisted boundary docs to audit. Triggers on "run maintenance", "docs health", "baseline this repo", "reconcile the harness", "upgrade the harness", "is this repo on the current harness", "/maintenance".
|
|
4
4
|
tools: Read, Glob, Grep, Edit, Write, Bash
|
|
5
5
|
model: opus
|
|
6
6
|
---
|
|
@@ -28,7 +28,9 @@ Decide the mode once, up front, from the version marker — then run.
|
|
|
28
28
|
| behind on minor/patch | **probe first**, below |
|
|
29
29
|
| equal to installed | **audit only** (the default spine) |
|
|
30
30
|
|
|
31
|
-
**The probe.** Run the Step B2 detection
|
|
31
|
+
**The probe.** Run the Step B2 **and B2b** detection passes read-only: if any `*.bkp.*` file sits under `docs/` (a fold awaiting its pass), **or** any **scaffolded file** in the target tree is missing (`docs/README.md`, `docs/structure.md`, `docs/history/CHANGELOG.md`, `docs/history/backlog.md`, `docs/memory/main.md`), **or B2b finds truth documents behind their genre's current schema**, the shape *did* change ⇒ **baseline mode**. Only when the probe finds nothing ⇒ re-stamp the version (no reshape), then audit.
|
|
32
|
+
|
|
33
|
+
**The third clause is not optional, and it is the one that used to be missing.** A repo can hold the current folder tree, every scaffold present, and still have every document in it written to a schema two minors old — because a genre's shape lives in its template, not in the directory layout. Probing only for absent scaffolds meant such a repo re-stamped its version and declared itself current, which is how a stamp comes to certify a tree nobody converged. **The version marker asserts the documents match the installed harness, not merely that the folders do.**
|
|
32
34
|
|
|
33
35
|
> **Never probe on a missing folder.** Git does not track empty directories, so an unused folder is absent on every clone — reading that as drift puts the repo in baseline mode *forever*. Only the five scaffolded files above are shape signals; every folder in the target tree is created on first use.
|
|
34
36
|
3. **Precondition for baseline mode:** a root `CLAUDE.md` must exist. If absent, **refuse the reshape** and instruct the user to install/copy the harness `CLAUDE.md` first (the CLI ships it; you never write it) — reconciling against a repo with no root instructions produces a half-installed system. Detect-and-instruct, not a hard gate on the audit.
|
|
@@ -60,6 +62,14 @@ For each `owns:` glob across the feature docs:
|
|
|
60
62
|
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). `node .claude/hooks/check-ownership.mjs` reports these mechanically alongside dead globs (`overlaps()` in `lib/ownership.mjs`); a glob at strictly higher specificity carving out of a broader `**` is the designed layering and is never flagged. Surface a real overlap as a question — narrowing someone's glob is an author's call, not remediation.
|
|
61
63
|
- **Stamp `type:` where missing.** Every doc carries a `type:` genre key (enum = the `documentation` skill's template names); docs predating the key are stamped mechanically — the genre is readable from placement and shape (a `docs/features/` doc is `type: feature`, an AD is `type: architecture-decision`). Stamping is remediation, not a question; an ambiguous genre is backlogged instead.
|
|
62
64
|
|
|
65
|
+
**Then validate the doc→doc edges — `owns:` is only half the graph.** `node .claude/hooks/check-ownership.mjs` and the session-injected **doc edge index** carry both directions; read the edge index and check three things:
|
|
66
|
+
|
|
67
|
+
- **Dangling edges.** A `verifies:` or `applies:` naming a doc that does not exist is remediation, not a question — fix the path, or drop the edge when its target was deleted deliberately. The generated index marks these `← TARGET MISSING`.
|
|
68
|
+
- **Checklists with no `verifies:`.** Every `docs/tests/` file names the doc whose promises it proves. A checklist without one is unreachable — nothing implicates it when its subject moves — so it is the single highest-value backfill in the tree, and it is mechanical wherever a same-named feature doc exists (`docs/tests/auth.md` → `docs/features/auth.md`). Where the name does not match, ask rather than guess.
|
|
69
|
+
- **Architecture docs nothing declares.** An architecture doc named by no `applies:` is either genuinely unbound or a doctrine everyone obeys and nobody declares. Both are worth reporting; neither is auto-fixable, because `applies:` states an obligation only the consuming doc's author can assert. Report the list; backlog the judgement.
|
|
70
|
+
|
|
71
|
+
**Never author an inverse to fix a gap.** A doc listing its own verifiers or consumers is a defect — the inverse is generated. If the index looks wrong, the frontmatter at the consuming end is what is wrong.
|
|
72
|
+
|
|
63
73
|
For each code symbol or path a doc explicitly **names** (function/export/route/type):
|
|
64
74
|
|
|
65
75
|
- Verify it resolves in the code, independent of git-churn — Step 3's churn gate is blind to churn-free false claims.
|
|
@@ -167,14 +177,33 @@ Detecting and folding old-shape artifacts is what makes a baseline more than a f
|
|
|
167
177
|
| `*.bkp.*` files under `docs/` (the installer's first-contact backups of the project's pre-existing files) | **fold the content, then delete the backup.** Classify each backup's content by nature and land it in the current shape — a backed-up changelog's rows into `docs/history/CHANGELOG.md` and per-entry files, a backed-up structure/readme's real claims into the scaffolded truth doc, a backed-up feature doc's content merged into its sibling (or adopted whole if the sibling is a pristine seed). Content is folded, never discarded silently; what cannot be classified goes to the backlog with the backup kept. When the fold lands, delete the `.bkp` file |
|
|
168
178
|
| `*.bkp.*` files at the repo root or in config (`CLAUDE.bkp.md`, `.prettierrc.bkp.json`, …) | **never merge these.** Top-level config is harness-owned by design; list each one to the user as their call, and leave the file in place |
|
|
169
179
|
| no `harness-version` in `structure.md` | stamp it (Step B4) |
|
|
180
|
+
| `docs/tests/` checklists carrying **no frontmatter** (the shape before the graph — the genre was exempt) | **backfill `type: checklist` + `verifies:`.** Mechanical where a same-named feature or UX doc exists; where the name does not match, list the checklist to the user with your best candidate and let them confirm. This is the row that upgrades a repo from a tree of files to a graph — without it every checklist in an older repo stays unreachable, and nothing detects that |
|
|
181
|
+
| feature / UX docs with no `applies:`, in a tree whose `docs/architecture/` is non-empty | **report, never invent.** `applies:` is an obligation the doc's author asserts, not a similarity a scan can infer; a guessed edge is worse than a missing one because it reads as declared. List the candidates (architecture docs the feature's prose already links) and backlog the confirmation |
|
|
170
182
|
|
|
171
183
|
**The support window: three past minor versions.** The fold table covers shapes at most three minors behind the installed harness — which today means the rows above, because every older dissolved construct (`docs/routing.md`, `docs/modules/`, pillars, the Refinement Ledger, the ADR genre, legacy brainstorm folders, …) predates the window. An older shape is **reported, never improvised**: name what the probe found, point the user at etna's dated migration runbooks (`docs/history/migrations/` in the forge), and recommend a fresh baseline over an archaeological fold — a repo more than three minors behind re-baselines rather than migrates.
|
|
172
184
|
|
|
185
|
+
### Step B2b — Converge documents to their genre's current schema
|
|
186
|
+
|
|
187
|
+
B2 folds artifacts whose **shape dissolved** — a file in the wrong place or the wrong genre. This step handles the other axis, and it is the one that silently rots a repo that otherwise looks current: a document in the **right folder with the right name** whose *internal* schema is behind. A changelog entry missing three of its seven required fields, a checklist with no `verifies:` edge, a feature doc predating `applies:`, an AD on the old key vocabulary — none of these move a folder, so nothing structural detects them, and a repo can pass every scaffold check while no document in it matches the installed harness.
|
|
188
|
+
|
|
189
|
+
**Read the schema from the `documentation` skill's templates, never from memory.** The template is the contract; your recollection of it is a version of unknown age. For each genre, take the required frontmatter keys and required sections from the template file itself, then check the repo's documents of that genre against them.
|
|
190
|
+
|
|
191
|
+
**The two natures split the treatment, and the split is absolute.**
|
|
192
|
+
|
|
193
|
+
| Nature | Genres | Treatment |
|
|
194
|
+
| --- | --- | --- |
|
|
195
|
+
| **Truth document** | features, ux, architecture, operations, `docs/tests/` checklists, `structure.md`, READMEs | **Converge in place.** These state what is true now, so rewriting them to the current schema is not revision, it is the genre working as designed. Add missing frontmatter keys, add missing required sections, rename keys the template renamed |
|
|
196
|
+
| **Record** | plans, changelog entries, migration runbooks, brainstorm records | **Never rewritten.** A completed plan and a committed entry are frozen — immutability begins at commit, and a record edited to match a newer schema stops being evidence of what happened. Report non-conformance; repair only the **living index** that points at the record (an absent `docs/history/CHANGELOG.md` row is an index defect, not a record defect, and is backfilled with a note saying so) |
|
|
197
|
+
|
|
198
|
+
**Never invent content to satisfy a field.** A frozen changelog entry missing `Skills:` is missing the *judgement* that field carries, and no later session holds it — writing one is fabrication wearing a schema's clothes. Report it, backlog the pattern if it recurs, and leave the entry alone. The same restraint applies to a truth document: a genre's new section that needs knowledge the repo does not contain is raised as a question, not filled with plausible prose.
|
|
199
|
+
|
|
200
|
+
**Scale gate — confirm once, not per file.** Count the convergences first and fold them into B3's single reconcile table as `Fold` rows. Where the count is large (roughly **20+ files, or any change spanning more than one genre**), state the counts per genre in that table and take B3's one approval for the whole set — a per-file prompt turns a mechanical upgrade into an interrogation and gets abandoned halfway, which leaves a tree half-converged and a version marker that lies about it. Where a genre's convergence is judgement-heavy rather than mechanical, split it out as its own row so approval is informed rather than bundled.
|
|
201
|
+
|
|
173
202
|
### Step B3 — Present the reconcile plan, then execute
|
|
174
203
|
|
|
175
204
|
Table: `| File | Action | From | To | Reason |` (actions: `Move`, `Merge into {target}`, `Delete`, `Keep`, `Create`, `Fold`). For a fresh baseline this is mostly `Create`; for an upgrade it is mostly `Fold`/`Move`. Ask once: **"Approve this reconcile?"** — you own this interaction (the `documentation` skill is a library and has none).
|
|
176
205
|
|
|
177
|
-
On approval, execute: create the folder structure; move/fold/merge/delete per the plan; add `Status:` to plan files; for each feature doc, derive and write its `owns:` globs (+ `module:` label + `keywords:`) from the source it describes by calling the `documentation` skill's **frontmatter derivation**; merge scattered backlogs into `docs/history/backlog.md`; fix links broken by moves.
|
|
206
|
+
On approval, execute: create the folder structure; move/fold/merge/delete per the plan; apply B2b's schema convergences to the truth documents (records are reported, never rewritten); add `Status:` to plan files; for each feature doc, derive and write its `owns:` globs (+ `module:` label + `keywords:`) from the source it describes, and each checklist's `verifies:` edge, by calling the `documentation` skill's **frontmatter derivation**; merge scattered backlogs into `docs/history/backlog.md`; fix links broken by moves.
|
|
178
207
|
|
|
179
208
|
Scaffold missing mandated files **by calling the `documentation` skill for each template** — you orchestrate, the skill supplies templates: `docs/structure.md` (with `harness-version`), `docs/history/CHANGELOG.md`, `docs/history/backlog.md`, `docs/memory/main.md`, and the repo-root `README.md` (filled from the real lockfile/scripts/layout — **never overwrite an existing root README**).
|
|
180
209
|
|
|
@@ -182,6 +211,8 @@ Scaffold missing mandated files **by calling the `documentation` skill for each
|
|
|
182
211
|
|
|
183
212
|
Write `harness-version: <installed version>` (the semver from `.claude/harness.json`) into `docs/structure.md` frontmatter. This single field is the repo's baseline-and-version sentinel — there is no second marker (no `epoch`, no `docs/memory/index.md` "is-baselined" check).
|
|
184
213
|
|
|
214
|
+
**Stamp only what converged.** The marker asserts the repo's *documents* match the installed harness, not merely its folders — so it is written after B2b has run and its truth-document convergences have landed. Where B2b's set was too large to finish in one pass, stamp nothing and say what remains: a version marker ahead of the tree it describes is worse than an absent one, because every later probe trusts it and skips the work.
|
|
215
|
+
|
|
185
216
|
### Step B5 — Module proposal
|
|
186
217
|
|
|
187
218
|
Modules are **labels, declared, never inferred from physical layout**. Derive the repo's candidate bounded contexts directly from the code and docs: distinct wire surfaces (who owns routes/handlers), schema/data-access ownership, entry points and process units, plus any contexts `docs/architecture/` already names.
|
|
@@ -206,6 +237,8 @@ Apply the **§ Strategy memory** retirement procedure to `docs/memory/maintenanc
|
|
|
206
237
|
- **Enforce the memory cap on every run** (§ Strategy memory) — you are its only writer, so no one else can do it.
|
|
207
238
|
- **A clean report is valid** — do not invent issues.
|
|
208
239
|
- **Declarative, idempotent.** Re-running baseline mode on a current repo reshapes nothing — a second pass is just the audit plus the module proposal. Converge to the current shape; never replay a version ladder; never reconstruct a past shape.
|
|
240
|
+
- **Brownfield extraction (operator-ruled 2026-08-19).** When baseline mode meets a repo with a real source tree and few or no feature docs, the module *proposal* is not enough — run the extraction pass: produce feature docs for the recognized modules (docs/features/, full frontmatter: `owns:` · `module:` · `keywords:` · `status:` · `last-reviewed:` · `type: feature` · `derivation: "extracted (baseline YYYY-MM-DD, codegraph + source read)"`), plus the architecture, tests, and operations layers. Use the `codegraph` skill where installed — localize through the index before reading; the docs record intent, the index answers facts. **Every extracted feature doc carries a `## History & Ownership` section** — top contributors with recency, churn posture, and bus-factor notes, dated "as of YYYY-MM-DD" (operator ruling: this section is mandatory in extraction output even though doc-lint flags dated narrative in truth docs; the findings are accepted for now, and a future harness version may retire the section in favor of live index pointers). `status:` is evidence-graded: `deprecated` only when history shows the area dead, never by guess. Scale by fan-out (one extraction lane per module, a completeness critic over the result) rather than one long cold read.
|
|
241
|
+
- **Tool tiers.** The SessionStart context names each manifest-declared tool with its tier; calibrate the run to it, never stall on it. **Present-fresh** (`Tool: codegraph 0.2.0`): use it — check `codegraph projects` for staleness, re-run `codegraph index` if HEAD has moved, then localize every question through the index before reading source. **Present-stale** (below the catalog minimum): still use it, but note the version gap and the upgrade command in the run report — a stale contract may lack answer shapes the skill assumes. **Absent** (`Tool: codegraph MISSING`): fall back to the cold method (Glob/Grep/read), state in the run report that the tool was absent and give the install command — degraded output is acceptable, silent degradation is not.
|
|
209
242
|
- **One sentinel.** `docs/structure.md` `harness-version` is the only baseline/version marker.
|
|
210
243
|
- **Modules are labels, layout-blind.** Seed from the code's bounded contexts; never from `pnpm-workspace.yaml`/workspace walls. Propose, don't force; acting is a `module:` tag, never a folder.
|
|
211
244
|
- **You orchestrate docs; the `documentation` skill supplies templates.** It is a library — never expect it to baseline.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "etna",
|
|
3
3
|
"name": "etna",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.11",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
8
8
|
".vscode/settings.json"
|
|
9
9
|
],
|
|
10
10
|
"seeds": [],
|
|
11
|
-
"
|
|
11
|
+
"tools": [],
|
|
12
|
+
"docsRoot": "docs",
|
|
13
|
+
"digest": "227d2ebbe014"
|
|
12
14
|
}
|
|
@@ -30,9 +30,10 @@ Rules for the plan regime between the plan gate and the verification gate:
|
|
|
30
30
|
**A discrete, user-triggered step.** When implementation is complete, report it and offer the gate — never run it unbidden, and never flip `Status: Complete` without it. An explicit user acceptance of finished plan work — "ship it", "commit", "looks good" — counts as the trigger; open the gate without asking again. A plan with outstanding obligations stays open, whatever the code does. The procedure — checklist, drive-then-pin ladder, row discharge, what earns a durable test — is the `testing` skill (gate mode).
|
|
31
31
|
|
|
32
32
|
- **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.
|
|
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. **Authoring test files is dispatched fan-out by default** — at any nesting level, sub-agents on the cheapest capable tier write the pins from the checklist and ledger rows while the gating agent keeps the checklist, the driving, the cold-shell suite run, and every discharge: a sub-agent's green claim is a report, and the verdict is the re-run. Only a lone regression test riding a bug fix stays inline — briefing a sub-agent costs more than the test.
|
|
34
34
|
- **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
|
-
- **
|
|
35
|
+
- **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.
|
|
36
|
+
- **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
37
|
- **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
38
|
- **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
39
|
|
|
@@ -40,13 +41,15 @@ Rules for the plan regime between the plan gate and the verification gate:
|
|
|
40
41
|
|
|
41
42
|
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
43
|
|
|
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) | — | — | — | — |
|
|
44
|
+
| Stack | Unit / integration | Component | Rendered (7b) | Judged drive | E2E | HTTP mocking | Don't use |
|
|
45
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
46
|
+
| 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` |
|
|
47
|
+
| 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` |
|
|
48
|
+
| 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` |
|
|
49
|
+
| Cloudflare Workers | `vitest` + `@cloudflare/vitest-pool-workers` | — | — (nothing rendered) | the real wire against `wrangler dev` | `@playwright/test` against `wrangler dev` | `msw` | `jest` |
|
|
50
|
+
| Rust | `cargo test` (built-in) | — | the app window, driven by the OS layer where one exists | the running binary, watched | — | — | — |
|
|
51
|
+
|
|
52
|
+
**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
53
|
|
|
51
54
|
Test packages install at `latest` — outside the approved-packages pin set. Fake data comes from `@ngneat/falso` — **never** `@faker-js/faker` / `chance`.
|
|
52
55
|
|
|
@@ -55,6 +58,7 @@ Test packages install at `latest` — outside the approved-packages pin set. Fak
|
|
|
55
58
|
- **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
59
|
- **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
60
|
- **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.
|
|
61
|
+
- **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
62
|
- **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
63
|
- **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
64
|
|
|
@@ -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
|
---
|
|
@@ -4,6 +4,8 @@ Record. Use for: every implementation plan. Plans are created here directly with
|
|
|
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.3",
|
|
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": "d54acc9dc88e"
|
|
46
46
|
}
|